Skip to content

abach42/design-pattern-cheat-sheet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gang of Four (GoF) design patterns

A cheat sheet of design patterns for your daily Java work.

Here are some hints and client codes to simplify decisions, links to example codes and tests to read function an meaning.

Remark: The class names are oriented towards patterns to recognize the structure. In practice, one would choose the names based on their function. As well unit tests are not fully covering, just showing function of patterns. It is a cheat sheet focussing fast election of patterns - or not - to avoid over engineering.

Creational Patterns

Singleton Pattern

✏️ The Singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. It's useful when exactly one object is needed to coordinate actions across the system.

public class Singleton {
    private static Singleton instance;

    // Private constructor to prevent instantiation from outside
    private Singleton() {}

    // Static method to provide access to the Singleton instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

And here's how you would use the Singleton class:

Singleton singleton = Singleton.getInstance();

While the Singleton pattern can be useful in certain situations, it's also often considered an "anti-pattern" due to some of its drawbacks like hidden dependencies, concurrency issues, testing challenges.

Factory Method Pattern

✏️ The Factory Method is a creational pattern that first defines a method that creates an object. Which type is instantiated is decided by the concrete class that implements this method.

💡 Link to code example

💊 Link to test

Client Code example

Creator creator = new CircleCreator();
Product product = creator.createProduct(new Point(100, 100));
Shape shape = product.getShape();
Creator creator = new SquareCreator();
Product product = creator.createProduct(new Point(100, 100));
Shape shape =  product.getShape();

Abstract Factory Pattern

✏️ The Abstract Factory pattern is another creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's useful when you need to ensure that the created objects are compatible with each other and when you want to hide the implementation details of the creation process.

💡 Link to code example

💊 Link to test

Client Code example

AbstractFactory firstFactory = new ConcreteFactory1();
String resultA = firstFactory.createProductA().toString(); //"Name:ProductA1"
String resultB = firstFactory.createProductB().toString(); //"Id:ProductB1"
AbstractFactory secondFactory = new ConcreteFactory2();
String resultA = secondFactory.createProductA().toString(); //"Name:ProductA2"
String resultB = secondFactory.createProductB().toString(); //"Id:ProductB2"

Builder Pattern

✏️ The Builder pattern is a creational pattern that composes data in steps and divides the build process from the presentation layer. It allows you to produce different types and representations of an object using the same construction process. This pattern is particularly useful when dealing with objects that have numerous parameters or configurations, as it provides a clear way to separate the construction logic from the rest of the code. See variants in test.

💡 Link to code example

💊 Link to test

Client Code example

Director director = new Director();

BareboneConcreteBuilder bareboneBuilder = new BareboneConcreteBuilder();
director.constructBasicComputer(bareboneBuilder);
BareboneProduct bareboneProduct = bareboneBuilder.build();

System.out.println(bareboneProduct.toString());

Prototype Pattern

✏️ The Prototype Pattern is a creational design pattern used to create objects based on a template of an existing object through cloning. It is particularly useful when the construction of a new instance is more efficient by copying an existing instance rather than creating it from scratch.

💡 Link to code example

💊 Link to test

Client Code example

CirclePrototype circlePrototype = new CirclePrototype();
circlePrototype.x = 1;
circlePrototype.y = 1;
circlePrototype.radius = 10;
circlePrototype.color = "blue";

CirclePrototype circle = (CirclePrototype) circlePrototype.clone();

Structural Patterns

Adapter Pattern

✏️ The Adapter pattern is a design pattern that allows adapting the interface of an existing object so that it can be used by a class that was originally incompatible with this object. The Adapter pattern is often used to integrate existing class libraries or other external APIs without needing to change their codebase.

The Adapter pattern consists of four main components:

