πΉ Interfaces and Design Patterns
π― Objective
- To explore how interfaces can decouple code
 - To demonstrate design patterns that leverage interfaces: Strategy and Factory
 - To promote substitutability, a core idea behind SOLID principles (especially the Open/Closed Principle and Dependency Inversion Principle)
 
πΈ 1. Why Use Interfaces?
Interfaces define behavioral contracts without specifying implementation.
β Bad Practice (Tightly Coupled)
public class OrderProcessor {
    private readonly CreditCardPaymentProcessor _paymentProcessor = new CreditCardPaymentProcessor();
    public void Process(Order order) {
        _paymentProcessor.Process(order);
    }
}
This class depends on a concrete implementation, which makes it:
- Hard to test
 - Hard to extend (e.g., support PayPal, Stripe, etc.)
 
β Good Practice (Decoupled via Interface)
public interface IPaymentProcessor {
    void Process(Order order);
}
public class CreditCardPaymentProcessor : IPaymentProcessor {
    public void Process(Order order) {
        // Payment logic
    }
}
public class OrderProcessor {
    private readonly IPaymentProcessor _paymentProcessor;
    public OrderProcessor(IPaymentProcessor paymentProcessor) {
        _paymentProcessor = paymentProcessor;
    }
    public void Process(Order order) {
        _paymentProcessor.Process(order);
    }
}
Now:
- 
OrderProcessoris reusable and extensible - You can inject different payment processors (Strategy Pattern)
 - Unit testing becomes easy with mock 
IPaymentProcessor 
πΈ 2. Strategy Pattern
Definition: Allows you to change an algorithm's behavior at runtime by injecting different strategy implementations.
Used here to inject different behaviors for
IPaymentProcessor.
public class PayPalPaymentProcessor : IPaymentProcessor {
    public void Process(Order order) {
        // PayPal logic
    }
}
// Inject the strategy at runtime
var orderProcessor = new OrderProcessor(new PayPalPaymentProcessor());
orderProcessor.Process(order);
Key Idea: Swap behaviors without changing OrderProcessor.
πΈ 3. Factory Pattern
Definition: Encapsulates object creation logic. Useful when:
- You need to decide which implementation to instantiate
 - You want to hide complex creation logic from the consumer
 
public class PaymentProcessorFactory {
    public IPaymentProcessor GetProcessor(string paymentMethod) {
        switch (paymentMethod) {
            case "creditcard": return new CreditCardPaymentProcessor();
            case "paypal": return new PayPalPaymentProcessor();
            default: throw new NotSupportedException();
        }
    }
}
// Usage
var factory = new PaymentProcessorFactory();
var processor = factory.GetProcessor("creditcard");
var orderProcessor = new OrderProcessor(processor);
This separates object creation from business logic, keeping things clean and extendable.
πΈ 4. Dependency Inversion Principle (D from SOLID)
This principle is emphasized in Chapter 3:
- High-level modules (like 
OrderProcessor) should not depend on low-level modules (likeCreditCardPaymentProcessor). - Both should depend on abstractions (
IPaymentProcessor). 
This makes systems:
- Easier to extend (add new payment methods)
 - Easier to test (mock interfaces)
 - More maintainable
 
