+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 304 of 355

๐Ÿ“˜ Template Method: Algorithm Steps

Master template method: algorithm steps in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

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

Ever followed a recipe to cook your favorite dish? ๐Ÿณ You follow the same steps each time - prep ingredients, cook them, season to taste, and serve. But each dish has its own unique ingredients and cooking times! Thatโ€™s exactly what the Template Method pattern is all about - defining a recipe (algorithm) with fixed steps, but letting each dish (subclass) decide how to execute those steps! ๐ŸŽจ

In this tutorial, weโ€™ll master the Template Method pattern in TypeScript and see how it helps us create flexible, reusable algorithms. Get ready to become a master chef of code! ๐Ÿ‘จโ€๐Ÿณ

๐Ÿ“š Understanding Template Method Pattern

The Template Method pattern is like creating a blueprint for an algorithm where:

  • ๐Ÿ“‹ The skeleton stays the same - The main steps are defined in a base class
  • ๐ŸŽจ The details can vary - Subclasses implement specific behaviors
  • ๐Ÿ”’ The order is protected - The algorithm structure canโ€™t be changed
  • ๐ŸŽฏ Consistency is guaranteed - All variations follow the same flow

Think of it as a dance routine ๐Ÿ’ƒ - everyone follows the same sequence of moves, but each dancer adds their own style!

๐Ÿ”ง Basic Syntax and Usage

Letโ€™s start with a simple Template Method pattern:

// ๐ŸŽญ Abstract base class defining the template
abstract class GameLevel {
  // ๐ŸŽฎ Template method - defines the algorithm
  play(): void {
    this.start();      // ๐Ÿ‘‹ Step 1
    this.spawn();      // ๐ŸŽฏ Step 2
    this.gameplay();   // ๐ŸŽฎ Step 3
    this.finish();     // ๐Ÿ Step 4
  }

  // ๐Ÿ”ง Common implementation
  protected start(): void {
    console.log("๐ŸŽฌ Level starting...");
  }

  // ๐ŸŽจ Abstract methods - must be implemented
  protected abstract spawn(): void;
  protected abstract gameplay(): void;
  
  // ๐Ÿ”ง Hook method - can be overridden
  protected finish(): void {
    console.log("๐Ÿ Level complete!");
  }
}

// ๐Ÿฐ Concrete implementation
class CastleLevel extends GameLevel {
  protected spawn(): void {
    console.log("๐Ÿฐ Spawning knights and dragons!");
  }

  protected gameplay(): void {
    console.log("โš”๏ธ Battle in the castle!");
  }
}

// ๐Ÿš€ Another concrete implementation
class SpaceLevel extends GameLevel {
  protected spawn(): void {
    console.log("๐Ÿš€ Spawning aliens and spaceships!");
  }

  protected gameplay(): void {
    console.log("๐Ÿ‘ฝ Battle in space!");
  }

  // ๐ŸŽจ Override the hook method
  protected finish(): void {
    console.log("๐ŸŒŸ Mission accomplished! Return to Earth!");
  }
}

// ๐ŸŽฎ Let's play!
const castle = new CastleLevel();
castle.play();
// Output:
// ๐ŸŽฌ Level starting...
// ๐Ÿฐ Spawning knights and dragons!
// โš”๏ธ Battle in the castle!
// ๐Ÿ Level complete!

const space = new SpaceLevel();
space.play();
// Output:
// ๐ŸŽฌ Level starting...
// ๐Ÿš€ Spawning aliens and spaceships!
// ๐Ÿ‘ฝ Battle in space!
// ๐ŸŒŸ Mission accomplished! Return to Earth!

๐Ÿ’ก Practical Examples

Example 1: Coffee Shop Brewing System โ˜•

Letโ€™s create a beverage brewing system where different drinks follow similar preparation steps:

// โ˜• Abstract beverage class
abstract class Beverage {
  // ๐ŸŽฏ Template method for brewing
  brew(): void {
    this.boilWater();
    this.brewIngredients();
    this.pourInCup();
    if (this.wantsCondiments()) {  // ๐Ÿ”„ Hook for customization
      this.addCondiments();
    }
    this.serve();
  }