  • A Target interface, which represents the interface that the class using the Adapter pattern wants to use.
  • An 'Adaptee' class, which is the class to be integrated but does not implement the Target interface.
  • An Adapter class, which implements the 'Adaptee' class and the Target interface. The Adapter class then translates the method calls of the Target interface into method calls to the 'Adaptee' class.

💡 Link to code example

💊 Link to test

Client Code example

ApiAdaptee<String> fakeApiAdaptee = () -> "{\"id\": 42, \"name\": \"John Doe\"}";
Target<Record> apiTarget = new ApiAdapter(fakeApiAdaptee);
Record actualRecord = apiTarget.fetchData();
Record expeRecord = new Target.Record(42, "John Doe");

In this example fakeApiAdaptee represents an external code or api without code insight.

Bridge Pattern

✏️ The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently. Abstraction defines a high-level interface that uses an implementation object. Refined Abstraction extends the abstraction interface. Implementor defines the interface for implementation classes. Concrete Implementors provide the specific implementation of the Implementor interface.

💡 Link to code example

💊 Link to test

Client Code example

Renderer2D renderer2D = new Renderer2D();
Circle circle = new Circle(renderer2D, 5.0, new Point2D.Double(10, 10));

Ellipse2D result = (Ellipse2D) circle.draw();
Renderer3D renderer3D = new Renderer3D();
Circle circle = new Circle(renderer3D, 5.0, new Point2D.Double(10, 10));

Circle3DHelper result = (Circle3DHelper) circle.draw();
Renderer3D renderer3D = new Renderer3D();
Rectangle rectangle = new Rectangle(renderer3D, 10.0, 5.0, new Point2D.Double(0, 0));

Rectangle3DHelper result = (Rectangle3DHelper) rectangle.draw();

Composite Pattern

✏️ The Composite Pattern is a structural design pattern used to treat individual objects and compositions of objects uniformly. It is commonly used when you want to represent part-whole hierarchies. The pattern lets clients treat both individual objects (leaves) and compositions of objects (composites) uniformly.

💡 Link to code example

💊 Link to test

Client Code example

File file1 = new File("file1.txt", 120);
File file2 = new File("file2.txt", 200);

Folder folder1 = new Folder("Folder1");
Folder folder2 = new Folder("Folder2");

folder1.addComponent(file1);
folder1.addComponent(file2);

folder2.addComponent(folder1);

Decorator Pattern

✏️ The Decorator pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.

The Decorator pattern allows you to stack decorators on top of each other, each adding its own behavior, and providing a flexible way to modify the behavior of individual objects at runtime.

💡 Link to code example

💊 Link to test

Client Code example

Component order = new FoamMilkDecorator(
    new ChocolateDecorator(
        new EspressoComponent()
    )
);

System.out.println(String.format("%.2f", order.calculatePrice()));

Facade Pattern

✏️ The Facade pattern provides a simplified interface to a set of complex subsystems. It allows clients to interact with a single unified interface without needing to understand the inner workings of each subsystem.

💡 Link to code example

💊 Link to test

OrderFacade facade = new OrderFacade();
String result = facade.placeOrder("Laptop", 2, 2000.0);
public class OrderFacade {
    private final PaymentProcessor paymentProcessor;
    private final InventoryManager inventoryManager;
    private final ShippingService shippingService;
    
    // ... some constructor

    public String placeOrder(String product, int quantity, double amount) {
        // operations accessing fields of subsystems
        
        return // some value
    }
}

Flyweight Pattern

✏️ The Flyweight pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible with similar objects. It separates intrinsic state (shared, immutable data) from extrinsic state (context-dependent, provided from outside).

The pattern is useful when a system needs to handle a large number of fine-grained objects efficiently, such as characters in a text editor, tiles in a game, or icons in a UI.

Components:

  • Flyweight: declares interface for objects that can act as shared.
  • ConcreteFlyweight: implements the Flyweight and stores intrinsic state.
  • UnsharedConcreteFlyweight: objects that cannot be shared but may use Flyweights internally.
  • FlyweightFactory: creates and manages Flyweight objects, ensuring they are reused.

💡 Link to code example

💊 Link to test

FlyweightFactory factory = new FlyweightFactory();

Flyweight sharedA1 = factory.getFlyweight("A");
Flyweight sharedA2 = factory.getFlyweight("A");
Flyweight sharedB = factory.getFlyweight("B");

String first = sharedA1.operation("first");
// "Intrinsic: A, Extrinsic: first"
String second = sharedA2.operation("second");
// "Intrinsic: A, Extrinsic: second"
String context = sharedB.operation("context");
// "Intrinsic: B, Extrinsic: context"

To keep interface:

Flyweight unshared = new UnsharedConcreteFlyweight("CustomState");
String result = unshared.operation("external context");
// "Unshared: CustomState, Extrinsic: external context"

Proxy Pattern

✏️ The Proxy pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. This can be useful for lazy initialization, access control, logging, or caching. The Proxy implements the same interface as the RealSubject and delegates calls to it, while possibly adding extra behavior.

💡 Link to code example

💊 Link to test

Client Code example

RealSubject real = new RealSubject();
Proxy proxy = new Proxy(real);

String result = proxy.getData();

Behavioral Patterns

Chain of Responsibility Pattern

✏️ The Chain of Responsibility pattern is a design pattern where a request is passed through a chain of handlers. Each handler decides either to process the request or pass it to the next handler in the chain. It promotes loose coupling between the sender and receiver of a request and is commonly used in scenarios like event handling or middleware processing in web applications.

💡 Link to code example

💊 Link to test

Client Code example

AbstractHandler.MemberList list = new AbstractHandler.MemberList();
list.add(new AbstractHandler.Member("John", 50));
list.add(new AbstractHandler.Member("Doe", 62));
list.add(new AbstractHandler.Member("Marilyn", 32));

AbstractHandler.initializeChain(
    new FilterHandler(list, 30), 
    new SortByAgeHandler(list)
).handle();

Variant 1: Very Simple example

This example does not work with a data context and no decision to process or pass.

💡 Link to very simple code example

💊 Link to test

Client Code example
AbstractHandler firstHandler = new FirstHandler();
AbstractHandler secondHandler = new SecondHandler();
firstHandler.setNext(secondHandler);
firstHandler.handle();

Variant 2: Example delegating handling tasks

Here data context is passed on process and handler does not care about handling next, building chain, steering handle tasks from outside of handle method.

💡 Link to code example

💊 Link to test

Client Code example
Chain.MemberList list = new Chain.MemberList();
list.add(new Chain.Member("John", 50));
list.add(new Chain.Member("Doe", 62));
list.add(new Chain.Member("Marilyn", 32));

new Chain()
    .add(new FilterHandler(40))
    .add(new SortByAgeHandler())
    .process(list);

Handling guard can vary in complexity...

if (memberList.isEmpty()) {
    return memberList;
}

Command Pattern

✏️ The Command pattern encapsulates a request as an object, allowing other objects to parameterize with different requests, enqueue commands in queues, log them, or support undo operations. This behavioral pattern separates tasks into small steps, enabling high reusability and can be used asynchronous concurrency.

💡 Link to code example

💊 Link to test

In this example receiver gets together with data storage object, this even can be separated. Obvious Person type is immutable but not used as record to provide multiple constructors.

Client Code example

PersonInvoker invoker = new PersonInvoker();
PersonSetReceiver list = new PersonSetReceiver();

invoker.setCommand(new CreatePersonCommand(list, new Person("John", 62)));
invoker.setCommand(new CreatePersonCommand(list, new Person("Peter", 43)));
invoker.setCommand(new CreatePersonCommand(list, new Person("James", 21)));

invoker.setCommand(new UpdatePersonCommand(list, new Person(1L, "Paul", 34)));
invoker.setCommand(new DeletePersonCommand(list, new Person(1L)));

invoker.runCommand();

Interpreter Pattern

✏️ The Interpreter Pattern defines a grammar and provides an interpreter that evaluates sentences of that grammar. Each rule or symbol of the grammar corresponds to a class implementing a common interface.

This pattern is suitable for small domain-specific languages, rule engines, and formula evaluators.

