DEV Community

Muhammad Salem
Muhammad Salem

Posted on

Encapsulate What Varies (EWV) Principle: A Pragmatic Approach

Applying the Encapsulate What Varies (EWV) principle helps create maintainable, flexible, and adaptable software systems. The essence of this principle is to identify aspects of your application that are likely to change and encapsulate them in such a way that these changes do not affect the core, stable parts of your system. Let's explore this with real-world examples and practical steps.

Understanding the Problem Domain

Domain Understanding: The first step is to deeply understand the problem domain and the core functionalities of your application. This involves:

  • Analyzing requirements
  • Identifying key use cases
  • Understanding the data involved

Example: Suppose you are developing an e-commerce platform. Core functionalities might include product catalog management, order processing, and user authentication.

Identifying Stable Core and Variable Aspects

Identify Stable Core: Pinpoint the functionalities that remain consistent throughout the application's lifecycle.

Example: In our e-commerce platform, the core order processing logic, such as adding items to the cart, calculating totals, and managing user sessions, remains stable.

Look for Variability: Analyze areas where functionalities might change due to evolving requirements.

Example: Payment methods (credit card, PayPal, cryptocurrencies), shipping options (standard, express, international), and promotional discount rules are likely to change over time.

Techniques for Identifying Variable Aspects

  1. Scenario Analysis: Consider different use cases and identify if some aspects of the application behave differently.
  2. Future-Proofing: Think about potential future requirements and how the application might need to adapt.
  3. Non-Functional Considerations: Reflect on performance, scalability, or security factors.
  4. External Dependencies: Recognize dependencies on external systems or data sources.

Real-World Example: Payment Processing

Stable Core: Order Processing

public class OrderService
{
    public void ProcessOrder(Order order)
    {
        // Stable core logic for processing an order
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Variable Aspect: Payment Method

public interface IPaymentProcessor
{
    void ProcessPayment(Order order);
}

public class CreditCardPaymentProcessor : IPaymentProcessor
{
    public void ProcessPayment(Order order)
    {
        // Logic for processing credit card payment
        // ...
    }
}

public class PayPalPaymentProcessor : IPaymentProcessor
{
    public void ProcessPayment(Order order)
    {
        // Logic for processing PayPal payment
        // ...
    }
}

public class PaymentService
{
    private readonly IPaymentProcessor _paymentProcessor;

    public PaymentService(IPaymentProcessor paymentProcessor)
    {
        _paymentProcessor = paymentProcessor;
    }

    public void ProcessPayment(Order order)
    {
        _paymentProcessor.ProcessPayment(order);
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Encapsulating What Varies

  1. Increased Maintainability: Easier to modify or extend variable aspects without affecting core logic.
  2. Improved Flexibility: The application can adapt to changing requirements more readily.
  3. Enhanced Reusability: Stable core components can be reused across different projects or integrations.
  4. Better Testability: Individual variable components can be tested in isolation.

Tips for Pragmatic Implementation

  1. Start with Core: Focus on building the stable core functionalities first.
  2. Identify Clear Boundaries: Clearly define the interfaces between stable and variable aspects.
  3. Use Abstraction Layers: Employ well-defined interfaces to separate variable aspects from the core.
  4. Don't Over-engineer: Focus on areas where variability is evident. Avoid over-complicating the design.
  5. Refactor as Needed: As the application evolves, refactor to identify new areas of variability and encapsulate accordingly.

Example: Shipping Options

Stable Core: Shipping Logic

public class ShippingService
{
    public void ShipOrder(Order order)
    {
        // Stable core logic for shipping an order
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Variable Aspect: Shipping Methods

public interface IShippingMethod
{
    void Ship(Order order);
}

public class StandardShipping : IShippingMethod
{
    public void Ship(Order order)
    {
        // Logic for standard shipping
        // ...
    }
}

public class ExpressShipping : IShippingMethod
{
    public void Ship(Order order)
    {
        // Logic for express shipping
        // ...
    }
}

public class ShippingService
{
    private readonly IShippingMethod _shippingMethod;

    public ShippingService(IShippingMethod shippingMethod)
    {
        _shippingMethod = shippingMethod;
    }

    public void ShipOrder(Order order)
    {
        _shippingMethod.Ship(order);
    }
}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

By adhering to the EWV principle, you can design software that is both robust and adaptable to change. This approach is particularly valuable in rapidly evolving domains where requirements can change frequently. The key is to deeply understand the problem domain, identify the stable core functionalities, and encapsulate the aspects that are likely to change. This not only makes your codebase more maintainable and flexible but also enhances testability and reusability.

Encapsulating what varies is a cornerstone of good object-oriented design and leads to high-quality code that stands the test of time.

Top comments (0)