Question 1 · Section 2

What are Design Patterns?

GoF (Gang of Four — Gamma, Helm, Johnson, Vlissides, authors of "Design Patterns", 1994).

Language versions: English Russian Ukrainian

Junior Level

Design patterns are solutions documented in books and articles that have been repeatedly applied in real projects and proven to work.

Why: No need to design a solution from scratch. Saves 2-5 hours per task because the pattern is already tested and understood by other developers.

Simple analogy: Imagine building a house. You don’t need to reinvent how to build a roof or foundation every time — there are standard blueprints that have already proven effective. Patterns are such “standard blueprints” for code.

Why needed:

  • Don’t reinvent the wheel
  • Use solutions that already work
  • Communicate with colleagues using a common language (“we need a Singleton here”)

Example:

// Singleton pattern — only one instance of the class
public class Logger {
    private static Logger instance = new Logger();
    private Logger() {}
    public static Logger getInstance() { return instance; }

    public void log(String message) {
        System.out.println(message);
    }
}

// Usage
Logger.getInstance().log("Hello!");

When to use:

  • When facing a typical problem
  • When code becomes complex and confusing
  • When you need to make code clearer for other developers

Middle Level

Pattern Levels

| Level | Examples | Where Applied | | —————— | ———————————— | ——————— | | Language Idioms | Try-with-resources, Optional, Streams | Java syntax | | GoF Patterns | Strategy, Decorator, Observer, Factory | Class interaction | GoF (Gang of Four — Gamma, Helm, Johnson, Vlissides, authors of “Design Patterns”, 1994). | Enterprise | Repository, Unit of Work, Data Mapper | Data handling | | Cloud/Distributed | Circuit Breaker, Saga, CQRS | Microservices |

Main Categories

1. Creational

  • Help create objects
  • Examples: Singleton, Factory, Builder
  • Why: Hide the object creation process

2. Structural

  • Help compose classes and objects
  • Examples: Adapter, Decorator, Proxy
  • Why: Make different interfaces compatible

3. Behavioral

  • Describe interaction between objects
  • Examples: Strategy, Observer, Chain of Responsibility
  • Why: Manage algorithms and data flow

Patterns in Modern Java

Many classic patterns became simpler with Java 8-21:

// Classic Strategy (many classes)
public interface PaymentStrategy { void pay(); }
public class CreditCardStrategy implements PaymentStrategy { ... }
public class PayPalStrategy implements PaymentStrategy { ... }

// Modern Java (lambdas)
public void processPayment(UnaryOperator<BigDecimal> paymentStrategy) {
    BigDecimal result = paymentStrategy.apply(amount);
}

// Usage
processPayment(amount -> amount.subtract(discount));

Typical Mistakes

  1. Golden Hammer
    // Using a pattern everywhere we know it
    // Creating Abstract Factory for a single object
    
    // Use only when truly needed
    // Simple if-else is better than unnecessary abstraction
    
  2. Overengineering
    • Too many levels of abstraction
    • Hard to read and debug
    • Stack trace with 50 levels
  3. Cargo Cult
    • Blind copying without understanding
    • Implementing Cloneable instead of a copy constructor

When NOT to use patterns

  1. Simple CRUD applications — direct code is more readable than abstractions
  2. Weekend prototypes — speed matters more than architecture
  3. When the pattern doesn’t solve your problem — don’t force the task to fit the pattern

Senior Level

Pattern as Architectural Contract

At a deeper level, a pattern defines:

  1. Separation of Concerns
    • What objects are responsible for
    • Clear boundaries between components
  2. Interaction Interfaces
    • Minimized dependencies (Loose Coupling)
    • Contracts between modules
  3. Extension Points
    • Where the system can be extended without modifying existing code
    • Open/Closed Principle in practice

Evolution of Patterns in Modern Java (8-21+)

Strategy / Command → Lambdas:

// Before: 10+ classes
// After: Function<T, R>, Consumer<T>, Predicate<T>

Template Method → Composition:

// Instead of inheritance — passing lambdas to constructor
public class Processor {
    private final Function<Data, Result> transformer;

    public Processor(Function<Data, Result> transformer) {
        this.transformer = transformer;
    }
}

Singleton / Prototype → DI Container:

// Spring manages the lifecycle
@Component  // Singleton scope by default
public class UserService { }

@Scope("prototype")
public class OrderProcessor { }

Proxy → Spring AOP:

// @Transactional, @Cacheable — all are Proxy under the hood
// JDK Dynamic Proxy or CGLIB under the hood
@Transactional
public void processOrder() { }

Architectural Trade-offs

What we gain:

  • Flexibility and extensibility
  • Testability
  • Clarity for those familiar with the pattern
  • Loose coupling

What we lose:

  • Increased number of classes
  • Levels of indirection
  • Debugging complexity
  • Performance overhead

Megamorphic Calls and JIT

// Patterns affect JVM performance!

// Monomorphic call (1 implementation) → inlining
PaymentStrategy strategy = new CreditCard();
strategy.pay();  // JIT inlines the code → 0 overhead

