diff --git a/content/post/dp.md b/content/post/dp.md new file mode 100644 index 0000000..35daba2 --- /dev/null +++ b/content/post/dp.md @@ -0,0 +1,463 @@ +--- +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 an additional 3 bonus patterns which I personally consider useful as well: + +- Singleton +- Builder +- Proxy + +# 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 fahrenheitDevice; + + public FahrenheitToCelsiusAdapter(Fahrenheit fahrenheitDevice) { + this.fahrenheitDevice = fahrenheitDevice; + } + + @Override + public double getCelsiusTemperature() { + double fahrenheit = fahrenheitDevice.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 FileSystemItem { + void list(String indent); +} + +public class File implements FileSystemItem { + private String name; + + public File(String name) { + this.name = name; + } + + @Override + public void list(String indent) { + System.out.println(indent + "File: " + name); + } +} + +public class Directory implements FileSystemItem { + private String name; + private List items = new ArrayList<>(); + + public Directory(String name) { + this.name = name; + } + + public void add(FileSystemItem item) { + items.add(item); + } + + public void remove(FileSystemItem item) { + items.remove(item); + } + + @Override + public void list(String indent) { + System.out.println(indent + "Directory: " + name); + for (FileSystemItem item : items) { + item.list(indent + " "); + } + } +} +``` + +**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!"); + } +} +```