Skip to main content

Protected Variations

Protect classes from variations in other classes using abstraction and stable interfaces

TL;DR

Protected Variations is the ultimate GRASP principle that ties all others together. It teaches you to protect classes from variations and change by using abstraction and stable interfaces. Rather than having classes vulnerable to changes in other classes, you design systems where variations are isolated behind stable abstractions.

Learning Objectives

  • Understand what variations are and why protecting from them matters
  • Learn to identify points of variation in your design
  • Apply abstraction to isolate variations
  • Design stable interfaces that insulate classes from change
  • Balance flexibility with simplicity in your designs

Motivating Scenario

Your system uses payment processing that may change: sometimes credit card, sometimes digital wallet, sometimes bank transfer. Without protection, your OrderProcessor knows about all payment variations and changes whenever a new payment method arrives. Protected Variations says: define a stable Payment interface that PaymentProcessor depends on. New payment methods extend this interface without affecting OrderProcessor.

Core Concepts

Protected Variations is the foundational principle underlying all other GRASP patterns. It states: identify points of likely variation and protect against them using abstraction and stable interfaces. Areas of variation include:

  1. External Systems: Database, API, file systems—hide behind stable interfaces
  2. Business Rules: Discounts, fees, calculations—isolate in modular objects
  3. Algorithms: Sorting, searching, processing—encapsulate in strategy patterns
  4. Object Types: Different implementations—use polymorphism
  5. Requirements: Features that change often—abstract into pluggable components

By designing systems that are protected from variations, you achieve systems that are more resilient, maintainable, and extensible. This isn't premature defense against every imagined change; it's thoughtful design that protects against known or likely variations.

Protected Variations connects to all other patterns:

  • Information Expert keeps variation knowledge localized
  • Creator keeps object creation strategies flexible
  • Controller isolates UI/domain variations
  • Low Coupling isolates components from variation in others
  • Polymorphism handles type variations
  • Pure Fabrication isolates infrastructure variations
  • Indirection creates barriers against variation
Protected Variations: Isolation Through Abstraction

Practical Example

Let's see how Protected Variations isolates variations:

protected_variations_example.py
from abc import ABC, abstractmethod

# WITHOUT PROTECTION (vulnerable to variations)
class BadOrderProcessor:
def process_order(self, order, payment_data):
if payment_data["type"] == "credit_card":
# Credit card logic - changes when CC varies
order.total = order.total * 0.95
elif payment_data["type"] == "digital_wallet":
# Wallet logic - changes when wallet varies
order.total = order.total * 0.92
elif payment_data["type"] == "bank_transfer":
# Transfer logic - changes when transfer varies
order.total = order.total * 0.98
# Adding new payment type requires modifying this method

# WITH PROTECTION (using abstraction)
class PaymentStrategy(ABC):
"""Stable interface protecting from payment variations"""
@abstractmethod
def apply_discount(self, amount: float) -> float:
pass

class CreditCardPayment(PaymentStrategy):
def apply_discount(self, amount: float) -> float:
return amount * 0.95

class DigitalWalletPayment(PaymentStrategy):
def apply_discount(self, amount: float) -> float:
return amount * 0.92

class BankTransferPayment(PaymentStrategy):
def apply_discount(self, amount: float) -> float:
return amount * 0.98

# New payment types can be added without changing OrderProcessor
class CryptoCurrencyPayment(PaymentStrategy):
def apply_discount(self, amount: float) -> float:
return amount * 0.90

class Order:
def __init__(self, customer: str, total: float):
self.customer = customer
self.total = total

class GoodOrderProcessor:
"""Protected from payment variations"""
def process_order(self, order: Order,
payment: PaymentStrategy) -> dict:
# No knowledge of specific payment types
order.total = payment.apply_discount(order.total)
return {
"success": True,
"customer": order.customer,
"total": order.total
}

# Usage
processor = GoodOrderProcessor()
order = Order("John", 100.0)

# Credit card payment
cc_payment = CreditCardPayment()
result = processor.process_order(order, cc_payment)
print(f"CC: ${result['total']:.2f}")

# Digital wallet payment
order.total = 100.0
wallet_payment = DigitalWalletPayment()
result = processor.process_order(order, wallet_payment)
print(f"Wallet: ${result['total']:.2f}")

# New cryptocurrency payment - NO CHANGES TO OrderProcessor!
order.total = 100.0
crypto_payment = CryptoCurrencyPayment()
result = processor.process_order(order, crypto_payment)
print(f"Crypto: ${result['total']:.2f}")

When to Use / When Not to Use

Use
  1. For known or likely variations in requirements
  2. When integrating with external systems that change
  3. For business rules that evolve over time
  4. When multiple implementations of a concept exist
  5. For architectural boundaries between subsystems
Avoid
  1. Protecting against imagined variations that never occur
  2. Over-abstracting simple, stable requirements
  3. Creating unnecessary layers for single-variant scenarios
  4. Sacrificing simplicity for hypothetical flexibility
  5. Designing based on speculation instead of real needs

Patterns and Pitfalls

Protected Variations Implementation

Design for known variations: Protect against variations you know exist or are likely to occur. Credit card vs. digital wallet is a real variation worth protecting against.

Use stable interfaces: Create interfaces or abstract classes that are unlikely to change, isolating variations behind them.

Locate variation: Identify the specific point of variation and create abstraction right there, not everywhere speculatively.

Over-engineering: Don't create abstraction layers for every possible future variation. Design for known variations, refactor when new ones appear.

Speculative design: Don't assume variations that never occur. This creates unnecessary complexity without providing value.

Leaky abstractions: Don't create interfaces that expose implementation details. Stable interfaces hide the variations they're meant to protect against.

Design Review Checklist

  • Have you identified the points of likely variation?
  • Are these variations protected behind stable abstractions?
  • Can variations be added or changed without modifying other classes?
  • Is the abstraction stable (unlikely to change)?
  • Does the protection provide real value or is it over-engineering?
  • Are variations truly isolated or do they leak through the interface?

Self-Check

  1. What is Protected Variations and why does it matter? It's the principle of protecting classes from variations in other classes using stable abstractions. This makes systems more resilient to change and extension.

  2. How does Protected Variations relate to other GRASP patterns? It's the underlying principle that justifies all other patterns. Low Coupling protects from variation in dependencies, Polymorphism protects from type variation, Pure Fabrication protects from infrastructure variation, etc.

  3. When should you protect against variations? When you have known or likely variations in requirements, external systems, algorithms, or implementations. Avoid protecting against imagined variations that may never occur.

info

One Takeaway: Identify points of likely variation and protect against them using stable abstractions. This makes your systems more resilient, maintainable, and extensible.

Next Steps

  • Review Low Coupling as a mechanism for protecting against variation
  • Study Polymorphism for protecting against type variation
  • Learn Pure Fabrication for protecting against infrastructure variation
  • Explore Indirection for protecting against direct dependencies
  • Review all GRASP patterns as applications of Protected Variations principle

References

  1. GRASP (Object-Oriented Design) - Wikipedia ↗️
  2. Applying UML and Patterns by Craig Larman ↗️