From fe1e1c0a9f3fbeacda3b50ebd2cc7ba4be7c71bf Mon Sep 17 00:00:00 2001 From: Joao Fraga Date: Mon, 30 Dec 2024 15:03:42 +0000 Subject: [PATCH] docs: dp.md --- content/post/dp.md | 692 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 692 insertions(+) create mode 100644 content/post/dp.md diff --git a/content/post/dp.md b/content/post/dp.md new file mode 100644 index 0000000..17e1343 --- /dev/null +++ b/content/post/dp.md @@ -0,0 +1,692 @@ +--- +title: "DP - Design Patterns" +description: "Design Patterns in Java." +date: 2024-12-30T13:00:00Z +draft: false +--- + +Overview of the simplest and most common design patterns according to the book *"Design Patterns Elements of Reusable Object-Oriented Software"*: + +- Abstract Factory +- Factory Method +- Adapter +- Composite +- Decorator +- Observer +- Strategy +- Template Method + +with additional patterns which I personally consider common as well: + +- Singleton +- Builder +- Proxy +- Command +- Chain of Responsibility +- Mediator +- Visitor + +# Creational Patterns + +**Abstract Factory**: + +Creates families of related objects without specifying their concrete classes. +It provides a higher level of abstraction, enabling the client to produce multiple types of objects from a single factory. + +```java +public interface ButtonAbstractFactory { + Button createButton(); +} + +public class WindowsButtonFactory implements ButtonAbstractFactory { + public Button createButton() { + return new WindowsButton(); + } +} + +public class MacButtonFactory implements ButtonAbstractFactory { + public Button createButton() { + return new MacButton(); + } +} + +public interface Button { + void render(); +} + +public class WindowsButton implements Button { + public void render() { + System.out.println("Rendering Windows Button"); + } +} + +public class MacButton implements Button { + public void render() { + System.out.println("Rendering Mac Button"); + } +} +``` + +**Factory Method**: + +Defines a method for creating objects, but allows subclasses to decide which object to instantiate. +This pattern promotes loose coupling by relying on the subclass for the specific implementation. + +```java +public interface Button { + void render(); +} + +public class WindowsButton implements Button { + public void render() { + System.out.println("Rendering Windows Button"); + } +} + +public class MacButton implements Button { + public void render() { + System.out.println("Rendering Mac Button"); + } +} + +public abstract class ButtonFactory { + public void display() { + Button button = createButton(); + button.render(); + } + + protected abstract Button createButton(); +} + +public class WindowsButtonFactory extends ButtonFactory { + @Override + protected Button createButton() { + return new WindowsButton(); + } +} + +public class MacButtonFactory extends ButtonFactory { + @Override + protected Button createButton() { + return new MacButton(); + } +} +``` + +**Singleton** + +Ensures a class has only one instance and provides a global access point to it. +This is often used for managing shared resources like configuration settings or database connections. + +```java +public class Singleton { + private static Singleton instance; + + private Singleton() { + // Private constructor to prevent instantiation + } + + public static Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } +} +``` + +**Builder** + +Separates the construction of a complex object from its representation, allowing the same construction process to create different representations. +This is often used to construct objects with many optional parameters. + +```java +public class House { + private int rooms; + private boolean hasGarage; + private boolean hasGarden; + + private House(Builder builder) { + this.rooms = builder.rooms; + this.hasGarage = builder.hasGarage; + this.hasGarden = builder.hasGarden; + } + + public static class Builder { + private int rooms; + private boolean hasGarage; + private boolean hasGarden; + + public Builder setRooms(int rooms) { + this.rooms = rooms; + return this; + } + + public Builder setGarage(boolean hasGarage) { + this.hasGarage = hasGarage; + return this; + } + + public Builder setGarden(boolean hasGarden) { + this.hasGarden = hasGarden; + return this; + } + + public House build() { + return new House(this); + } + } +} +``` + +# Structural Patterns + +**Adapter**: + +Acts as a bridge between two incompatible interfaces. +It allows existing classes to work together without modifying their source code by adapting their interfaces. + +```java +public interface Celsius { + double getCelsiusTemperature(); +} + +public class Fahrenheit { + private double temperature; + + public Fahrenheit(double temperature) { + this.temperature = temperature; + } + + public double getFahrenheitTemperature() { + return temperature; + } +} + +public class FahrenheitToCelsiusAdapter implements Celsius { + private Fahrenheit device; + + public FahrenheitToCelsiusAdapter(Fahrenheit device) { + this.device = device; + } + + @Override + public double getCelsiusTemperature() { + double fahrenheit = device.getFahrenheitTemperature(); + return (fahrenheit - 32) * 5 / 9; + } +} +``` + +**Composite**: + +Enables you to compose objects into tree structures to represent part-whole hierarchies. +The pattern treats individual objects and compositions of objects uniformly, simplifying client code. + +```java +public interface JSONElement { + String toJSONString(); +} + +public class JSONValue implements JSONElement { + private Object value; + + public JSONValue(Object value) { + this.value = value; + } + + @Override + public String toJSONString() { + return (value instanceof String) ? "\"" + value + "\"" : value.toString(); + } +} + +public class JSONArray implements JSONElement { + private List elements = new ArrayList<>(); + + public void add(JSONElement element) { + elements.add(element); + } + + @Override + public String toJSONString() { + return elements.stream() + .map(JSONElement::toJSONString) + .collect(Collectors.joining(", ", "[", "]")); + } +} + +public class JSONObject implements JSONElement { + private Map elements = new HashMap<>(); + + public void add(String key, JSONElement element) { + elements.put(key, element); + } + + @Override + public String toJSONString() { + return elements.entrySet().stream() + .map(e -> "\"" + e.getKey() + "\": " + e.getValue().toJSONString()) + .collect(Collectors.joining(", ", "{", "}")); + } +} +``` + +**Decorator**: + +Attaches additional behavior or responsibilities to an object dynamically. +It provides a flexible alternative to subclassing for extending functionality. + +```java +public interface DataSource { + void writeData(String data); + String readData(); +} + +public class FileDataSource implements DataSource { + private String filename; + + public FileDataSource(String filename) { + this.filename = filename; + } + + @Override + public void writeData(String data) { + System.out.println("Writing data to " + filename); + } + + @Override + public String readData() { + return "Reading data from " + filename; + } +} + +public class EncryptionDecorator implements DataSource { + private DataSource source; + + public EncryptionDecorator(DataSource source) { + this.source = source; + } + + @Override + public void writeData(String data) { + source.writeData(encrypt(data)); + } + + @Override + public String readData() { + return decrypt(source.readData()); + } + + private String encrypt(String data) { + return "Encrypted[" + data + "]"; + } + + private String decrypt(String data) { + return data.replace("Encrypted[", "").replace("]", ""); + } +} +``` + +**Proxy**: + +Provides a placeholder or surrogate to control access to another object. +This is commonly used for purposes like lazy initialization, access control, or logging. + +```java +public interface Database { + void query(String sql); +} + +public class RealDatabase implements Database { + public RealDatabase() { + connectToDatabase(); + } + + private void connectToDatabase() { + System.out.println("Connecting to the database..."); + } + + @Override + public void query(String sql) { + System.out.println("Executing query: " + sql); + } +} + +public class DatabaseProxy implements Database { + private RealDatabase realDatabase; + + @Override + public void query(String sql) { + if (realDatabase == null) { + realDatabase = new RealDatabase(); + } + realDatabase.query(sql); + } +} +``` + +# Behavioral Patterns + +**Observer**: + +Defines a one-to-many dependency between objects, where a change in the state of one object automatically notifies and updates all its dependents. +This pattern is particularly useful for implementing event-driven systems. + +```java +public interface Observer { + void update(String message); +} + +public class Subscriber implements Observer { + private String name; + + public Subscriber(String name) { + this.name = name; + } + + @Override + public void update(String message) { + System.out.println(name + " received update: " + message); + } +} + +public class Publisher { + private List observers = new ArrayList<>(); + + public void subscribe(Observer observer) { + observers.add(observer); + } + + public void unsubscribe(Observer observer) { + observers.remove(observer); + } + + public void notifyObservers(String message) { + for (Observer observer : observers) { + observer.update(message); + } + } +} +``` + +**Strategy**: + +Encapsulates interchangeable behaviors and algorithms, allowing you to select them at runtime. +This pattern promotes the Open/Closed Principle by enabling new strategies to be introduced without altering existing code. + +```java +public interface PaymentStrategy { + void pay(int amount); +} + +public class CreditCardPayment implements PaymentStrategy { + @Override + public void pay(int amount) { + System.out.println("Paid " + amount + " using credit card."); + } +} + +public class BankTransferPayment implements PaymentStrategy { + @Override + public void pay(int amount) { + System.out.println("Paid " + amount + " using bank transfer."); + } +} +``` + +**Template Method**: + +Defines the skeleton of an algorithm in a base class and allows subclasses to override specific steps of the algorithm without changing its structure. +This ensures code reuse while maintaining flexibility for customization. + +```java +public abstract class Game { + public final void play() { + initialize(); + startPlay(); + endPlay(); + } + + protected abstract void initialize(); + protected abstract void startPlay(); + protected abstract void endPlay(); +} + +public class Chess extends Game { + @Override + protected void initialize() { + System.out.println("Chess Game Initialized!"); + } + + @Override + protected void startPlay() { + System.out.println("Chess Game Started!"); + } + + @Override + protected void endPlay() { + System.out.println("Chess Game Finished!"); + } +} +``` + +**Command**: + +Encapsulates a request as an object, allowing you to parameterize objects with different operations, delay execution, or support undoable operations. +This pattern decouples the invoker from the specific actions performed. + +```java +public interface Command { + void execute(); +} + +public class Light { + public void turnOn() { + System.out.println("Light is ON"); + } + + public void turnOff() { + System.out.println("Light is OFF"); + } +} + +public class TurnOnLightCommand implements Command { + private Light light; + + public TurnOnLightCommand(Light light) { + this.light = light; + } + + @Override + public void execute() { + light.turnOn(); + } +} + +public class TurnOffLightCommand implements Command { + private Light light; + + public TurnOffLightCommand(Light light) { + this.light = light; + } + + @Override + public void execute() { + light.turnOff(); + } +} + +public class RemoteControl { + private Command command; + + public void setCommand(Command command) { + this.command = command; + } + + public void pressButton() { + command.execute(); + } +} +``` + +**Chain of Responsibility**: + +Passes a request along a chain of handlers, where each handler decides whether to process the request or pass it to the next handler. +This pattern promotes flexibility and reduces coupling between senders and receivers + +```java +public abstract class Handler { + private Handler next; + + public void setNext(Handler next) { + this.next = next; + } + + public void handleRequest(String request) { + if (next != null) { + next.handleRequest(request); + } + } +} + +public class LoggingHandler extends Handler { + @Override + public void handleRequest(String request) { + System.out.println("Logging request: " + request); + super.handleRequest(request); + } +} + +public class AuthenticationHandler extends Handler { + @Override + public void handleRequest(String request) { + System.out.println("Authentication successful."); + super.handleRequest(request); + } +} + +public class AuthorizationHandler extends Handler { + @Override + public void handleRequest(String request) { + System.out.println("Authorization successful."); + super.handleRequest(request); + } +} +``` + +**Mediator**: + +Encapsulates how objects interact by centralizing communication into a mediator object. +This promotes loose coupling and simplifies object collaboration. + +```java +public interface Mediator { + void sendMessage(String message, Person sender); +} + +public abstract class Person { + protected Mediator mediator; + + public Person(Mediator mediator) { + this.mediator = mediator; + } + + public abstract void receiveMessage(String message); +} + +public class ConcreteMediator implements Mediator { + private Person person1; + private Person person2; + + public void setPerson1(Person person) { + this.person1 = person; + } + + public void setPerson2(Person person) { + this.person2 = person; + } + + @Override + public void sendMessage(String message, Person sender) { + if (sender.equals(person1)) { + person2.receiveMessage(message); + } else { + person1.receiveMessage(message); + } + } +} + +public class User extends Person { + public User(Mediator mediator) { + super(mediator); + } + + @Override + public void receiveMessage(String message) { + System.out.println("User received: " + message); + } +} +``` + +**Visitor**: + +Separates algorithms from the object structure they operate on, enabling new operations to be added without modifying the objects. +This pattern is particularly useful for operations on complex object structures. + +```java +public interface Visitor { + void visit(Book book); + void visit(Game game); +} + +public interface ItemElement { + void accept(Visitor visitor); +} + +public class Book implements ItemElement { + private String title; + + public Book(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} + +public class Game implements ItemElement { + private String name; + + public game(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} + +public class ShoppingCartVisitor implements Visitor { + @Override + public void visit(Book book) { + System.out.println("Buying book: " + book.getTitle()); + } + + @Override + public void visit(Game game) { + System.out.println("Buying game: " + game.getName()); + } +} +```