  // ๐Ÿ”ฅ Common steps
  private boilWater(): void {
    console.log("๐Ÿ’ง Boiling water to perfect temperature...");
  }

  private pourInCup(): void {
    console.log("๐Ÿฅค Pouring into cup...");
  }

  // ๐ŸŽจ Abstract methods
  protected abstract brewIngredients(): void;
  protected abstract addCondiments(): void;
  protected abstract serve(): void;

  // ๐Ÿช Hook method - can be overridden
  protected wantsCondiments(): boolean {
    return true;
  }
}

// โ˜• Coffee implementation
class Coffee extends Beverage {
  private withMilk: boolean = true;

  constructor(withMilk: boolean = true) {
    super();
    this.withMilk = withMilk;
  }

  protected brewIngredients(): void {
    console.log("โ˜• Brewing rich coffee grounds...");
  }

  protected addCondiments(): void {
    console.log("๐Ÿฅ› Adding creamy milk and sugar...");
  }

  protected serve(): void {
    console.log("โ˜• Your coffee is ready! Enjoy! โ˜•");
  }

  protected wantsCondiments(): boolean {
    return this.withMilk;
  }
}

// ๐Ÿต Tea implementation
class Tea extends Beverage {
  protected brewIngredients(): void {
    console.log("๐Ÿต Steeping delicate tea leaves...");
  }

  protected addCondiments(): void {
    console.log("๐Ÿ‹ Adding fresh lemon slice...");
  }

  protected serve(): void {
    console.log("๐Ÿต Your tea is ready! Relax and enjoy! ๐ŸŒธ");
  }
}

// ๐Ÿฅค Hot chocolate implementation
class HotChocolate extends Beverage {
  protected brewIngredients(): void {
    console.log("๐Ÿซ Melting rich chocolate...");
  }

  protected addCondiments(): void {
    console.log("๐Ÿฅ Adding fluffy marshmallows...");
  }

  protected serve(): void {
    console.log("๐Ÿซ Your hot chocolate is ready! So cozy! โ„๏ธ");
  }
}

// โ˜• Let's brew some drinks!
console.log("=== Coffee Shop Orders ===");

const latte = new Coffee();
latte.brew();
console.log();

const blackCoffee = new Coffee(false);
blackCoffee.brew();
console.log();

const tea = new Tea();
tea.brew();

Example 2: Data Processing Pipeline ๐Ÿ“Š

Create a flexible data processing system:

// ๐Ÿ“Š Abstract data processor
abstract class DataProcessor<T> {
  // ๐ŸŽฏ Template method for processing
  process(data: T[]): void {
    const validated = this.validate(data);
    const transformed = this.transform(validated);
    const analyzed = this.analyze(transformed);
    this.export(analyzed);
    this.notifyComplete();
  }

  // ๐Ÿ” Validation step
  protected validate(data: T[]): T[] {
    console.log("โœ… Validating data...");
    return data.filter(item => this.isValid(item));
  }

  // ๐ŸŽจ Abstract methods
  protected abstract isValid(item: T): boolean;
  protected abstract transform(data: T[]): T[];
  protected abstract analyze(data: T[]): any;
  protected abstract export(results: any): void;

  // ๐Ÿ“ข Hook for notifications
  protected notifyComplete(): void {
    console.log("โœจ Processing complete!");
  }
}

// ๐Ÿ’ฐ Sales data processor
interface SalesData {
  id: number;
  amount: number;
  date: string;
  product: string;
}

class SalesProcessor extends DataProcessor<SalesData> {
  protected isValid(item: SalesData): boolean {
    return item.amount > 0 && item.product.length > 0;
  }

  protected transform(data: SalesData[]): SalesData[] {
    console.log("๐Ÿ”„ Transforming sales data...");
    return data.map(sale => ({
      ...sale,
      amount: Math.round(sale.amount * 100) / 100  // ๐Ÿ’ฐ Round to cents
    }));
  }

