Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on the Abstract Factory pattern! ๐ In this guide, weโll explore how to create families of related objects without specifying their concrete classes.
Youโll discover how Abstract Factory can transform your TypeScript development by organizing complex object creation. Whether youโre building UI components ๐จ, game systems ๐ฎ, or cross-platform applications ๐ฑ, understanding Abstract Factory is essential for writing flexible, maintainable code.
By the end of this tutorial, youโll feel confident using Abstract Factory patterns in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Abstract Factory
๐ค What is Abstract Factory?
Abstract Factory is like a restaurant chain with multiple locations ๐ฝ๏ธ. Each restaurant (factory) creates the same types of dishes (objects), but with local flavors. You order โpizzaโ and get Italian-style in Rome, New York-style in NYC, but itโs still pizza!
In TypeScript terms, Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes. This means you can:
- โจ Create related objects that work together
- ๐ Switch entire product families easily
- ๐ก๏ธ Ensure compatibility between objects
๐ก Why Use Abstract Factory?
Hereโs why developers love Abstract Factory:
- Consistency ๐: Ensures objects from the same family work together
- Flexibility ๐ป: Easy to switch between product families
- Extensibility ๐: Add new product families without changing existing code
- Type Safety ๐ง: TypeScript ensures compile-time correctness
Real-world example: Imagine building a game with different themes ๐ฎ. With Abstract Factory, you can switch between fantasy, sci-fi, or modern themes seamlessly!
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
// ๐จ Abstract products
interface Button {
render(): string;
click(): void;
}
interface Checkbox {
render(): string;
check(): void;
}
// ๐ญ Abstract factory
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// ๐ Concrete products - Light theme
class LightButton implements Button {
render(): string {
return "โ๏ธ Light Button";
}
click(): void {
console.log("โจ Light button clicked!");
}
}
class LightCheckbox implements Checkbox {
render(): string {
return "โฌ Light Checkbox";
}
check(): void {
console.log("โ
Light checkbox checked!");
}
}
๐ก Explanation: We define abstract interfaces for products (Button, Checkbox) and a factory interface that creates them!
๐ฏ Common Patterns
Here are patterns youโll use daily:
// ๐ Dark theme implementation
class DarkButton implements Button {
render(): string {
return "๐ Dark Button";
}
click(): void {
console.log("๐ซ Dark button clicked!");
}
}
class DarkCheckbox implements Checkbox {
render(): string {
return "โฌ Dark Checkbox";
}
check(): void {
console.log("โ
Dark checkbox checked!");
}
}
// ๐ญ Concrete factories
class LightThemeFactory implements UIFactory {
createButton(): Button {
return new LightButton();
}
createCheckbox(): Checkbox {
return new LightCheckbox();
}
}
class DarkThemeFactory implements UIFactory {
createButton(): Button {
return new DarkButton();
}
createCheckbox(): Checkbox {
return new DarkCheckbox();
}
}
๐ก Practical Examples
๐ Example 1: E-commerce Platform Themes
Letโs build something real:
// ๐๏ธ Product interfaces
interface ProductCard {
display(name: string, price: number): string;
}
interface ShoppingCart {
addItem(item: string): void;
getIcon(): string;
}
interface PaymentButton {
process(amount: number): string;
}
// ๐ช Store theme factory
interface StoreThemeFactory {
createProductCard(): ProductCard;
createShoppingCart(): ShoppingCart;
createPaymentButton(): PaymentButton;
}
// ๐ Holiday theme implementation
class HolidayProductCard implements ProductCard {
display(name: string, price: number): string {
return `๐ ${name} - $${price} (Holiday Special!)`;
}
}
class HolidayShoppingCart implements ShoppingCart {
private items: string[] = [];
addItem(item: string): void {
this.items.push(item);
console.log(`๐
Added ${item} to holiday cart!`);
}
getIcon(): string {
return "๐ท";
}
}
class HolidayPaymentButton implements PaymentButton {
process(amount: number): string {
return `๐ Processing holiday payment: $${amount} ๐`;
}
}
// ๐ญ Holiday factory
class HolidayThemeFactory implements StoreThemeFactory {
createProductCard(): ProductCard {
return new HolidayProductCard();
}
createShoppingCart(): ShoppingCart {
return new HolidayShoppingCart();
}
createPaymentButton(): PaymentButton {
return new HolidayPaymentButton();
}
}
// ๐ฎ Using the factory
class OnlineStore {
private factory: StoreThemeFactory;
constructor(factory: StoreThemeFactory) {
this.factory = factory;
}
renderProduct(name: string, price: number): void {
const card = this.factory.createProductCard();
console.log(card.display(name, price));
}
checkout(items: string[], total: number): void {
const cart = this.factory.createShoppingCart();
const payButton = this.factory.createPaymentButton();
items.forEach(item => cart.addItem(item));
console.log(`Cart icon: ${cart.getIcon()}`);
console.log(payButton.process(total));
}
}
// ๐ Let's shop!
const holidayStore = new OnlineStore(new HolidayThemeFactory());
holidayStore.renderProduct("TypeScript Book", 29.99);
holidayStore.checkout(["Book", "Course"], 59.98);
๐ฏ Try it yourself: Add a SummerThemeFactory with beach-themed emojis! ๐๏ธ
๐ฎ Example 2: Cross-Platform Game UI
Letโs make it fun:
// ๐ฎ Game UI elements
interface HealthBar {
display(health: number, maxHealth: number): string;
}
interface ScoreDisplay {
show(score: number): string;
}
interface PowerUpIndicator {
activate(powerUp: string): string;
}
// ๐ญ Platform factory
interface GameUIFactory {
createHealthBar(): HealthBar;
createScoreDisplay(): ScoreDisplay;
createPowerUpIndicator(): PowerUpIndicator;
}
// ๐ฑ Mobile implementation
class MobileHealthBar implements HealthBar {
display(health: number, maxHealth: number): string {
const hearts = "โค๏ธ".repeat(Math.ceil(health / 20));
return `๐ฑ Health: ${hearts} (${health}/${maxHealth})`;
}
}
class MobileScoreDisplay implements ScoreDisplay {
show(score: number): string {
return `๐ฑ Score: ${score} ๐`;
}
}
class MobilePowerUpIndicator implements PowerUpIndicator {
activate(powerUp: string): string {
const powerUpEmojis: Record<string, string> = {
speed: "โก",
shield: "๐ก๏ธ",
damage: "๐ฅ"
};
return `๐ฑ Power-Up: ${powerUpEmojis[powerUp] || "โจ"} ${powerUp}!`;
}
}
// ๐ฅ๏ธ Desktop implementation
class DesktopHealthBar implements HealthBar {
display(health: number, maxHealth: number): string {
const percentage = (health / maxHealth) * 100;
const bar = "โ".repeat(Math.ceil(percentage / 10));
return `๐ฅ๏ธ Health: [${bar.padEnd(10, "โ")}] ${percentage}%`;
}
}
// ๐ฎ Game manager using factory
class GameManager {
private uiFactory: GameUIFactory;
private health = 100;
private score = 0;
constructor(platform: "mobile" | "desktop") {
this.uiFactory = platform === "mobile"
? new MobileUIFactory()
: new DesktopUIFactory();
}
takeDamage(damage: number): void {
this.health -= damage;
const healthBar = this.uiFactory.createHealthBar();
console.log(healthBar.display(this.health, 100));
}
addScore(points: number): void {
this.score += points;
const scoreDisplay = this.uiFactory.createScoreDisplay();
console.log(scoreDisplay.show(this.score));
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Generic Abstract Factory
When youโre ready to level up, try this advanced pattern:
// ๐ฏ Generic factory with type constraints
interface Theme {
primary: string;
secondary: string;
emoji: string;
}
interface UIComponent<T extends Theme> {
theme: T;
render(): string;
}
abstract class AbstractUIFactory<T extends Theme> {
constructor(protected theme: T) {}
abstract createButton(): UIComponent<T>;
abstract createInput(): UIComponent<T>;
// ๐ช Shared logic
getThemeInfo(): string {
return `${this.theme.emoji} Theme: ${this.theme.primary}/${this.theme.secondary}`;
}
}
// ๐ Type-safe theme implementation
interface SpaceTheme extends Theme {
primary: "cosmic-blue";
secondary: "star-white";
emoji: "๐";
special: "nebula-effect";
}
class SpaceButton implements UIComponent<SpaceTheme> {
constructor(public theme: SpaceTheme) {}
render(): string {
return `${this.theme.emoji} Space Button with ${this.theme.special}`;
}
}
๐๏ธ Advanced Topic 2: Factory Registry Pattern
For the brave developers:
// ๐ Dynamic factory registration
class FactoryRegistry {
private factories = new Map<string, UIFactory>();
register(name: string, factory: UIFactory): void {
this.factories.set(name, factory);
console.log(`โจ Registered ${name} factory`);
}
getFactory(name: string): UIFactory {
const factory = this.factories.get(name);
if (!factory) {
throw new Error(`๐ซ Factory '${name}' not found!`);
}
return factory;
}
listFactories(): string[] {
return Array.from(this.factories.keys());
}
}
// ๐จ Usage
const registry = new FactoryRegistry();
registry.register("light", new LightThemeFactory());
registry.register("dark", new DarkThemeFactory());
registry.register("holiday", new HolidayThemeFactory());
const selectedFactory = registry.getFactory("holiday");
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Inconsistent Product Families
// โ Wrong way - mixing themes!
class BadFactory implements UIFactory {
createButton(): Button {
return new LightButton(); // ๐ฐ Light theme
}
createCheckbox(): Checkbox {
return new DarkCheckbox(); // ๐ฑ Dark theme - mismatch!
}
}
// โ
Correct way - consistent families!
class GoodFactory implements UIFactory {
createButton(): Button {
return new LightButton(); // โ๏ธ Light theme
}
createCheckbox(): Checkbox {
return new LightCheckbox(); // โ๏ธ Light theme - matches!
}
}
๐คฏ Pitfall 2: Forgetting to Implement All Products
// โ Dangerous - incomplete factory!
class IncompleteFactory implements UIFactory {
createButton(): Button {
return new LightButton();
}
createCheckbox(): Checkbox {
throw new Error("Not implemented"); // ๐ฅ Runtime error!
}
}
// โ
Safe - complete implementation!
class CompleteFactory implements UIFactory {
createButton(): Button {
return new LightButton();
}
createCheckbox(): Checkbox {
return new LightCheckbox(); // โ
All products implemented!
}
}
๐ ๏ธ Best Practices
- ๐ฏ Keep Families Cohesive: All products from a factory should work together
- ๐ Use Clear Naming:
LightThemeFactory
notFactory1
- ๐ก๏ธ Leverage TypeScript: Let interfaces enforce completeness
- ๐จ One Factory Per Theme: Donโt mix concerns
- โจ Consider Factory Methods: For simpler cases, factory methods might suffice
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Restaurant Chain System
Create a type-safe restaurant system with different cuisine factories:
๐ Requirements:
- โ Different cuisine types (Italian, Japanese, Mexican)
- ๐ Each cuisine has: appetizer, main course, dessert
- ๐จโ๐ณ Each dish has name, price, and preparation time
- ๐ Menu display functionality
- ๐จ Each cuisine needs themed emojis!
๐ Bonus Points:
- Add dietary restrictions (vegetarian, gluten-free)
- Implement combo meal creation
- Add restaurant rating system
๐ก Solution
๐ Click to see solution
// ๐ฝ๏ธ Restaurant system interfaces
interface Appetizer {
name: string;
price: number;
prepTime: number;
serve(): string;
}
interface MainCourse {
name: string;
price: number;
prepTime: number;
serve(): string;
}
interface Dessert {
name: string;
price: number;
prepTime: number;
serve(): string;
}
// ๐ญ Cuisine factory
interface CuisineFactory {
createAppetizer(): Appetizer;
createMainCourse(): MainCourse;
createDessert(): Dessert;
getCuisineType(): string;
}
// ๐ Italian implementation
class ItalianAppetizer implements Appetizer {
name = "Bruschetta";
price = 8.99;
prepTime = 10;
serve(): string {
return `๐
${this.name} - Fresh tomatoes on toasted bread`;
}
}
class ItalianMainCourse implements MainCourse {
name = "Margherita Pizza";
price = 15.99;
prepTime = 20;
serve(): string {
return `๐ ${this.name} - Classic pizza with mozzarella`;
}
}
class ItalianDessert implements Dessert {
name = "Tiramisu";
price = 7.99;
prepTime = 5;
serve(): string {
return `โ ${this.name} - Coffee-flavored Italian dessert`;
}
}
// ๐ฎ๐น Italian factory
class ItalianCuisineFactory implements CuisineFactory {
createAppetizer(): Appetizer {
return new ItalianAppetizer();
}
createMainCourse(): MainCourse {
return new ItalianMainCourse();
}
createDessert(): Dessert {
return new ItalianDessert();
}
getCuisineType(): string {
return "๐ฎ๐น Italian Cuisine";
}
}
// ๐ฑ Japanese implementation
class JapaneseAppetizer implements Appetizer {
name = "Edamame";
price = 5.99;
prepTime = 5;
serve(): string {
return `๐ฑ ${this.name} - Steamed soybeans with sea salt`;
}
}
class JapaneseMainCourse implements MainCourse {
name = "Salmon Sushi Platter";
price = 24.99;
prepTime = 15;
serve(): string {
return `๐ฃ ${this.name} - Fresh salmon sushi selection`;
}
}
// ๐ฝ๏ธ Restaurant using factories
class Restaurant {
private factory: CuisineFactory;
constructor(cuisineType: "italian" | "japanese" | "mexican") {
switch(cuisineType) {
case "italian":
this.factory = new ItalianCuisineFactory();
break;
case "japanese":
this.factory = new JapaneseCuisineFactory();
break;
case "mexican":
this.factory = new MexicanCuisineFactory();
break;
}
}
displayMenu(): void {
console.log(`\n๐ Menu - ${this.factory.getCuisineType()}`);
console.log("=".repeat(40));
const appetizer = this.factory.createAppetizer();
const main = this.factory.createMainCourse();
const dessert = this.factory.createDessert();
console.log("\n๐ฅ Appetizers:");
console.log(` ${appetizer.serve()} - $${appetizer.price}`);
console.log("\n๐ฝ๏ธ Main Courses:");
console.log(` ${main.serve()} - $${main.price}`);
console.log("\n๐ฐ Desserts:");
console.log(` ${dessert.serve()} - $${dessert.price}`);
}
orderComboMeal(): void {
const appetizer = this.factory.createAppetizer();
const main = this.factory.createMainCourse();
const dessert = this.factory.createDessert();
const total = appetizer.price + main.price + dessert.price;
const comboPrice = total * 0.9; // 10% discount
console.log(`\n๐ Combo Meal Special!`);
console.log(`Original: $${total.toFixed(2)}`);
console.log(`Combo Price: $${comboPrice.toFixed(2)} ๐ฐ`);
}
}
// ๐ฎ Test it out!
const italianRestaurant = new Restaurant("italian");
italianRestaurant.displayMenu();
italianRestaurant.orderComboMeal();
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create Abstract Factories for related object families ๐ช
- โ Ensure consistency between related objects ๐ก๏ธ
- โ Switch implementations easily and safely ๐ฏ
- โ Build flexible systems that adapt to changes ๐
- โ Apply the pattern in real-world scenarios! ๐
Remember: Abstract Factory helps you manage complexity when dealing with families of related objects! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered the Abstract Factory pattern!
Hereโs what to do next:
- ๐ป Practice with the restaurant exercise above
- ๐๏ธ Apply Abstract Factory to your current project
- ๐ Move on to our next tutorial: Builder Pattern
- ๐ Share your implementations with the community!
Remember: Every design pattern master started by understanding one pattern at a time. Keep coding, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