Mixin Pattern
Compose objects from multiple sources to achieve multiple inheritance
Pattern Overview
🎯 Mixin Pattern
The Mixin Pattern enables multiple inheritance-like behavior by composing objects from multiple sources. It allows sharing functionality across different class hierarchies without traditional inheritance limitations.
Core Concepts
🔹 Mixins - Small, focused units of functionality that can be combined
🔹 Composition over Inheritance - Build complex objects by mixing behaviors
🔹 Multiple Inheritance - JavaScript/TypeScript doesn't support it natively, mixins provide alternative
🔹 Generic Mixins - Type-safe mixins that work with any base class
Real-World Applications
Frontend Frameworks - Vue.js mixins, React Higher-Order Components (HOCs) Utility Libraries - Lodash mixins, Observable patterns, Validation systems Enterprise Software - Audit trails, caching, logging, serialization Game Development - Entity component systems with mixed behaviorsTypeScript Implementation
Constructor Types - Generic constructor typing for type safety Interface Composition - Multiple interfaces implemented through mixins Method Resolution - Last mixin wins for conflicting methods Generic Constraints - Ensure type compatibility across mixin chainComposition Strategies
Linear Composition - A extends B extends C pattern Functional Mixins - Functions that return mixin classes Factory Functions - Create configured mixins with parameters Multiple Mixins - Combine several mixins into single classImplementation Benefits
✅ Code reuse - Share functionality across unrelated class hierarchies
✅ Flexible composition - Mix and match behaviors as needed
✅ Type safety - TypeScript provides full type checking for mixins
✅ Runtime flexibility - Can apply mixins conditionally
Examples:
const user = createTimestampedUser('John Doe', 'john@example.com');
console.log('User:', user.getDisplayName());
console.log('Created:', user.getFormattedTimestamp());
// Wait a moment and check age
setTimeout(() => {
console.log('Age (ms):', user.getAge());
}, 100);
user.setTimestamp(new Date('2023-01-01'));
console.log('Updated timestamp:', user.getFormattedTimestamp());User: John Doe
Created: 2024-01-01T12:00:00.000Z
Age (ms): 102
Updated timestamp: 2023-01-01T00:00:00.000Zconst product = createTrackedProduct('P001', 'Laptop', 999.99);
console.log('Product:', product.name, product.getDisplayPrice());
console.log('Created:', product.getFormattedTimestamp());
product.updatePrice(899.99);
console.log('Updated price:', product.getDisplayPrice());
const serialized = product.serialize();
console.log('Serialized length:', serialized.length);
const cloned = product.clone();
console.log('Clone price:', cloned.getDisplayPrice());Product: Laptop $999.99
Created: 2024-01-01T12:00:00.000Z
Updated price: $899.99
Serialized length: 156
Clone price: $899.99const post = createEnhancedBlogPost('My Blog Post', 'This is the content of my blog post.', 'John Doe');
// Add observer
post.addObserver((data) => console.log('Event:', data.event, 'at', data.timestamp));
console.log('Valid:', post.isValid());
console.log('Word count:', post.getWordCount());
post.updateContent('This is the updated content of my blog post with more words.');
console.log('Valid after update:', post.isValid());
const published = post.publish();
console.log('Published:', published);Valid: true
Word count: 9
Event: updated at 2024-01-01T12:00:01.000Z
Valid after update: true
Event: published at 2024-01-01T12:00:01.000Z
Published: trueConcepts
Complexity Analysis
Implementation
basic-mixins
// Timestamped Mixin
interface Timestamped {
timestamp: Date;
getAge(): number;
setTimestamp(date: Date): void;
}
function TimestampedMixin<TBase extends Constructor>(Base: TBase) {
return class extends Base implements Timestamped {
timestamp: Date = new Date();
getAge(): number {
return Date.now() - this.timestamp.getTime();
}
setTimestamp(date: Date): void {
this.timestamp = date;
}
getFormattedTimestamp(): string {
return this.timestamp.toISOString();
}
};
}
// Validatable Mixin
interface Validatable {
errors: string[];
isValid(): boolean;
addError(error: string): void;
clearErrors(): void;
}
function ValidatableMixin<TBase extends Constructor>(Base: TBase) {
return class extends Base implements Validatable {
errors: string[] = [];
isValid(): boolean {
this.validate();
return this.errors.length === 0;
}
addError(error: string): void {
if (!this.errors.includes(error)) {
this.errors.push(error);
}
}
clearErrors(): void {
this.errors = [];
}
protected validate(): void {
this.clearErrors();
// Override in subclasses for specific validation
}
};
}
// Example usage
class User {
constructor(public name: string, public email: string) {}
}
class TimestampedUser extends TimestampedMixin(User) {
constructor(name: string, email: string) {
super(name, email);
}
}
class ValidatedUser extends ValidatableMixin(User) {
constructor(name: string, email: string) {
super(name, email);
}
protected validate(): void {
super.validate();
if (!this.name || this.name.trim().length === 0) {
this.addError('Name is required');
}
if (!this.email || !this.email.includes('@')) {
this.addError('Valid email is required');
}
}
}