  protected analyze(data: SalesData[]): any {
    console.log("๐Ÿ“ˆ Analyzing sales trends...");
    const total = data.reduce((sum, sale) => sum + sale.amount, 0);
    const average = total / data.length;
    
    return {
      totalSales: total,
      averageSale: average,
      topProduct: this.findTopProduct(data),
      salesCount: data.length
    };
  }

  private findTopProduct(data: SalesData[]): string {
    const productSales = new Map<string, number>();
    
    data.forEach(sale => {
      const current = productSales.get(sale.product) || 0;
      productSales.set(sale.product, current + sale.amount);
    });

    let topProduct = "";
    let maxSales = 0;
    
    productSales.forEach((sales, product) => {
      if (sales > maxSales) {
        maxSales = sales;
        topProduct = product;
      }
    });
    
    return topProduct;
  }

  protected export(results: any): void {
    console.log("๐Ÿ“ค Exporting sales report...");
    console.log(`๐Ÿ’ฐ Total Sales: $${results.totalSales.toFixed(2)}`);
    console.log(`๐Ÿ“Š Average Sale: $${results.averageSale.toFixed(2)}`);
    console.log(`๐Ÿ† Top Product: ${results.topProduct}`);
    console.log(`๐Ÿ“ˆ Total Transactions: ${results.salesCount}`);
  }

  protected notifyComplete(): void {
    console.log("๐ŸŽ‰ Sales report ready! Check your dashboard!");
  }
}

// ๐ŸŽฎ Usage
const salesData: SalesData[] = [
  { id: 1, amount: 99.99, date: "2024-01-01", product: "Gaming Mouse" },
  { id: 2, amount: 149.99, date: "2024-01-02", product: "Mechanical Keyboard" },
  { id: 3, amount: -10, date: "2024-01-03", product: "Invalid" },  // โŒ Will be filtered
  { id: 4, amount: 99.99, date: "2024-01-04", product: "Gaming Mouse" },
  { id: 5, amount: 279.99, date: "2024-01-05", product: "Gaming Monitor" }
];

const processor = new SalesProcessor();
processor.process(salesData);

Example 3: Game Character Creation ๐ŸŽฎ

Build a character creation system with consistent steps:

// ๐ŸŽฎ Character creation template
abstract class CharacterCreator {
  // ๐ŸŽฏ Template method for character creation
  createCharacter(name: string): void {
    console.log(`๐ŸŽจ Creating ${name}...`);
    this.selectClass();
    this.assignBaseStats();
    this.addSpecialAbilities();
    
    if (this.wantsCustomization()) {
      this.customizeAppearance();
    }
    
    this.finalizeCharacter(name);
  }

  // ๐ŸŽญ Abstract methods
  protected abstract selectClass(): void;
  protected abstract assignBaseStats(): void;
  protected abstract addSpecialAbilities(): void;
  protected abstract customizeAppearance(): void;

  // ๐Ÿช Hook methods
  protected wantsCustomization(): boolean {
    return true;
  }

  protected finalizeCharacter(name: string): void {
    console.log(`โœจ ${name} is ready for adventure!`);
  }
}

// โš”๏ธ Warrior creator
class WarriorCreator extends CharacterCreator {
  protected selectClass(): void {
    console.log("โš”๏ธ Class: Mighty Warrior");
  }

  protected assignBaseStats(): void {
    console.log("๐Ÿ’ช Stats: High Strength, High Defense");
  }

  protected addSpecialAbilities(): void {
    console.log("๐Ÿ›ก๏ธ Abilities: Shield Bash, Berserker Rage");
  }

  protected customizeAppearance(): void {
    console.log("๐ŸŽจ Appearance: Heavy armor, battle scars");
  }
}

// ๐Ÿง™โ€โ™‚๏ธ Wizard creator
class WizardCreator extends CharacterCreator {
  protected selectClass(): void {
    console.log("๐Ÿง™โ€โ™‚๏ธ Class: Wise Wizard");
  }

  protected assignBaseStats(): void {
    console.log("๐Ÿง  Stats: High Intelligence, High Mana");
  }

  protected addSpecialAbilities(): void {
    console.log("โœจ Abilities: Fireball, Teleport");
  }

  protected customizeAppearance(): void {
    console.log("๐ŸŽจ Appearance: Flowing robes, magical staff");
  }
}

// ๐Ÿน Quick ranger (no customization)
class QuickRangerCreator extends CharacterCreator {
  protected selectClass(): void {
    console.log("๐Ÿน Class: Swift Ranger");
  }

  protected assignBaseStats(): void {
    console.log("๐ŸŽฏ Stats: High Agility, High Perception");
  }

  protected addSpecialAbilities(): void {
    console.log("๐Ÿฆ… Abilities: Eagle Eye, Multi-shot");
  }

  protected customizeAppearance(): void {
    // ๐Ÿšซ Not called due to hook
  }

  protected wantsCustomization(): boolean {
    return false;  // ๐Ÿƒโ€โ™‚๏ธ Skip customization for quick creation
  }
}

// ๐ŸŽฎ Create some characters!
console.log("=== Character Creation ===\n");

const warriorCreator = new WarriorCreator();
warriorCreator.createCharacter("Thorin");
console.log();

const wizardCreator = new WizardCreator();
wizardCreator.createCharacter("Gandalf");
console.log();

const rangerCreator = new QuickRangerCreator();
rangerCreator.createCharacter("Legolas");

๐Ÿš€ Advanced Concepts

Advanced Template Method with Hooks and Guards ๐Ÿ”

// ๐Ÿ—๏ธ Advanced template with multiple hooks
abstract class BuildProcess {
  private errors: string[] = [];

  // ๐ŸŽฏ Main template method
  build(): boolean {
    try {
      if (!this.shouldBuild()) {
        console.log("๐Ÿšซ Build skipped");
        return false;
      }

      this.preBuild();
      
      if (!this.validate()) {
        this.handleValidationError();
        return false;
      }

      this.compile();
      
      if (this.needsOptimization()) {
        this.optimize();
      }

      this.package();
      
      if (this.shouldRunTests()) {
        const testsPassed = this.runTests();
        if (!testsPassed) {
          this.handleTestFailure();
          return false;
        }
      }

      this.deploy();
      this.postBuild();
      
      return true;
    } catch (error) {
      this.handleBuildError(error);
      return false;
    }
  }

  // ๐Ÿช Hook methods with defaults
  protected shouldBuild(): boolean {
    return true;
  }

  protected preBuild(): void {
    console.log("๐Ÿ”ง Preparing build environment...");
  }

  protected needsOptimization(): boolean {
    return true;
  }

  protected shouldRunTests(): boolean {
    return true;
  }

  protected postBuild(): void {
    console.log("โœ… Build completed successfully!");
  }

  // ๐ŸŽจ Abstract methods
  protected abstract validate(): boolean;
  protected abstract compile(): void;
  protected abstract optimize(): void;
  protected abstract package(): void;
  protected abstract runTests(): boolean;
  protected abstract deploy(): void;

  // ๐Ÿšจ Error handling hooks
  protected handleValidationError(): void {
    console.error("โŒ Validation failed!");
  }

  protected handleTestFailure(): void {
    console.error("โŒ Tests failed!");
  }

  protected handleBuildError(error: any): void {
    console.error("๐Ÿ’ฅ Build error:", error.message);
  }
}

// ๐ŸŒ Web app builder
class WebAppBuilder extends BuildProcess {
  private production: boolean;

  constructor(production: boolean = false) {
    super();
    this.production = production;
  }

  protected validate(): boolean {
    console.log("๐Ÿ” Validating dependencies...");
    return true;  // ๐ŸŽฏ Simplified for demo
  }

  protected compile(): void {
    console.log("๐Ÿ“ฆ Bundling JavaScript and CSS...");
  }

  protected optimize(): void {
    console.log("โšก Minifying and tree-shaking...");
  }

  protected package(): void {
    console.log("๐Ÿ“ค Creating deployment package...");
  }

  protected runTests(): boolean {
    console.log("๐Ÿงช Running unit and integration tests...");
    return true;  // โœ… All tests pass
  }

  protected deploy(): void {
    const target = this.production ? "production" : "staging";
    console.log(`๐Ÿš€ Deploying to ${target}...`);
  }

  protected needsOptimization(): boolean {
    return this.production;  // ๐ŸŽฏ Only optimize for production
  }
}

// ๐Ÿ“ฑ Mobile app builder
class MobileAppBuilder extends BuildProcess {
  private platform: "iOS" | "Android";

  constructor(platform: "iOS" | "Android") {
    super();
    this.platform = platform;
  }

  protected validate(): boolean {
    console.log(`๐Ÿ” Validating ${this.platform} SDK...`);
    return true;
  }

  protected compile(): void {
    console.log(`๐Ÿ“ฑ Compiling ${this.platform} app...`);
  }

  protected optimize(): void {
    console.log("๐Ÿ—œ๏ธ Optimizing app size...");
  }

  protected package(): void {
    const packageType = this.platform === "iOS" ? "IPA" : "APK";
    console.log(`๐Ÿ“ฆ Creating ${packageType} package...`);
  }

  protected runTests(): boolean {
    console.log("๐Ÿงช Running UI tests on simulator...");
    return true;
  }

  protected deploy(): void {
    const store = this.platform === "iOS" ? "App Store" : "Play Store";
    console.log(`๐Ÿ“ฒ Uploading to ${store}...`);
  }
}

// ๐Ÿ—๏ธ Let's build!
console.log("=== Web App Build ===");
const webBuilder = new WebAppBuilder(true);
webBuilder.build();

console.log("\n=== Mobile App Build ===");
const mobileBuilder = new MobileAppBuilder("iOS");
mobileBuilder.build();

โš ๏ธ Common Pitfalls and Solutions

โŒ Wrong: Exposing the template method steps

// โŒ Bad: Steps are public and can be called individually
class BadRecipe {
  public prepareIngredients(): void {
    console.log("Preparing...");
  }

  public cook(): void {
    console.log("Cooking...");
  }

  public serve(): void {
    console.log("Serving...");
  }
}

// ๐Ÿ˜ฑ Can call steps out of order!
const recipe = new BadRecipe();
recipe.serve();  // โŒ Serving before cooking!
recipe.cook();

โœ… Correct: Encapsulated template method

// โœ… Good: Template method controls the flow
abstract class GoodRecipe {
  // ๐ŸŽฏ Public template method
  cook(): void {
    this.prepareIngredients();
    this.cookFood();
    this.serve();
  }

  // ๐Ÿ”’ Protected steps - can't be called directly
  protected abstract prepareIngredients(): void;
  protected abstract cookFood(): void;
  protected abstract serve(): void;
}

class PastaRecipe extends GoodRecipe {
  protected prepareIngredients(): void {
    console.log("๐Ÿ Boiling water and preparing pasta...");
  }

  protected cookFood(): void {
    console.log("๐Ÿ”ฅ Cooking pasta al dente...");
  }

  protected serve(): void {
    console.log("๐Ÿฝ๏ธ Serving with fresh basil!");
  }
}

// โœ… Can only use the template method
const pasta = new PastaRecipe();
pasta.cook();  // โœ… Follows the correct order

โŒ Wrong: Forcing implementation of optional steps

// โŒ Bad: Forces all subclasses to implement everything
abstract class BadWorkflow {
  abstract initialize(): void;
  abstract validate(): void;
  abstract process(): void;
  abstract cleanup(): void;  // โŒ Not always needed!
  abstract notify(): void;   // โŒ Not always needed!
}

โœ… Correct: Using hooks for optional steps

// โœ… Good: Hooks provide flexibility
abstract class GoodWorkflow {
  execute(): void {
    this.initialize();
    
    if (this.shouldValidate()) {
      this.validate();
    }
    
    this.process();
    
    if (this.needsCleanup()) {
      this.cleanup();
    }
    
    if (this.shouldNotify()) {
      this.notify();
    }
  }

  // ๐ŸŽฏ Required steps
  protected abstract initialize(): void;
  protected abstract process(): void;

  // ๐Ÿช Optional steps with hooks
  protected shouldValidate(): boolean { return true; }
  protected validate(): void {
    console.log("โœ… Validating...");
  }

  protected needsCleanup(): boolean { return false; }
  protected cleanup(): void {
    console.log("๐Ÿงน Cleaning up...");
  }

  protected shouldNotify(): boolean { return false; }
  protected notify(): void {
    console.log("๐Ÿ“ง Sending notification...");
  }
}

๐Ÿ› ๏ธ Best Practices

1. ๐ŸŽฏ Keep the template method final

abstract class SecureTemplate {
  // ๐Ÿ”’ Use 'final' behavior - don't override in subclasses
  cook(): void {  // Don't make this abstract or virtual
    this.step1();
    this.step2();
    this.step3();
  }

  protected abstract step1(): void;
  protected abstract step2(): void;
  protected abstract step3(): void;
}

2. ๐Ÿช Use hooks for optional behavior

abstract class FlexibleTemplate {
  process(): void {
    this.required();
    
    if (this.wantsOptionalStep()) {  // ๐Ÿช Hook
      this.optional();
    }
  }

  protected abstract required(): void;
  
  // ๐ŸŽฏ Hook with default
  protected wantsOptionalStep(): boolean {
    return true;
  }
  
  protected optional(): void {
    console.log("๐Ÿ“Œ Default optional behavior");
  }
}

3. ๐Ÿ“Š Use meaningful method names

// โœ… Good: Clear, descriptive names
abstract class DataPipeline {
  process(): void {
    this.extractDataFromSource();
    this.transformToTargetFormat();
    this.loadIntoDestination();
  }

  protected abstract extractDataFromSource(): void;
  protected abstract transformToTargetFormat(): void;
  protected abstract loadIntoDestination(): void;
}

// โŒ Bad: Generic names
abstract class BadPipeline {
  process(): void {
    this.doStep1();  // โ“ What does this do?
    this.doStep2();  // โ“ Not clear!
    this.doStep3();  // โ“ Confusing!
  }
}

๐Ÿงช Hands-On Exercise

Create a social media post publisher that handles different platforms! ๐Ÿ“ฑ

// ๐Ÿ’ช Your challenge: Implement a social media publisher!

abstract class SocialMediaPublisher {
  // TODO: Create a publish() template method that:
  // 1. Authenticates with the platform
  // 2. Formats the content
  // 3. Validates post requirements
  // 4. Posts the content
  // 5. Returns the post URL

  // TODO: Add abstract methods for platform-specific logic
  // TODO: Add hooks for optional features (hashtags, mentions, etc.)
}

// TODO: Implement these concrete publishers:
// 1. TwitterPublisher - 280 char limit, hashtags
// 2. InstagramPublisher - Requires image, hashtags
// 3. LinkedInPublisher - Professional formatting, no char limit

// Test your implementation:
const post = {
  text: "Check out TypeScript Template Method pattern!",
  image: "typescript-logo.png",
  hashtags: ["TypeScript", "DesignPatterns"]
};

// Should work with all publishers!
๐Ÿ’ก Click here for the solution
// ๐Ÿ“ฑ Social Media Publisher Template
abstract class SocialMediaPublisher {
  // ๐ŸŽฏ Template method
  publish(content: PostContent): string {
    console.log(`๐Ÿ“ฑ Publishing to ${this.getPlatformName()}...`);
    
    this.authenticate();
    const formatted = this.formatContent(content);
    
    if (!this.validateContent(formatted)) {
      throw new Error("โŒ Content validation failed!");
    }
    
    const postId = this.postContent(formatted);
    
    if (this.supportsMentions() && content.mentions) {
      this.processMentions(content.mentions);
    }
    
    if (this.supportsHashtags() && content.hashtags) {
      this.processHashtags(content.hashtags);
    }
    
    const url = this.getPostUrl(postId);
    this.onPublishSuccess(url);
    
    return url;
  }

  // ๐Ÿ”ง Common implementations
  protected authenticate(): void {
    console.log("๐Ÿ” Authenticating with platform...");
  }

  protected onPublishSuccess(url: string): void {
    console.log(`โœ… Published successfully: ${url}`);
  }

  // ๐ŸŽจ Abstract methods
  protected abstract getPlatformName(): string;
  protected abstract formatContent(content: PostContent): FormattedPost;
  protected abstract validateContent(content: FormattedPost): boolean;
  protected abstract postContent(content: FormattedPost): string;
  protected abstract getPostUrl(postId: string): string;

  // ๐Ÿช Hook methods
  protected supportsMentions(): boolean { return false; }
  protected supportsHashtags(): boolean { return false; }
  
  protected processMentions(mentions: string[]): void {}
  protected processHashtags(hashtags: string[]): void {}
}

// ๐Ÿ“Š Data structures
interface PostContent {
  text: string;
  image?: string;
  mentions?: string[];
  hashtags?: string[];
}

interface FormattedPost {
  content: string;
  media?: string;
  metadata?: any;
}

// ๐Ÿฆ Twitter Publisher
class TwitterPublisher extends SocialMediaPublisher {
  private readonly MAX_LENGTH = 280;

  protected getPlatformName(): string {
    return "Twitter";
  }

  protected formatContent(content: PostContent): FormattedPost {
    let text = content.text;
    
    // ๐Ÿท๏ธ Add hashtags to text
    if (content.hashtags) {
      const hashtagText = content.hashtags.map(tag => `#${tag}`).join(" ");
      text = `${text} ${hashtagText}`;
    }
    
    // โœ‚๏ธ Truncate if needed
    if (text.length > this.MAX_LENGTH) {
      text = text.substring(0, this.MAX_LENGTH - 3) + "...";
    }
    
    return {
      content: text,
      media: content.image
    };
  }

  protected validateContent(content: FormattedPost): boolean {
    if (content.content.length > this.MAX_LENGTH) {
      console.log("โŒ Tweet too long!");
      return false;
    }
    return true;
  }

  protected postContent(content: FormattedPost): string {
    console.log("๐Ÿฆ Tweeting:", content.content);
    if (content.media) {
      console.log("๐Ÿ“ท With image:", content.media);
    }
    return "tweet_" + Date.now();
  }

  protected getPostUrl(postId: string): string {
    return `https://twitter.com/user/status/${postId}`;
  }

  protected supportsHashtags(): boolean { return true; }
  protected supportsMentions(): boolean { return true; }

  protected processMentions(mentions: string[]): void {
    console.log("๐Ÿ‘ฅ Processing mentions:", mentions.map(m => `@${m}`).join(", "));
  }
}

// ๐Ÿ“ธ Instagram Publisher
class InstagramPublisher extends SocialMediaPublisher {
  protected getPlatformName(): string {
    return "Instagram";
  }

  protected formatContent(content: PostContent): FormattedPost {
    if (!content.image) {
      throw new Error("โŒ Instagram requires an image!");
    }

    let caption = content.text;
    
    // ๐Ÿท๏ธ Add hashtags
    if (content.hashtags) {
      const tags = content.hashtags.map(tag => `#${tag}`).join(" ");
      caption = `${caption}\n\n${tags}`;
    }
    
    return {
      content: caption,
      media: content.image,
      metadata: { type: "photo" }
    };
  }

  protected validateContent(content: FormattedPost): boolean {
    if (!content.media) {
      console.log("โŒ Instagram post must have an image!");
      return false;
    }
    
    if (content.content.length > 2200) {
      console.log("โŒ Caption too long!");
      return false;
    }
    
    return true;
  }

  protected postContent(content: FormattedPost): string {
    console.log("๐Ÿ“ธ Posting to Instagram...");
    console.log("๐Ÿ–ผ๏ธ Image:", content.media);
    console.log("๐Ÿ’ฌ Caption:", content.content);
    return "ig_" + Date.now();
  }

  protected getPostUrl(postId: string): string {
    return `https://instagram.com/p/${postId}`;
  }

  protected supportsHashtags(): boolean { return true; }

  protected processHashtags(hashtags: string[]): void {
    console.log("๐Ÿท๏ธ Adding hashtags for discovery:", hashtags.join(", "));
  }
}

// ๐Ÿ’ผ LinkedIn Publisher
class LinkedInPublisher extends SocialMediaPublisher {
  protected getPlatformName(): string {
    return "LinkedIn";
  }

  protected formatContent(content: PostContent): FormattedPost {
    // ๐Ÿ’ผ Professional formatting
    let professionalContent = content.text;
    
    // ๐Ÿ“ Add professional context
    if (content.hashtags) {
      const tags = content.hashtags.map(tag => `#${tag}`).join(" ");
      professionalContent = `${professionalContent}\n\n${tags}`;
    }
    
    return {
      content: professionalContent,
      media: content.image,
      metadata: { visibility: "public" }
    };
  }

  protected validateContent(content: FormattedPost): boolean {
    // ๐Ÿ’ผ LinkedIn has generous limits
    if (content.content.length > 3000) {
      console.log("โŒ Post too long for LinkedIn!");
      return false;
    }
    return true;
  }

  protected postContent(content: FormattedPost): string {
    console.log("๐Ÿ’ผ Sharing on LinkedIn...");
    console.log("๐Ÿ“„ Content:", content.content);
    if (content.media) {
      console.log("๐Ÿ“Ž Attachment:", content.media);
    }
    return "li_" + Date.now();
  }

  protected getPostUrl(postId: string): string {
    return `https://linkedin.com/feed/update/${postId}`;
  }

  protected supportsHashtags(): boolean { return true; }
  protected supportsMentions(): boolean { return true; }

  protected processMentions(mentions: string[]): void {
    console.log("๐Ÿ”” Notifying connections:", mentions.join(", "));
  }
}

// ๐ŸŽฎ Test the implementation
const post: PostContent = {
  text: "Check out TypeScript Template Method pattern! ๐Ÿš€",
  image: "typescript-logo.png",
  hashtags: ["TypeScript", "DesignPatterns", "Programming"],
  mentions: ["typescript", "developer"]
};

console.log("=== Publishing to All Platforms ===\n");

// ๐Ÿฆ Twitter
const twitter = new TwitterPublisher();
try {
  const tweetUrl = twitter.publish(post);
  console.log(`๐Ÿ”— Tweet URL: ${tweetUrl}\n`);
} catch (error) {
  console.error(error.message);
}

// ๐Ÿ“ธ Instagram
const instagram = new InstagramPublisher();
try {
  const igUrl = instagram.publish(post);
  console.log(`๐Ÿ”— Instagram URL: ${igUrl}\n`);
} catch (error) {
  console.error(error.message);
}

// ๐Ÿ’ผ LinkedIn
const linkedin = new LinkedInPublisher();
try {
  const liUrl = linkedin.publish(post);
  console.log(`๐Ÿ”— LinkedIn URL: ${liUrl}\n`);
} catch (error) {
  console.error(error.message);
}

๐ŸŽ“ Key Takeaways

  1. ๐ŸŽฏ Template Method defines algorithm structure - The skeleton stays the same, details vary
  2. ๐Ÿ”’ Encapsulation is key - Keep steps protected, expose only the template method
  3. ๐Ÿช Hooks provide flexibility - Optional steps make patterns adaptable
  4. ๐ŸŽจ Abstract methods enforce implementation - Subclasses must provide specifics
  5. ๐Ÿ“Š Great for consistent workflows - Perfect when steps are fixed but details vary

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered the Template Method pattern! You can now create flexible algorithms with fixed structures but variable implementations. This pattern is perfect for frameworks, data processing, and any scenario where you need consistent workflows with custom behavior!

Next up, try:

  • ๐ŸŽฏ Combining Template Method with Factory pattern
  • ๐Ÿ”ง Creating your own mini-framework
  • ๐Ÿ“š Building a testing framework with template methods
  • ๐ŸŽฎ Implementing game AI with different behaviors

Keep building those amazing patterns! Youโ€™re becoming a TypeScript design pattern master! ๐Ÿš€โœจ