Zum Hauptinhalt springen
Intelligence View
⚡ tsecurity.de Intelligence

Design Patterns #11: Controlled Extensibility in C#

Hello. Today, I want to discuss a topic you're probably familiar with but might not consider very important: the Template Method pattern. We'll explore how it functions, when to apply it, and what problems it can solve.

Beginners often make this mistake by ignoring this pattern, which can impact readability, extendability, and performance. Let's examine a common scenario where it can be applied.

Scenario

Imagine you run an e-shop with various payment methods — the simplest way is to add a payment service for each method.

public class PayPalService
{
    public async Task ProcessPaymentAsync(decimal amount)
    {
        await ValidatePaymentAsync();
        await AuthorizePaymentAsync(amount);
        await ExecutePaymentAsync(amount);
        await SendReceiptAsync();
        await Task.CompletedTask;
    }

    private Task ValidatePaymentAsync()
    {
        Console.WriteLine("✅ Checking PayPal-account...");
        return Task.CompletedTask;
    }

    private Task AuthorizePaymentAsync(decimal amount)
    {
        Console.WriteLine($"🔐 Authorizing PayPal to {amount:C}...");
        return Task.CompletedTask;
    }

    private Task ExecutePaymentAsync(decimal amount)
    {
        Console.WriteLine($"💰 Executing PayPal payment to {amount:C}...");
        return Task.CompletedTask;
    }

    private Task SendReceiptAsync()
    {
        Console.WriteLine("📧 Sending receipt via email...");
        return Task.CompletedTask;
    }
}

public class StripeService
{
    public async Task ProcessPaymentAsync(decimal amount)
    {
        await ValidatePaymentAsync();
        await AuthorizePaymentAsync(amount);
        await ExecutePaymentAsync(amount);
        await SendReceiptAsync();
        await Task.CompletedTask;
    }

    private Task ValidatePaymentAsync()
    {
        Console.WriteLine("✅ Checking Stripe-account...");
        return Task.CompletedTask;
    }

    private Task AuthorizePaymentAsync(decimal amount)
    {
        Console.WriteLine($"🔐 Authorizing Stripe to {amount:C}...");
        return Task.CompletedTask;
    }

    private Task ExecutePaymentAsync(decimal amount)
    {
        Console.WriteLine($"💰 Executing Stripe payment to {amount:C}...");
        return Task.CompletedTask;
    }

    private Task SendReceiptAsync()
    {
        Console.WriteLine("📧 Sending receipt via email...");
        return Task.CompletedTask;
    }
}

Payment methods generally share similar functions and perform identical tasks. However, they are often separated to accommodate future extensions. This can result in redundant code, even though the code functions correctly. At first glance, this approach appears advantageous because it allows service expansion from a single location.

Template method

The Template Method pattern solves the problem of redundant code by following the DRY principle.

scheme

The Template method is an abstract class consisting of three parts:

  • a public or template method, which is a non-abstract method that encapsulates all protected methods in the class;
  • abstract methods, which are meant to be overridden by derived classes;
  • virtual methods or hooks, which may have a default implementation or not.
public abstract class PaymentProcessor
{
    public async Task ProcessPaymentAsync(decimal amount)
    {
        await ValidatePaymentAsync();
        await AuthorizePaymentAsync(amount);
        await ExecutePaymentAsync(amount);
        await SendReceiptAsync();
    }

    protected abstract Task ValidatePaymentAsync();
    protected abstract Task AuthorizePaymentAsync(decimal amount);
    protected abstract Task ExecutePaymentAsync(decimal amount);

    protected virtual Task SendReceiptAsync()
    {
        Console.WriteLine("📧 Sending receipt via email...");
        return Task.CompletedTask;
    }
}

Payments typically include common features like public methods and sending receipts. We can extract these methods into an abstract class to prevent code duplication.

public class PayPalPaymentProcessor : PaymentProcessor
{
    protected override Task ValidatePaymentAsync()
    {
        Console.WriteLine("✅ Checking PayPal-account...");
        return Task.CompletedTask;
    }

    protected override Task AuthorizePaymentAsync(decimal amount)
    {
        Console.WriteLine($"🔐 Authorizing PayPal to {amount:C}...");
        return Task.CompletedTask;
    }

    protected override Task ExecutePaymentAsync(decimal amount)
    {
        Console.WriteLine($"💰 Executing PayPal payment to {amount:C}...");
        return Task.CompletedTask;
    }
}

public class StripePaymentProcessor : PaymentProcessor
{
    protected override Task ValidatePaymentAsync()
    {
        Console.WriteLine("✅ Checking Stripe-account...");
        return Task.CompletedTask;
    }

    protected override Task AuthorizePaymentAsync(decimal amount)
    {
        Console.WriteLine($"🔐 Authorizing Stripe to {amount:C}...");
        return Task.CompletedTask;
    }

    protected override Task ExecutePaymentAsync(decimal amount)
    {
        Console.WriteLine($"💰 Executing Stripe payment to {amount:C}...");
        return Task.CompletedTask;
    }
}

Derived classes override abstract methods solely for their specific purpose. This approach addresses the problem of duplicated code.

However, consider a situation where you need to extend a service. In the previous approach, this can be done easily. Here's an update to the base class:

public abstract class PaymentProcessor
{
    public async Task ProcessPaymentAsync(decimal amount)
    {
        await ValidatePaymentAsync();
        await AuthorizePaymentAsync(amount);
        await ExecutePaymentAsync(amount);


        await CashbackPaymentAsync(amount);

        await SendReceiptAsync();
    }

    ...

    protected virtual Task CashbackPaymentAsync(decimal amount)
    {
        return Task.CompletedTask;
    }

    ...
}

You should add an additional hook. Because it is only required for one service, we included an empty virtual method for you to override as needed, ensuring other services remain unaffected.

public class StripePaymentProcessor : PaymentProcessor
{
    ...
    protected override Task CashbackPaymentAsync(decimal amount)
    {
        var cashback = Math.Round(amount * 0.05m, 2);
        Console.WriteLine($"🎁 Cashback is credited {cashback:C} to your Stripe-account!");
        return Task.CompletedTask;
    }
    ...
}

Behavior you can adopt in the targeted service.

Performance

What about performance? The results will impress you. Despite having one additional class and an extended service, we still deliver the best performance.

| Method                | Mean     | Error      | StdDev   |
|---------------------- |---------:|-----------:|---------:|
| TemplatePaymentMethod | 263.6 us | 1,562.5 us | 85.65 us |
| NaivePaymentMethod    | 375.4 us | 1,119.0 us | 61.34 us |

Conclusion

Don't underestimate the significance of this pattern. The Template Method pattern addresses key challenges:

  • Readability: Each service includes only its specific code, improving clarity.
  • Extendability: It offers flexibility to add or modify multiple services.
  • Avoiding Repetition: Supports the DRY principle by preventing duplicate code.
  • Performance: Ensures optimal performance.

If you found this article helpful, please consider supporting me.
Buy Me A Beer

This article's sample, along with others, focuses on design patterns available in my repository.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Design Patterns #11: Controlled Extensibility in C#

Thematisch verwandte Begriffe: Design, Patterns, Controlled, Extensibility · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61591 | djust provides Phoenix LiveView-style reactive server-side rendering for…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
News ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

↗ Original-Quelle