Skip to content

Abstraction Strategies

When to Use

When deciding how to eliminate duplication through abstraction — choosing the right abstraction mechanism for the situation.

Decision Framework

If duplication is... Use Why
Procedural logic (algorithms, calculations) Functions Simplest, most composable
Related data + behavior Classes/Objects Encapsulation, state management
Cross-cutting concerns (logging, auth) Decorators, Middleware, Aspects Separate concerns from business logic
Shared behavior across unrelated types Mixins, Traits, Interfaces Composition without inheritance coupling
Platform/language differences Adapter pattern, abstraction layer Isolate platform-specific code
Complex initialization Factory pattern, Builder pattern Centralize object creation logic
Variation in algorithm Strategy pattern, polymorphism Runtime behavior selection

Abstraction Techniques

1. Extract Function (Simplest)

// BEFORE: Duplicated logic
function processUserOrder(user, order) {
  const tax = order.amount * 0.08;
  const total = order.amount + tax;
  // ...
}

function processGuestOrder(guest, order) {
  const tax = order.amount * 0.08;
  const total = order.amount + tax;
  // ...
}

// AFTER: Extract function
function calculateTotal(amount) {
  const tax = amount * 0.08;
  return amount + tax;
}

function processUserOrder(user, order) {
  const total = calculateTotal(order.amount);
  // ...
}

function processGuestOrder(guest, order) {
  const total = calculateTotal(order.amount);
  // ...
}

2. Extract Class/Module

# BEFORE: Validation logic scattered
class UserController:
    def create(self, data):
        if not data.get('email') or '@' not in data['email']:
            raise ValueError('Invalid email')
        if len(data.get('password', '')) < 8:
            raise ValueError('Password too short')
        # ...

class RegistrationController:
    def register(self, data):
        if not data.get('email') or '@' not in data['email']:
            raise ValueError('Invalid email')
        if len(data.get('password', '')) < 8:
            raise ValueError('Password too short')
        # ...

# AFTER: Extract validator class
class UserValidator:
    @staticmethod
    def validate_email(email):
        if not email or '@' not in email:
            raise ValueError('Invalid email')

    @staticmethod
    def validate_password(password):
        if len(password or '') < 8:
            raise ValueError('Password too short')

    @classmethod
    def validate_user_data(cls, data):
        cls.validate_email(data.get('email'))
        cls.validate_password(data.get('password'))

3. Composition Over Inheritance

// AVOID: Deep inheritance hierarchies
class Animal { eat() {} }
class Mammal extends Animal { breathe() {} }
class Dog extends Mammal { bark() {} }
class Puppy extends Dog { playful() {} }
// Rigid, hard to change, couples unrelated behaviors

// PREFER: Composition with traits/mixins
interface Eater { eat(): void; }
interface Breather { breathe(): void; }
interface Barker { bark(): void; }

class Dog implements Eater, Breather, Barker {
  eat() { /* ... */ }
  breathe() { /* ... */ }
  bark() { /* ... */ }
}
// Flexible, can mix behaviors independently

4. Configuration Over Code

// BEFORE: Hardcoded variations
class EmailNotifier {
    public function send($message) {
        $smtp = new SmtpClient('smtp.gmail.com', 587);
        $smtp->send($message);
    }
}

class SmsNotifier {
    public function send($message) {
        $api = new TwilioApi('api.twilio.com', '8080');
        $api->send($message);
    }
}

// AFTER: Configuration-driven
class Notifier {
    private $client;

    public function __construct($config) {
        $this->client = ClientFactory::create($config);
    }

    public function send($message) {
        $this->client->send($message);
    }
}

// Usage driven by config, not code changes
$emailNotifier = new Notifier(['type' => 'smtp', 'host' => 'smtp.gmail.com']);
$smsNotifier = new Notifier(['type' => 'twilio', 'host' => 'api.twilio.com']);

Common Mistakes

  • Premature abstraction — Creating abstractions before understanding the variance; leads to wrong abstractions
  • Over-engineering — Using complex patterns (Abstract Factory, Command) when simple function would suffice
  • Deep inheritance hierarchies — Fragile base class problem, hard to change, couples implementations
  • God objects — Single class/module handling too many responsibilities because it was "DRY"
  • Leaky abstractions — Abstraction exposes implementation details, defeating its purpose
  • Cargo cult patterns — Applying design patterns because "best practice" without understanding trade-offs

See Also