πΈ 5. Interface vs Abstract Class in C
| Feature | Interface | Abstract Class | 
|---|---|---|
| Multiple inheritance | β Yes | β No | 
| Constructors | β Not allowed | β Allowed | 
| Default implementation | β (C# 8+) | β | 
| Use case | Behavioral contract | Shared implementation | 
Gary recommends favoring interfaces when:
- Youβre defining pluggable behavior
 - You want to support multiple inheritance
 - You want to use Strategy, Factory, or other behavioral patterns
 
π‘ Key Takeaways
- Interfaces enable polymorphism and are fundamental to Agile architecture
 - You should program to abstractions, not implementations
 - Patterns like Strategy and Factory are powerful when combined with interfaces
 - Avoid directly instantiating dependencies (use DI + Factories)
 - Design code to be closed for modification, but open for extension
 
β Example Refactor (Factory + Strategy + Interface)
// Interface
public interface IShippingCalculator {
    decimal CalculateShipping(Order order);
}
// Concrete Strategies
public class StandardShipping : IShippingCalculator {
    public decimal CalculateShipping(Order order) => 5;
}
public class ExpressShipping : IShippingCalculator {
    public decimal CalculateShipping(Order order) => 15;
}
// Factory
public class ShippingCalculatorFactory {
    public IShippingCalculator Create(string type) {
        return type switch {
            "standard" => new StandardShipping(),
            "express" => new ExpressShipping(),
            _ => throw new NotSupportedException()
        };
    }
}
// Usage
var factory = new ShippingCalculatorFactory();
var calculator = factory.Create("express");
var shippingCost = calculator.CalculateShipping(order);
Awesome! Here's a mini C# project based on Chapter 3 of Adaptive Code via C#, showcasing:
- β
 
Interfaces - β
 
Strategy Pattern - β
 
Factory Pattern - β
 
Dependency Injection - β
 
Testable & SOLID-compliant architecture 
π¦ Project: Shipping Cost Calculator
We'll build a console app that calculates shipping costs based on the shipping type selected (Standard, Express, or Overnight).
π Folder Structure
/ShippingCalculatorApp
β
βββ Program.cs
βββ Models/
β   βββ Order.cs
βββ Interfaces/
β   βββ IShippingCalculator.cs
βββ Strategies/
β   βββ StandardShipping.cs
β   βββ ExpressShipping.cs
β   βββ OvernightShipping.cs
βββ Factories/
β   βββ ShippingCalculatorFactory.cs
βββ Services/
β   βββ OrderService.cs
  
  
  πΉ 1. Order.cs (Model)
namespace ShippingCalculatorApp.Models;
public class Order {
    public string Destination { get; set; }
    public double Weight { get; set; }
}
  
  
  πΉ 2. IShippingCalculator.cs (Interface)
namespace ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Models;
public interface IShippingCalculator {
    decimal Calculate(Order order);
}
πΉ 3. Strategy Implementations
  
  
  StandardShipping.cs
namespace ShippingCalculatorApp.Strategies;
using ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Models;
public class StandardShipping : IShippingCalculator {
    public decimal Calculate(Order order) => (decimal)(order.Weight * 1.2);
}
  
  
  ExpressShipping.cs
namespace ShippingCalculatorApp.Strategies;
using ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Models;
public class ExpressShipping : IShippingCalculator {
    public decimal Calculate(Order order) => (decimal)(order.Weight * 2.5 + 5);
}
  
  
  OvernightShipping.cs
namespace ShippingCalculatorApp.Strategies;
using ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Models;
public class OvernightShipping : IShippingCalculator {
    public decimal Calculate(Order order) => (decimal)(order.Weight * 4 + 15);
}
  
  
  πΉ 4. ShippingCalculatorFactory.cs (Factory Pattern)
namespace ShippingCalculatorApp.Factories;
using ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Strategies;
public class ShippingCalculatorFactory {
    public IShippingCalculator Create(string type) {
        return type.ToLower() switch {
            "standard" => new StandardShipping(),
            "express" => new ExpressShipping(),
            "overnight" => new OvernightShipping(),
            _ => throw new ArgumentException("Unsupported shipping type")
        };
    }
}
  
  
  πΉ 5. OrderService.cs
namespace ShippingCalculatorApp.Services;
using ShippingCalculatorApp.Interfaces;
using ShippingCalculatorApp.Models;
public class OrderService {
    private readonly IShippingCalculator _calculator;
    public OrderService(IShippingCalculator calculator) {
        _calculator = calculator;
    }
    public decimal CalculateShipping(Order order) {
        return _calculator.Calculate(order);
    }
}
  
  
  πΉ 6. Program.cs (App Entry Point)
using ShippingCalculatorApp.Models;
using ShippingCalculatorApp.Factories;
using ShippingCalculatorApp.Services;
class Program {
    static void Main() {
        var order = new Order { Destination = "USA", Weight = 10.5 };
        Console.WriteLine("Select shipping type: standard | express | overnight");
        var type = Console.ReadLine();
        var factory = new ShippingCalculatorFactory();
        var calculator = factory.Create(type!);
        var service = new OrderService(calculator);
        var cost = service.CalculateShipping(order);
        Console.WriteLine($"Shipping cost: ${cost:F2}");
    }
}
β How It All Ties Together
- 
OrderServiceis decoupled from shipping logic viaIShippingCalculator - 
ShippingCalculatorFactoryinjects behavior using the Strategy pattern - Code is testable, maintainable, and extensible
 
              
    
Top comments (0)