What design patterns, besides Singleton, have you used in your projects?
sobes.tech AI
Answer from AI
-
Factory Method: Used for creating objects of various types within a parent class, delegating creation to subclasses. This made the code more flexible when adding new subtypes.
// Example Factory Method interface Product { void doSomething(); } class ConcreteProductA implements Product { @Override public void doSomething() { System.out.println("Product A"); } } class ConcreteProductB implements Product { @Override public void doSomething() { System.out.println("Product B"); } } abstract class Creator { public abstract Product factoryMethod(); public void operate() { Product product = factoryMethod(); product.doSomething(); } } class ConcreteCreatorA extends Creator { @Override public Product factoryMethod() { return new ConcreteProductA(); } } class ConcreteCreatorB extends Creator { @Override public Product factoryMethod() { return new ConcreteProductB(); } } -
Observer: Used to implement a subscription/notification system where one object (publisher) notifies multiple dependent objects (subscribers) about changes in its state.
// Example Observer import java.util.ArrayList; import java.util.List; interface Observer { void update(String message); } class ConcreteObserverA implements Observer { @Override public void update(String message) { System.out.println("Observer A received: " + message); } } class ConcreteObserverB implements Observer { @Override public void update(String message) { System.out.println("Observer B received: " + message); } } class Subject { private List<Observer> observers = new ArrayList<>(); private String state; public void attach(Observer observer) { observers.add(observer); } public void detach(Observer observer) { observers.remove(observer); } public void setState(String state) { this.state = state; notifyObservers(); } private void notifyObservers() { for (Observer observer : observers) { observer.update(state); } } } -
Strategy: Used to define a family of algorithms, encapsulate each one, and make them interchangeable. This allows the client to choose an algorithm at runtime.
// Example Strategy interface Strategy { double execute(double a, double b); } class AddStrategy implements Strategy { @Override public double execute(double a, double b) { return a + b; } } class SubtractStrategy implements Strategy { @Override public double execute(double a, double b) { return a - b; } } class MultiplyStrategy implements Strategy { @Override public double execute(double a, double b) { return a * b; } } class Context { private Strategy strategy; public void setStrategy(Strategy strategy) { this.strategy = strategy; } public double executeStrategy(double a, double b) { return strategy.execute(a, b); } } -
Decorator: Used for dynamically adding responsibilities to objects while preserving the same interface. This is a flexible alternative to inheritance for extending functionality.
// Example Decorator interface Coffee { String getDescription(); double getCost(); } class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple coffee"; } @Override public double getCost() { return 1.0; } } abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee decoratedCoffee) { this.decoratedCoffee = decoratedCoffee; } @Override public String getDescription() { return decoratedCoffee.getDescription(); } @Override public double getCost() { return decoratedCoffee.getCost(); } } class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee decoratedCoffee) { super(decoratedCoffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Milk"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.5; } } class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee decoratedCoffee) { super(decoratedCoffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Sugar"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.2; } } -
Template Method: Used to define the skeleton of an algorithm in a superclass, allowing subclasses to override specific steps without changing the overall algorithm structure.
// Example Template Method abstract class Game { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); // template method public final void playGame() { initialize(); startPlay(); endPlay(); } } class Chess extends Game { @Override void initialize() { System.out.println("Initializing Chess Game..."); } @Override void startPlay() { System.out.println("Starting Chess Game..."); } @Override void endPlay() { System.out.println("Ending Chess Game..."); } } class Monopoly extends Game { @Override void initialize() { System.out.println("Initializing Monopoly Game..."); } @Override void startPlay() { System.out.println("Starting Monopoly Game..."); } @Override void endPlay() { System.out.println("Ending Monopoly Game..."); } }
Also encountered the use of Dependency Injection (often implemented by frameworks, but understanding the principles is important).