Sobes.tech
Middle

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).