  • Expression – defines the interpret(Context) interface.
  • VariableExpression – represents variables (terminal symbols).
  • NonTerminalExpression – represents grammar rules combining other expressions (AND, OR, NOT, etc.).
  • Context – provides variable values.
  • Client – builds and interprets expression trees.

💡 Link to code example

💊 Link to test

Client Code example

Context context = new Context(Map.of(
        "a", true,
        "b", false,
        "c", true
));

// Expression: a AND NOT (b OR c)
Expression expr = new AndExpression(
        new VariableExpression("a"),
        new NotExpression(
                new OrExpression(
                        new VariableExpression("b"),
                        new VariableExpression("c")
                )
        )
);

boolean result = expr.interpret(context); // false

Iterator Pattern

✏️ The Iterator Pattern simplifies traversing collections without exposing their structure. With a custom iterator, you can tailor traversal to specific needs. Java's built-in iterators streamline iteration over standard collections, ensuring consistency and interoperability.

Variant 1: Example using simply Java's built-in iterator

💡 Link to code example

💊 Link to test

Variant 2: Example using custom iterator

💡 Link to code example

💊 Link to test

Client Code example

List<Integer> numbers = new ArrayList<>(List.of(1, 2));
ConcreteAggregate<Integer> aggregate = new ConcreteAggregate<>(numbers);
Iterator<Integer> iterator = aggregate.createIterator();

List<Integer> result = new ArrayList<>();
while (iterator.hasNext()) {
    result.add(iterator.next());
}

Mediator Pattern

✏️ The Mediator pattern is a behavioral design pattern that allows objects to communicate with each other without needing to know each other's implementation. When a 'colleague' sends a message through the mediator, the mediator distributes the message to all other 'colleagues' except the sender. This bidirectional communication demonstrates the Mediator pattern's central idea: decoupling the objects communicating with each other by introducing a mediator object. Instead of directly interacting with each other, objects communicate through the mediator, which encapsulates the communication logic and facilitates indirect communication between them.

💡 Link to code example

💊 Link to test

Client Code example

Mediator mediator = new Mediator();

ConcreteGermanColleague germanColleague = new ConcreteGermanColleague(mediator);
ConcretePolishColleague polishColleague = new ConcretePolishColleague(mediator);

mediator.addColleague(germanColleague);
mediator.addColleague(polishColleague);

germanColleague.send("Schönes Wetter!");
polishColleague.send("ładna pogoda!");

polishColleague.getLastReceivedMessage(); // "Witaj świecie: Schönes Wetter!"
germanColleague.getLastReceivedMessage(); // "Hallo Welt: ładna pogoda!"

Memento Pattern

✏️ The Memento pattern enables the capture and externalization of an object's internal state, without violating encapsulation. It achieves this by allowing an object to create a memento object that stores its state, which can later be restored to the object. This pattern is particularly useful in scenarios where the state of an object needs to be saved and restored, such as in undo mechanisms or checkpoints in an application.

💡 Link to code example

💊 Link to test

Client Code example

Originator originator = new Originator("foo");
Caretaker caretaker = new Caretaker();
originator.changeState("bar");
caretaker.addMemento(originator.createSnapshot());
originator.restoreSnapshot(caretaker.getMemento(0));
String actualFooBar = originator.getState();

Observer Pattern

✏️ The Observer Pattern is a design pattern where an object (the subject) maintains a list of its dependents (observers) and notifies them of any state changes. This allows for loose coupling between components, making it easier to maintain and extend the system.

💡 Link to code example

💊 Link to test

Client Code example

ConcreteRoutingSubject subject = new ConcreteRoutingSubject();
AbstractObserver.createAndAttach(new CalculateDistanceObserver(subject));
AbstractObserver.createAndAttach(new CalculateDurationObserver(subject));
        
RouteEntity route = new RouteEntity("way home", new Point(0,0), new Point(20,20));
//subject.detachObserver(/*some concrete observer*/);
subject.setStateEntity(route);

System.out.println(
    "You would have " 
    + new DecimalFormat("#0.00").format(route.getDuration()) 
    + " h to travel"
);

Variant delegating event handling using java.beans.PropertyChangeListener

This example makes use of PropertyChangeListener-API of java.beans module. It delegates notification and verification of state change to module. Even registration of listeners will be supported in PropertyChangeListener.

Client code example, see above - interface did not change.

💡 Link to code example

💊 Link to test

State Pattern

✏️ The State pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes. The object will appear to change its class. Instead of using conditional logic scattered across the code, states encapsulate behavior and transitions explicitly.

In practice, each State encapsulates rules of transitions, and the Context delegates behavior to its current state.

💡 Link to code example

💊 Link to test

Client Code example

Document document = new Document();
String firstState = document.getStateName();
// "Draft"

document.publish();
String secondState = document.getStateName();
// "Published"

document.archive();
String thirdState = document.getStateName();
// "Archived"

Strategy Pattern

✏️ The Strategy pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one of them, and make them interchangeable. It enables a client to choose a particular algorithm from the family of algorithms at runtime without altering the client's code.

The key idea behind the Strategy pattern is to encapsulate algorithms into separate classes and make them interchangeable. This allows the client to select the appropriate algorithm at runtime.

💡 Link to code example

💊 Link to test

Client Code example

CalculatorContext calculator = new CalculatorContext();
calculator.setBehavior(new AdditionStrategy());
calculator.calculate(2,6); // 8
calculator.setBehavior(new MultiplicationStrategy());
calculator.calculate(2,6); // 12

Template Method Pattern

✏️ Gang of Four describes the purpose of the "Template Method" pattern as follows: "Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses define certain steps of an algorithm without changing the algorithm's structure". It’s a way to outline the steps of a process while letting subclasses fill in the details. Think of it as a recipe: the main steps are fixed, but you can choose the ingredients for some parts.

💡 Link to code example

💊 Link to test

Client Code example

UserProcessor userProcessor = new UserProcessor();
List<Data> dataList = userProcessor.fetchData();

ProductProcessor productProcessor = new ProductProcessor();
List<Data> dataList = productProcessor.fetchData();

Visitor Pattern

✏️ Visitor pattern allows operations to be performed on elements of a structure without knowing the elements themselves. This is achieved by defining a separate "Visitor" class that provides specific operations for each element of the structure. The elements of the structure then implement an "accept" method, which allows the Visitor to perform the operations on the element.

The Visitor pattern is often used in scenarios where one wants to perform operations on various elements of a structure without knowing the elements themselves or changing their class hierarchy. It provides a way to make behavioral changes to elements of a structure without modifying the elements themselves.

💡 Link to code example

💊 Link to test

Client Code example

Visitor methods in this example are divided by override of different element types. Element-visitation methods named by their certain element is possible, too.

ObjectStructure zoo = new ObjectStructure();
zoo.addAnimal(new LionElement());
zoo.addAnimal(new ElephantElement());

Visitor feedingVisitor = new FeedingVisitor();
Visitor VeterinarianVisitor = new VeterinarianVisitor();
zoo.visitAnimals(feedingVisitor);
zoo.visitAnimals(VeterinarianVisitor);

You could make use of functional interface of Consumer instead of defining accept on your own:

+ import java.util.function.Consumer;

- public interface Element {
+ public interface Element extends Consumer<Visitor> {
-   void accept(Visitor visitor);
-
    boolean isFedUp();
    boolean isHealthChecked();

Loop in ObjectStructure would do same job:

public void visitAnimals(Visitor visitor) {
    animals.forEach(element -> element.accept(visitor));
}

About

A cheat sheet of design patterns 🪛 for your daily Java work.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages