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
- ๐ฏ Template Method defines algorithm structure - The skeleton stays the same, details vary
- ๐ Encapsulation is key - Keep steps protected, expose only the template method
- ๐ช Hooks provide flexibility - Optional steps make patterns adaptable
- ๐จ Abstract methods enforce implementation - Subclasses must provide specifics
- ๐ 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! ๐โจ