The Practical Client

Creating New Classes from Old with Mixins

One of the features of JavaScript is the ability to create a mixin: An object that combines the functionality of several other objects. Mixins are one strategy for avoiding the problems associated with allowing a class to inherit from multiple other classes (as C++ does, for example) while still picking up the functionality of multiple other objects. Mixins allow one class to acquire the methods of another class but without invoking inheritance.

In JavaScript, mixin-like behavior can be invoked using the assign method on the Object constructor. This code, for example, copies the members of User into the Customer prototype to create a SuperCustomer:

var SuperCustomer = Object.assign(Customer.Prototype, User)

TypeScript doesn't really support mixins as dynamically as JavaScript does, but you can create something very much like it. In fact, there are two ways to do it.

Defining a Mixin: The TypeScript Team
One method is proposed by the TypeScript team and requires you to set up the mixin to accept the individual classes. The individual classes, however, require no special setup.

For example, my two individual classes, User and Customer, might look like this:

class User {
    id: string;
    password: string;
}
class Customer {
    BuyStuff():void {
        alert("Your Purchase has been made");
    }
}

While these are plain-old TypeScript classes, my mixin class requires some setup. I must define my SuperCustomer class as implementing the individual classes that I want to "mix in." Using the implements keyword treats the two other classes as interfaces ... and, unlike using extends to mimic inheritance (where I can inherit from only one other class), I can have my SuperCustomer implement as many interfaces as I want.

Of course, because I'm using implements rather than extends I don't get to pick up any of the functionality in either User or Customer. If I let Visual Studio 2017 write out the code required by implementing the classes I want to mix in, the default code in my BuyStuff method makes that pretty obvious:

class SuperCustomer implements Customer, User {
    id: string;
    password: string;
    BuyStuff(): void {
        throw new Error("Method not implemented.");
    }
}

Obviously if I call the BuyStuff method, I'm going to throw an error.

Equally obviously, I've given up the ability to dynamically mix in any other classes to my mixin class.

Unlike JavaScript, TypeScript doesn't have a built-in method for mixing in the other classes. The TypeScript team has provided a function that does that and I'm shamelessly copying it here:

function applyMixins(mixin: any, individuals: any[]) {
    individuals.forEach(individual => {
        Object.getOwnPropertyNames(individual.prototype).forEach(name => {
            mixin.prototype[name] = individual.prototype[name];
        });
    });
}

To use the applyMixins function, you pass the mixin class (SuperCustomer, in this case) and an array of the individual classes (User and Customer) to generate a new version of SuperCustomer. Adding applyMixins to my previous sample code gives this:

applyMixins(SuperCustomer, [Customer, User]);
let sc: SuperCustomer;
sc = new SuperCustomer();
sc.BuyStuff();

Now when I call SuperCustomer's BuyStuff method, I get the version of the method picked up from Customer rather than the Error thrown in the original SuperCustomer class.

This is a great strategy if you want to set up your target class to accept specific other classes that you will mix and match at run time.

Defining a Mixin: GitHub Deep Dive
An alternative method is proposed in TypeScript Deep Dive on GitHub by Basarat Ali Syed. Rather than enhance the mixin class so that it can accept specific individual classes, this process creates customized "mixin functions" that add functionality to the plain old TypeScript mixin class.

Syed's first step is to create a type that fulfills some of the functionality of the applyMixins function I used in the previous method:

type mixinApplicator = new (...args: any[]) => T;

The next step is to create mixin functions based on that type so that, when passed a class, the function returns the class with the mixin function's behavior added to it. Here are the equivalents to my Customer and User classes written as these mixin functions:

function Customer(Base: TBase) {
    return class extends Base {
        BuyStuff(): void {
            alert("Your purchase has been made");
        }
    };
}

function User(Base: TBase) {
    return class extends Base {
        id: string;
        password: string;
    };
}

My target class can now be any old class ... or very little class at all, as in this example:

class SuperCustomer {
}

To create a mixin, you pass your mixin class (SuperCustomer, in this case) to one of your mixin functions. If you want to mix in multiple classes, you can pass the output of that function to another of the mixin function. The final output is a mixin class that you can instantiate and use.

Code to create my SuperCustomer class from User and Customer would look like this:

let scProto = Customer(User(SuperCustomer));
let sc = new scProto();
sc.BuyStuff();

Here, again, BuyStuff will invoke the code in Customer.

This is an excellent strategy if you want to be able to create your mixin from a library of mixin functions (a library that you could continue to expand). With this method, however, if you wanted to pass SuperCustomer to another function, you'd have to declare the parameter accepting the SuperCustomer object as any, giving up type safety within that function.

One last note: It speaks to the power of the TypeScript compiler that both methods give you IntelliSense support and compile time checking. Even the more flexible second method supports TypeScript's type safety (well, until you pass your mixin to another method). I still think that's pretty cool.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube