Class based composition/Mix-Ins in TS & JS made intuitive, elegant and powerful. If you have missed multiple inheritance in TS, you have missed this library.
Write Classes that describe Traits
of objects and compose those traits to form new, more complex types. If you don't like the official Mix-In syntax and its constraints, this library offers a viable (but experimental) alternative.
This project is experimental and is only roughly tested on V8 based platforms so far.
In object-oriented programming, composition is a design principle that allows for the creation of complex types by combining simpler, more focused types. Unlike inheritance, which defines a class in terms of another class, composition defines a class as containing instances of other classes. This approach offers several benefits:
- Modularity: By breaking down functionality into smaller, reusable components, code becomes more organized and easier to manage.
- Flexibility: Components can be easily replaced or updated without affecting the overall system.
- Maintainability: Smaller, well-defined components are easier to understand, test, and debug.
- Avoidance of Inheritance Pitfalls: Composition avoids issues like the fragile base class problem and deep inheritance hierarchies that can make code brittle and difficult to refactor.
The Fusium Composition Library leverages TypeScript's advanced type system together with JS proxies to provide a powerful and type-safe way to compose classes. It allows developers to fuse multiple classes into a single one, combining their behaviors and properties without the drawbacks of traditional inheritance.
This library is tiny and builds on top of three main ideas:
- Composable/Trait: A
Trait
orComposable
(they are equivalent, but just differently named to aid semantics in certain contexts) is a group of functionality and data that can be composed together. In practice this is bundled together as an ES6-class and can be either used standalone if sufficient or in aFusion
.Traits
may for example bedeletable
,writable
etc. - Fusions: A
Fusion
is the composition or combination of multipleTraits
into either a more complexTrait
or anObject
that exhibits theseTraits
. A super-trait like "streamable" may, for example, be composed ofTraits
likeasynchronous
andcancellable
and thus would be aFusion
of the two. AnObject
exhibiting these traits may for example be aDocument
that is aFusion
of thewritable
anddeletable
Traits. - Co-Traits: Sometimes certain
Traits
need to make assumptions about their "environment". Looking at an example:UIElement
for instance could be anObject
that may bedraggable
. This implies, however, that the element ismovable
, and thus has a position on screen - and thedraggable
trait will try to interact with that position in a way. It thus requires the traitspositioned
,movable
andpointable
to be other traits of an object that wants to have thedraggable
trait.positioned
,pointable
andmoveable
would beCo-Traits
ofdraggable
.draggable
assumes these traits andUIElement
must be aFusion
of at least these three traits together.
Fusium is just an npm install away:
npm install fusium-js
Here's a simple pseudo-code example concretizing the concepts above to compose a UIElement
:
import { Trait, CoTraits, FusionOf } from "fusium-js";
// Define the UIElement as a Fusion/Composition of the traits
// Not that UIElement does not contain any logic for wiring traits together as the traits
// already know their environment and how to interact with each other through their defined "Co-Traits"
class UIElement extends FusionOf(Draggable, Movable, Pointable, Positioned) {
constructor()
{
this.onPositionChanged(() => { /*...reRendering logic...*/});
}
}
// Define the individual traits as classes that extend from Trait
class Positioned extends Trait
{
protected x: number;
protected y: number;
public onPositionChanged = new EventEmitter();
setPosition(x, y) {...}
}
// This trait assumes and works with the x and y coordinates from the Co-Trait `Positioned`
class Movable extends CoTraits(Positioned)
{
move(dx: number, dy: number) {
this.setPosition(this.x + dx, this.y + dy);
}
}
// Note that in order to be Composable, a class (or the base end of its inheritance chain) needs to derive from `Composable` or `Trait`
class Pointable extends Trait
{
constructor()
{
environment.trackPointer(this)
}
onPointerPressed = new EventEmitter();
onPointerReleased = new EventEmitter();
onPointerMove = new EventEmitter();
}
// Note that the CoTraits-Function serves very little purpose on JS level (it's a typed Placeholder for `Trait`), but rather helps with type-safety on TS level
// Only the FusionOf-Function actually composes functionality on the JS level
class Draggable extends CoTraits(Pointable, Movable)
{
registerListeners()
{
this.onPointerPressed(() => this.onPointerMove(handlePressedAndMoved));
}
handlePressedAndMoved(dx, dy)
{
this.move(dx, dy);
}
}
This library should have little effect on runtime performance, except from marginal slow-down in object definition (as the types are composed once you call the "FusionOf"-Function) and object instantiation (single-line constructor-proxy). The resultant classes should still be optimizable by V8 etc. as the prototype chain remains flat and static once "FusionOf" was called, which normally happens while "parsing"/initially running a module.
For now this project is experimental - thus your feedback and input and thoughts are highly appreciated.
Fusium is open-source software licensed under the MIT License. This means you're free to use, modify, and distribute the library, provided you include the original license and copyright notice in any copies or substantial portions of the software.