A comprehensive example demonstrating how multiple design patterns work together to create a flexible and maintainable system.
This Duck Simulator showcases the integration of six different design patterns working in harmony. The application simulates various types of ducks (and geese!) that can quack, be organized into flocks, have their quacks counted, and be observed by interested parties.
- Location:
Quackableinterface and its implementations - Purpose: Defines a family of quacking algorithms and makes them interchangeable
- Implementation: Each duck type implements the
Quackableinterface with its own quacking behavior
- Location:
GooseAdapterclass - Purpose: Converts the interface of
Gooseto work with theQuackableinterface - Implementation: Wraps a
Gooseobject and translatesquack()calls tohonk()calls - Benefit: Allows geese to participate in the duck simulator without modifying their original implementation
- Location:
QuackCounterclass - Purpose: Adds functionality (counting) to Quackable objects without modifying their code
- Implementation: Wraps any
Quackableand counts each quack while delegating to the wrapped object - Benefit: Separates counting logic from duck behavior; can be applied selectively
- Location:
AbstractDuckFactory,DuckFactory,AbstractCountingDucks - Purpose: Creates families of related objects (decorated vs non-decorated ducks) without specifying concrete classes
- Implementation:
DuckFactory: Creates plain ducksAbstractCountingDucks: Creates ducks pre-wrapped withQuackCounterdecorator
- Benefit: Centralizes object creation and makes it easy to switch between decorated and non-decorated ducks
- Location:
Flockclass - Purpose: Treats individual ducks and groups of ducks uniformly
- Implementation:
FlockimplementsQuackableand contains a collection ofQuackableobjects - Benefit: Allows hierarchical organization of ducks and operations on entire groups
- Location:
Observer,Observable,QuackObservable,Quackologist - Purpose: Notifies interested parties when ducks quack
- Implementation:
QuackObservable: Interface for observable subjectsObservable: Helper class managing observer registration and notificationObserver: Interface for observersQuackologist: Concrete observer that tracks which ducks quack
- Benefit: Loose coupling between ducks and observers; multiple observers can track duck behavior
Quackable (interface)
├── extends QuackObservable
│
Duck Implementations:
├── MallardDuck
├── RedheadDuck
├── RubberDuck
├── DuckCall
│
Patterns:
├── GooseAdapter (Adapter Pattern)
├── QuackCounter (Decorator Pattern)
├── Flock (Composite Pattern)
│
Factory:
├── AbstractDuckFactory (Abstract Factory)
├── DuckFactory
├── AbstractCountingDucks
│
Observer:
├── Observer (interface)
├── QuackObservable (interface)
├── Observable (helper class)
├── Quackologist (concrete observer)
From the root directory of the project:
javac -d out MultiPatterns/DuckSimulatorExample/*.javajava -cp out MultiPatterns.DuckSimulatorExample.DuckSimulatorDuck Simulator: With Composite -Flocks
Duck Simulator: With Observer
Duck Simulator: Whole Flock Simulation
Quack
Quackologist: Redhead Duck just quacked.
Kwak
Quackologist: Duck Call just quacked.
Squeak
Quackologist: Rubber Duck just quacked.
Honk
Quackologist: Goose pretending to be a Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Duck Simulator: Mallard Flock Simulation
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
Quack
Quackologist: Mallard Duck just quacked.
The ducks quacked 11 times
- Flexibility: Easy to add new duck types without modifying existing code
- Maintainability: Each pattern has a single responsibility, making the code easier to understand and maintain
- Extensibility: New behaviors (decorators), observers, or factories can be added without changing core classes
- Reusability: Patterns can be applied independently or in combination
- Testability: Individual components can be tested in isolation
- Complexity: Multiple patterns increase the number of classes and relationships
- Learning Curve: Requires understanding of multiple design patterns
- Overhead: May be over-engineered for simple use cases
- Performance: Multiple layers of abstraction can have minor performance impacts
Pros:
- Open/Closed Principle: New quacking behaviors don't require changing existing code
- Eliminates conditional logic for different behaviors
Cons:
- Clients must be aware of different strategies
- Increases number of objects in the system
Pros:
- Integrates incompatible interfaces without modifying original classes
- Promotes code reuse
Cons:
- Adds an extra layer of indirection
- Can make code harder to understand if overused
Pros:
- More flexible than inheritance for adding functionality
- Responsibilities can be added/removed at runtime
- Follows Single Responsibility Principle
Cons:
- Can result in many small objects
- Order of decorators may matter
- Can be complex to debug wrapped objects
Pros:
- Ensures consistency in object families
- Isolates concrete classes from client code
- Easy to switch between product families
Cons:
- Adding new product types requires changing all factory classes
- Increases overall complexity
Pros:
- Uniform treatment of individual objects and compositions
- Simplifies client code
- Easy to add new component types
Cons:
- Can make design overly general
- Type safety can be a concern (all elements must implement same interface)
Pros:
- Loose coupling between subjects and observers
- Dynamic relationships (observers can be added/removed at runtime)
- Broadcast communication to multiple observers
Cons:
- Observers are notified in random order
- Memory leaks possible if observers aren't properly unregistered
- Can be hard to debug cascading updates
- Pattern Synergy: Multiple patterns can work together seamlessly when properly designed
- Composition Over Inheritance: The decorator and composite patterns show the power of object composition
- Loose Coupling: The observer pattern demonstrates how to achieve loose coupling between components
- Flexible Object Creation: The abstract factory pattern centralizes and standardizes object creation
- Interface Segregation: Small, focused interfaces (
Quackable,Observer) are more flexible than large ones
Good fit for:
- Systems that need high flexibility and extensibility
- Applications with multiple variations of similar objects
- Projects where requirements frequently change
- Systems requiring runtime behavior modification
Not ideal for:
- Simple applications with stable requirements
- Performance-critical systems where every millisecond counts
- Projects with tight deadlines and limited resources
- Teams unfamiliar with design patterns
Try extending the simulator by:
- Adding new duck types (e.g.,
DecoyDuck,WildDuck) - Creating new decorators (e.g.,
EchoDecoratorthat makes ducks quack twice) - Implementing additional observers (e.g.,
QuackLogger,QuackStatistics) - Adding a new factory that creates ducks with different decorator combinations