An abstraction captures behavior shared by multiple implementations while hiding implementation-specific details. Genuine abstractions are discovered after variation exists, not invented speculatively. section 8.4
Use abstraction to clarify real variation and improve testability:
- Require at least two implementations before treating an interface as a genuine abstraction; a single-implementation interface does not create loose coupling by itself. section 8.4
- Avoid speculative abstractions for future functionality; apply YAGNI and implement new behavior when the actual need appears. section 8.4
- Extract complex private logic into a separate class when public behavior cannot test it adequately, rather than exposing the private method. section 11.1 section 11.8
Extracting pricing logic
When Order.GenerateDescription relies on a complex private GetPrice method, extract that logic into PriceCalculator.Calculate(customer, products). The pricing rules become independently testable without making implementation details public. section 11.1
Listing 11.2. Extracting the complex private method
public class Order
{
private Customer _customer;
private List<Product> _products;
public string GenerateDescription()
{
var calc = new PriceCalculator();
return $"Customer name: {_customer.Name}, " +
$"total number of products: {_products.Count}, " +
$"total price: {calc.Calculate(_customer, _products)}";
}
}
public class PriceCalculator
{
public decimal Calculate(Customer customer, List<Product> products)
{
decimal basePrice = /* Calculate based on products */;
decimal discounts = /* Calculate based on customer */;
decimal taxes = /* Calculate based on products */;
return basePrice - discounts + taxes;
}
}
Inspect the refactoring from a complex private method to a separate PriceCalculator abstraction. section 11.1














