fusium-js v0.3.1
Fusium
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.
State of the project
This project is experimental and is only roughly tested on V8 based platforms so far.
Introduction
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.
Core Concepts
This library is tiny and builds on top of three main ideas:
- Composable/Trait: A
TraitorComposable(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.Traitsmay for example bedeletable,writableetc. - Fusions: A
Fusionis the composition or combination of multipleTraitsinto either a more complexTraitor anObjectthat exhibits theseTraits. A super-trait like "streamable" may, for example, be composed ofTraitslikeasynchronousandcancellableand thus would be aFusionof the two. AnObjectexhibiting these traits may for example be aDocumentthat is aFusionof thewritableanddeletableTraits. - Co-Traits: Sometimes certain
Traitsneed to make assumptions about their "environment". Looking at an example:UIElementfor instance could be anObjectthat may bedraggable. This implies, however, that the element ismovable, and thus has a position on screen - and thedraggabletrait will try to interact with that position in a way. It thus requires the traitspositioned,movableandpointableto be other traits of an object that wants to have thedraggabletrait.positioned,pointableandmoveablewould beCo-Traitsofdraggable.draggableassumes these traits andUIElementmust be aFusionof at least these three traits together.
Installation
Fusium is just an npm install away:
npm install fusium-jsUsage Examples
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);
}
}Performance
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.
Contribution
For now this project is experimental - thus your feedback and input and thoughts are highly appreciated.
License
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.