// Bimorphic call (2 implementations) → still fast
// Megamorphic call (>2 implementations) → indirect call via vtable
// → Measurable CPU drop in hot path!

Optimization:

// For critical paths: enum instead of interface
public enum PaymentType {
    CREDIT_CARD {
        @Override public void pay() { /* code */ }
    },
    PAYPAL {
        @Override public void pay() { /* code */ }
    };
    public abstract void pay();
}
// → JIT better optimizes enum switch

Patterns in Distributed Systems

Cloud-Native patterns:

Circuit Breaker → fault tolerance
Saga → distributed transactions
Sidecar → infrastructure alongside the service
CQRS → separation of reads and writes
Event Sourcing → event journal instead of state

Pattern Interconnections:

Abstract Factory (Creational)
  → returns Proxy (Structural)
    → which wraps business logic
      → managed via Strategy (Behavioral)

Chain of Responsibility (Behavioral)
  → assembled via Builder (Creational)

Facade (Structural)
  → simplifies interface to a subsystem
    → managed via Strategy (Behavioral)

Sufficiency Principle

The best pattern is the one that solves the task with minimal code:

// Overengineering
public interface MessageValidator { boolean validate(Message m); }
public class EmailValidator implements MessageValidator { ... }
public class PhoneValidator implements MessageValidator { ... }
public class ValidatorFactory { ... }
public class ValidationStrategy { ... }

// Sufficient
public boolean isValid(Message m) {
    return m.getType() == EMAIL
        ? emailRegex.matcher(m.getContent()).matches()
        : phoneRegex.matcher(m.getContent()).matches();
}

// Pattern needed ONLY if:
// - Extension is planned (new validation types)
// - Complex logic in each branch
// - Needs to be tested separately

Production Experience

Real scenario #1: Overengineering killed readability

  • Project: 15 patterns in 10,000 lines of code
  • Problem: new developer spends 2 weeks on onboarding
  • Solution: simplified to 5 main patterns
  • Result: 3-day onboarding, fewer bugs

Real scenario #2: Pattern misuse in Spring

  • Manual Singleton instead of @Component
  • Problem: impossible to mock in tests
  • Solution: delegated to Spring IoC
  • Result: testability + flexibility

Best Practices

  1. Know patterns, but don’t use them unnecessarily
  2. Modern Java simplifies many patterns (lambdas, Records)
  3. DI container replaces manual Singleton/Prototype/Factory
  4. Composition > Inheritance — more flexible and less fragile
  5. Simplicity — if if-else is enough, don’t overcomplicate
  6. Document — why you chose this pattern
  7. Verify — does the pattern violate SOLID
  8. Think about JIT — megamorphic calls affect CPU

Senior Summary

  • Patterns are a tool for fighting complexity, not a goal
  • Modern Java makes many patterns “invisible” (lambdas, Records)
  • DI Container — your Singleton/Factory/Prototype manager
  • Trade-offs: flexibility vs readability, extensibility vs complexity
  • JIT optimizations: monomorphic > megamorphic calls
  • Sufficiency principle: the best pattern = minimal code
  • Distributed systems — new patterns (Saga, Circuit Breaker)
  • Patterns should respond to a real need, not a theoretical one

Interview Cheat Sheet

Must know:

  • Patterns are proven solutions to typical problems, saving 2-5 hours per task
  • Three categories: Creational (creation), Structural (composition), Behavioral (interaction)
  • GoF (Gang of Four) — Gamma, Helm, Johnson, Vlissides, authors of the 1994 book
  • Modern Java (8-21) simplifies many patterns: lambdas replace Strategy, DI replaces Singleton
  • Sufficiency principle: the best pattern = minimal code, if if-else is enough — don’t overcomplicate
  • Overengineering kills readability: 15 patterns in 10K lines — that’s an anti-pattern
  • Composition is preferred over inheritance — more flexible and less fragile
  • Megamorphic calls (>2 implementations) affect JIT optimizations and CPU in hot paths

Common follow-up questions:

  • When NOT to use patterns? — Simple CRUD, prototypes, pattern doesn’t solve the problem
  • How does an idiom differ from a pattern? — Idiom is language-specific (try-with-resources), pattern is universal
  • What cloud-native patterns do you know? — Circuit Breaker, Saga, CQRS, Event Sourcing, Sidecar
  • What is a Golden Hammer? — Using a favorite pattern everywhere possible

Red flags (DO NOT say):

  • “I use patterns everywhere I know them” — that’s overengineering
  • “Singleton solves all problems” — Singleton is considered an anti-pattern in many cases
  • “Patterns always improve performance” — often they add overhead
  • “I don’t use patterns, I write simpler” — ignorance of basic architectural solutions

Related topics:

  • [[02. What pattern categories exist]] — detailed classification
  • [[16. What anti-patterns do you know]] — bad solutions that harm code
  • [[07. Difference between Factory Method and Abstract Factory]] — creational patterns
  • [[10. When to use Strategy]] — behavioral patterns in practice
  • [[12. Advantage of Decorator over inheritance]] — composition vs inheritance