Polymorphism and yarn crafts

5 min readPublished November 24, 2019Updated May 02, 2022

This post is the eleventh and final post of a series adapted from my conference talk, Fun, Friendly Computer Science. Links to other posts in the series can be found at the end of this post.

You might have already gathered this about me, but I love to craft. My 2 main crafts are spinning yarn and crocheting, but I’ll dabble with anything. If you are an avid crafter, you may know multiple different crafts in which you could create a fabric. And depending on the type or style of fabric you want, you would use a different craft.

For example, if you want to make a nice, drapey sweater you may choose knitting because it creates a fabric better suited to garments and without bulky seams. If you wanted to create a blanket, you may choose to crochet because it’s really quick and creates a warm, thick fabric. And if you wanted to create a fabric that could later be used in a sewing project, you may choose to weave. But regardless of which craft you choose, you want to do the same thing: create fabric. If we were to model that programmatically, we would take advantage of polymorphism.

Polymorphism is an object-oriented concept where you use multiple classes in the same way so that their concrete class and their internal implementation don't matter. Inheritance, as we saw in the previous post, is one way to achieve polymorphism. But other ways include duck typing or using interfaces.

Duck typing is common in dynamically typed languages such as Ruby. It derives its name from “if it walks like a duck and talks like a duck, it’s a duck.” Duck typing is where the developer defines the same method and method signature (the parameters it takes) on multiple classes. It is also up to the developer to enforce that each of the method definitions returns the same type of object. This allows you to use the classes interchangeably at runtime because they all have the same method defined.

Interfaces are used in statically typed languages, such as C#. Interfaces are a concept in the code that a developer defines to enforce a contract. For example, you could define an interface with 3 methods and their corresponding parameters and return types (remember, we’re in static land where return types are defined 😉). Then, the compiler will enforce that any class that implements that interface must define those 3 matching methods and method signatures that you declared in your interface.

In our code example, we'll use duck typing to achieve polymorphism. In this example, we have a class for each yarn craft, knitting, weaving, and crocheting. You’ll notice that we don’t have a parent YarnCraft class, because we’re not using inheritance for polymorphism in this case. Instead, each class defines a createFabric method, allowing us to use them all the same way without caring about which concrete class we're using at that moment.

I’ve abbreviated the code samples in this blog post, but for the full example see my Javascript repo or if Ruby is more your thing, check out my Ruby repo.

Here is what the internal implementation of createFabric looks like for Knitting. You’ll notice that I have a createFabric method that takes a single parameter, numberOfRows. Internally, it handles how it stitches each row of the fabric.

createFabric(numberOfRows) {
  let fabric = [];
  for (let row = 0; row < numberOfRows; row++) {
    if (row % 2 === 0) {
      fabric.push(this.stitchRow(Knit));
    }
    if (row % 2 > 0) {
      fabric.push(this.stitchRow(Purl));
    }
  }
  return fabric;
}

stitchRow(stitch) {
  let row = "";
  for (let stitch = 0; stitch < this.rowLength; stitch++) {
    if (stitch === Knit) {
      row += this.knit();
    } else if (stitch === Purl) {
      row += this.purl();
    }
  }

  row += "Turn.\n";
  return row;
}

Then, we have Weaving. Again, we have a createFabric method defined on Weaving that takes a single parameter for numberOfRows. Internally, it weaves the weft over and under the warp instead of doing any stitching as we saw in Knitting, but it still returns the fabric that we’ve created.

createFabric(numberOfRows) {
  let fabric = [];
  for (let row = 0; row < numberOfRows; row++) {
    fabric.push(this.weaveRow(row));
  }
  return fabric;
}

weaveRow(rowNumber) {
  let row = ""
  for (let stitch = 0; stitch < this.rowLength; stitch++) {
    const isEvenRow = rowNumber % 2 === 0;
    const isEvenStitch = stitch % 2 === 0;
    if ((isEvenRow && isEvenStitch) || (!isEvenRow && !isEvenStitch)) {
      row += this.weaveWeftOverWarp();
    } else if ((isEvenRow && !isEvenStitch) || (!isEvenRow && isEvenStitch)) {
      row += this.weaveWeftUnderWarp();
    }
  }
  row += "Turn.\n";
  return row;
}

Finally, we have Crocheting. This is the simplest implementation of createFabric (just like real life—crochet is delightfully simple!). But even in the simple implementation, there is still a createFabric method with a numberOfRows parameter that returns the fabric we created.

createFabric(numberOfRows) {
  let fabric = [];
  for (let row = 0; row < numberOfRows; row++) {
    fabric.push(this.stitchRow());
  }
  return fabric;
}

stitchRow() {
  let row = "";
  for (let stitch = 0; stitch < this.rowLength; stitch++) {
    row += this.singleCrochet();
  }
  row += "Turn.\n";
  return row;
}

Now that we have our duck-typed yarn craft classes, polymorphism allows us to use them interchangeably. If we had a user interface where we asked the user to choose their favorite craft and decide how big they wanted their fabric, we could implement creating the fabric like this.

let YarnCraft;

const craft = // read from input;
const rowLength = // read from input;
const numberOfRows = // read from input;

if (craft === "knit") {
  YarnCraft = Knitting;
} else if (craft === "crochet") {
  YarnCraft = Crocheting;
} else if (craft === "weave") {
  YarnCraft = Weaving;
}

const chosenCraft = new YarnCraft(rowLength);
const fabric = chosenCraft.createFabric(numberOfRows);

Polymorphism is the last of the 4 principles of object-oriented programming. It can be a bit challenging to wrap your head around. But once you do, it can open a lot of doors to understanding how software design patterns are implemented and why they’re successful.

This is the final post in my series on Fun, Friendly Computer Science. If you liked this or any of the blog posts in this series, tweet me! I’d love to hear about what you learned or about what else you’d be interested in breaking down. Happy coding! 🙌

Well-Rounded Dev

Liked this post? Subscribe to receive semi-regular thoughts by email.

    I won't send you spam. Unsubscribe at any time.