실무에서 자주 사용되는 디자인 패턴

싱글톤 패턴

싱글톤 패턴은 인스턴스가 오직 하나만 생성되는 패턴입니다. 이 패턴은 전역적으로 사용되어야 할 객체를 하나의 인스턴스로 유지하고, 다른 객체들이 이 인스턴스에 접근할 수 있도록 합니다.

싱글톤 패턴의 구현

싱글톤 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 생성자를 private으로 설정하여 외부에서 인스턴스 생성 방지
    }

    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

팩토리 메서드 패턴

팩토리 메서드 패턴은 객체를 생성하기 위한 인터페이스를 제공하고, 어떤 클래스의 인스턴스를 생성할지는 서브클래스가 결정하도록 하는 패턴입니다. 이를 통해 객체의 생성과 관련된 코드를 분리할 수 있습니다.

팩토리 메서드 패턴의 구현

팩토리 메서드 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public abstract class Product {
    public abstract void use();
}

public class ConcreteProduct extends Product {
    @Override
    public void use() {
        System.out.println("사용중인 상품입니다.");
    }
}

public abstract class Creator {
    public abstract Product factoryMethod();

    public void operate() {
        Product product = factoryMethod();
        product.use();
    }
}

public class ConcreteCreator extends Creator {
    @Override
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}

추상 팩토리 패턴

추상 팩토리 패턴은 서로 관련된 객체들을 통째로 생성하거나 서로 관련된 객체들의 집합을 생성하기 위한 인터페이스를 제공하는 패턴입니다. 이를 통해 클라이언트 코드는 구체적인 클래스를 알 필요 없이 객체를 생성할 수 있습니다.

추상 팩토리 패턴의 구현

추상 팩토리 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface AbstractFactory {
    AbstractProductA createProductA();
    AbstractProductB createProductB();
}

public class ConcreteFactory1 implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB1();
    }
}

public class ConcreteFactory2 implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB2();
    }
}

public interface AbstractProductA {
    void use();
}

public class ConcreteProductA1 implements AbstractProductA {
    @Override
    public void use() {
        System.out.println("Product A1을 사용중입니다.");
    }
}

public class ConcreteProductA2 implements AbstractProductA {
    @Override
    public void use() {
        System.out.println("Product A2를 사용중입니다.");
    }
}

public interface AbstractProductB {
    void eat();
}

public class ConcreteProductB1 implements AbstractProductB {
    @Override
    public void eat() {
        System.out.println("Product B1을 섭취중입니다.");
    }
}

public class ConcreteProductB2 implements AbstractProductB {
    @Override
    public void eat() {
        System.out.println("Product B2를 섭취중입니다.");
    }
}

빌더 패턴

빌더 패턴은 복잡한 객체의 생성 과정을 단순화하기 위한 패턴입니다. 빌더 패턴은 객체의 생성과 구성을 분리하여, 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있도록 합니다.

빌더 패턴의 구현

빌더 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public class Product {
    private String propertyA;
    private String propertyB;
    
    private Product(Builder builder) {
        this.propertyA = builder.propertyA;
        this.propertyB = builder.propertyB;
    }
    
    public String getPropertyA() {
        return propertyA;
    }
    
    public String getPropertyB() {
        return propertyB;
    }
    
    public static class Builder {
        private String propertyA;
        private String propertyB;
        
        public Builder setPropertyA(String propertyA) {
            this.propertyA = propertyA;
            return this;
        }
        
        public Builder setPropertyB(String propertyB) {
            this.propertyB = propertyB;
            return this;
        }
        
        public Product build() {
            return new Product(this);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Product product = new Product.Builder()
            .setPropertyA("valueA")
            .setPropertyB("valueB")
            .build();
        
        System.out.println("Property A: " + product.getPropertyA());
        System.out.println("Property B: " + product.getPropertyB());
    }
}

위의 예시 코드에서는 Product 클래스에 Builder 클래스를 내부 정적 클래스로 정의하여 객체의 생성 과정을 단순화하고, 속성을 설정하기 위한 메서드 체인을 제공합니다. 클라이언트는 빌더를 사용하여 필요한 속성을 설정한 다음, build() 메서드를 호출하여 최종적으로 객체를 생성할 수 있습니다.


프로토타입 패턴

프로토타입 패턴은 객체를 복사하여 새로운 객체를 생성하는 패턴입니다. 이를 통해 객체 생성 과정이 번거롭고 복잡한 경우에 객체를 효율적으로 생성할 수 있습니다.

프로토타입 패턴의 구현

프로토타입 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public abstract class Shape implements Cloneable {
    private String id;
    protected String type;
    
    public String getType() {
        return type;
    }
    
    public String getId() {
        return id;
    }
    
    public void setId(String id) {
        this.id = id;
    }
    
    abstract void draw();
    
    @Override
    public Object clone() {
        Object clone = null;
        
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        
        return clone;
    }
}

public class Circle extends Shape {
    public Circle() {
        type = "Circle";
    }
    
    @Override
    void draw() {
        System.out.println("원을 그립니다.");
    }
}

public class Square extends Shape {
    public Square() {
        type = "Square";
    }
    
    @Override
    void draw() {
        System.out.println("사각형을 그립니다.");
    }
}

public class ShapeCache {
    private static Map shapeMap = new HashMap<>();
    
    public static Shape getShape(String shapeId) {
        Shape cachedShape = shapeMap.get(shapeId);
        return (Shape) cachedShape.clone();
    }
    
    public static void loadCache() {
        Circle circle = new Circle();
        circle.setId("1");
        shapeMap.put(circle.getId(), circle);
        
        Square square = new Square();
        square.setId("2");
        shapeMap.put(square.getId(), square);
    }
}

public class Main {
    public static void main(String[] args) {
        ShapeCache.loadCache();
        
        Shape clonedShape1 = ShapeCache.getShape("1");
        System.out.println("Shape: " + clonedShape1.getType());
        
        Shape clonedShape2 = ShapeCache.getShape("2");
        System.out.println("Shape: " + clonedShape2.getType());
    }
}

위의 예시 코드에서는 Shape 클래스를 추상 클래스로 정의하고, 이를 상속받은 Circle과 Square 클래스를 구현합니다. ShapeCache 클래스는 초기에 원본 객체를 생성하여 Map에 저장하고, getShape 메서드를 통해 복제된 객체를 반환합니다. 클라이언트는 ShapeCache를 통해 필요한 객체를 복제하여 사용할 수 있습니다.


어댑터 패턴

어댑터 패턴은 서로 다른 인터페이스를 가지는 두 개의 클래스를 연결하여 함께 동작할 수 있게 해주는 패턴입니다. 이를 통해 기존의 클래스를 수정하지 않고도 호환성이 없는 클래스들을 함께 사용할 수 있습니다.

어댑터 패턴의 구현

어댑터 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface MediaPlayer {
    void play(String audioType, String fileName);
}

public interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}

public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file. Name: " + fileName);
    }
    
    @Override
    public void playMp4(String fileName) {
        // do nothing
    }
}

public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // do nothing
    }
    
    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file. Name: " + fileName);
    }
}

public class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedMediaPlayer;
    
    public MediaAdapter(String audioType) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMediaPlayer = new VlcPlayer();
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMediaPlayer = new Mp4Player();
        }
    }
    
    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("vlc")) {
            advancedMediaPlayer.playVlc(fileName);
        } else if (audioType.equalsIgnoreCase("mp4")) {
            advancedMediaPlayer.playMp4(fileName);
        }
    }
}

public class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file. Name: " + fileName);
        } else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Invalid media type. " + audioType + " format not supported.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();

        audioPlayer.play("mp3", "song.mp3");
        audioPlayer.play("vlc", "movie.vlc");
        audioPlayer.play("mp4", "video.mp4");
        audioPlayer.play("avi", "video.avi");
    }
}

위의 예시 코드에서는 MediaPlayer 인터페이스와 AdvancedMediaPlayer 인터페이스를 정의하고, 이를 구현한 VlcPlayer와 Mp4Player 클래스를 생성합니다. MediaAdapter 클래스는 MediaPlayer 인터페이스를 구현하여 AdvancedMediaPlayer의 객체를 생성하고 연결시켜줍니다. AudioPlayer 클래스는 필요한 경우 MediaAdapter를 통해 알맞은 형식으로 재생합니다.


브리지 패턴

브리지 패턴은 추상화와 구현을 분리하여 두 개의 계층 구조를 독립적으로 확장할 수 있는 패턴입니다. 이를 통해 추상화와 구현의 관계가 유연해지며, 서로 다른 종류의 클래스를 연결할 수 있습니다.

브리지 패턴의 구현

브리지 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface Color {
    void applyColor();
}

public class RedColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying red color");
    }
}

public class GreenColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying green color");
    }
}

public abstract class Shape {
    protected Color color;
    
    public Shape(Color color) {
        this.color = color;
    }
    
    abstract void draw();
}

public class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }
    
    @Override
    void draw() {
        System.out.print("Drawing a circle. ");
        color.applyColor();
    }
}

public class Square extends Shape {
    public Square(Color color) {
        super(color);
    }
    
    @Override
    void draw() {
        System.out.print("Drawing a square. ");
        color.applyColor();
    }
}

public class Main {
    public static void main(String[] args) {
        Color red = new RedColor();
        Shape redCircle = new Circle(red);
        redCircle.draw();
        
        Color green = new GreenColor();
        Shape greenSquare = new Square(green);
        greenSquare.draw();
    }
}

위의 예시 코드에서는 Color 인터페이스와 이를 구현한 RedColor와 GreenColor 클래스를 생성합니다. Shape 클래스는 Color 인터페이스를 포함하여 추상화를 정의하고, 이를 구현한 Circle과 Square 클래스를 생성합니다. Main 클래스에서는 각각의 도형을 색과 연결하여 그리는 예시를 보여줍니다.


데코레이터 패턴

데코레이터 패턴은 객체의 기능을 동적으로 확장할 수 있는 패턴입니다. 이를 통해 객체의 기능을 조합하여 다양한 기능을 가진 객체를 만들 수 있습니다. 데코레이터 패턴은 상속을 통해 기능을 확장하는 것보다 유연하고 확장성이 더 높습니다.

데코레이터 패턴의 구현

데코레이터 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface Beverage {
    String getDescription();
    double getCost();
}

public class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "Espresso";
    }
    
    @Override
    public double getCost() {
        return 1.99;
    }
}

public class HouseBlend implements Beverage {
    @Override
    public String getDescription() {
        return "House Blend Coffee";
    }
    
    @Override
    public double getCost() {
        return 0.99;
    }
}

public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage;
    
    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

public class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Milk";
    }
    
    @Override
    public double getCost() {
        return 0.10 + beverage.getCost();
    }
}

public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }
    
    @Override
    public double getCost() {
        return 0.20 + beverage.getCost();
    }
}

public class Main {
    public static void main(String[] args) {
        Beverage beverage = new Espresso();
        System.out.println("Beverage: " + beverage.getDescription() + ", Cost: $" + beverage.getCost());
        
        Beverage beverageWithMilk = new Milk(new Espresso());
        System.out.println("Beverage: " + beverageWithMilk.getDescription() + ", Cost: $" + beverageWithMilk.getCost());
        
        Beverage beverageWithMilkAndMocha = new Mocha(new Milk(new Espresso()));
        System.out.println("Beverage: " + beverageWithMilkAndMocha.getDescription() + ", Cost: $" + beverageWithMilkAndMocha.getCost());
    }
}

위의 예시 코드에서는 Beverage 인터페이스를 정의하고, 이를 구현한 Espresso와 HouseBlend 클래스를 생성합니다. CondimentDecorator 추상 클래스는 Beverage를 기반으로 데코레이터를 정의하며, Milk와 Mocha 클래스가 이를 확장합니다. Main 클래스에서는 각각의 음료를 조합하여 기능을 확장한 예시를 보여줍니다.


컴포지트 패턴

컴포지트 패턴은 객체들을 트리 구조로 구성하여 단일 객체와 복합 객체를 동일한 인터페이스로 다룰 수 있게 하는 패턴입니다. 이를 통해 클라이언트는 단일 객체와 복합 객체를 구분하지 않고 일관된 방식으로 사용할 수 있습니다.

컴포지트 패턴의 구현

컴포지트 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


import java.util.ArrayList;
import java.util.List;

public interface Employee {
    String getName();
    double getSalary();
}

public class Developer implements Employee {
    private String name;
    private double salary;
    
    public Developer(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public double getSalary() {
        return salary;
    }
}

public class Manager implements Employee {
    private String name;
    private double salary;
    private List subordinates;
    
    public Manager(String name, double salary) {
        this.name = name;
        this.salary = salary;
        this.subordinates = new ArrayList<>();
    }
    
    public void addSubordinate(Employee employee) {
        subordinates.add(employee);
    }
    
    public void removeSubordinate(Employee employee) {
        subordinates.remove(employee);
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public double getSalary() {
        return salary;
    }
    
    public List getSubordinates() {
        return subordinates;
    }
}

public class Main {
    public static void main(String[] args) {
        Employee dev1 = new Developer("John", 5000);
        Employee dev2 = new Developer("Mike", 4000);
        Employee dev3 = new Developer("Sarah", 6000);
        
        Manager manager = new Manager("Tom", 10000);
        manager.addSubordinate(dev1);
        manager.addSubordinate(dev2);
        manager.addSubordinate(dev3);
        
        System.out.println("Manager: " + manager.getName() + ", Salary: $" + manager.getSalary());
        
        System.out.println("Subordinates:");
        List subordinates = manager.getSubordinates();
        for (Employee subordinate : subordinates) {
            System.out.println(" - " + subordinate.getName() + ", Salary: $" + subordinate.getSalary());
        }
    }
}

위의 예시 코드에서는 Employee 인터페이스를 정의하고, 이를 구현한 Developer와 Manager 클래스를 생성합니다. Manager 클래스는 하위 직원들을 관리하기 위해 리스트를 사용합니다. Main 클래스에서는 Manager와 그의 하위 직원들을 출력하는 예시를 보여줍니다.


옵서버 패턴

옵서버 패턴은 객체 사이에 일 대 다 의존 관계를 정의하여, 어떤 객체의 상태가 변경되면 그 객체에 의존하는 다른 객체들이 자동으로 알림을 받고 업데이트될 수 있게 하는 패턴입니다. 이를 통해 객체 간의 느슨한 결합을 유지하며, 변화에 유연하게 대처할 수 있습니다.

옵서버 패턴의 구현

옵서버 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


import java.util.ArrayList;
import java.util.List;

public interface Observer {
    void update();
}

public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

public class WeatherData implements Subject {
    private double temperature;
    private List observers;
    
    public WeatherData() {
        this.observers = new ArrayList<>();
    }
    
    public double getTemperature() {
        return temperature;
    }
    
    public void setTemperature(double temperature) {
        this.temperature = temperature;
        notifyObservers();
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

public class CurrentConditionsDisplay implements Observer {
    private double temperature;
    
    public CurrentConditionsDisplay(Subject subject) {
        subject.registerObserver(this);
    }
    
    @Override
    public void update() {
        WeatherData weatherData = (WeatherData) subject;
        this.temperature = weatherData.getTemperature();
        display();
    }
    
    public void display() {
        System.out.println("Current temperature: " + temperature);
    }
}

public class StatisticsDisplay implements Observer {
    private double temperature;
    
    public StatisticsDisplay(Subject subject) {
        subject.registerObserver(this);
    }
    
    @Override
    public void update() {
        WeatherData weatherData = (WeatherData) subject;
        this.temperature = weatherData.getTemperature();
        display();
    }
    
    public void display() {
        System.out.println("Max temperature: " + temperature);
    }
}

public class Main {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        
        weatherData.setTemperature(25.5);
        weatherData.setTemperature(30.1);
    }
}

위의 예시 코드에서는 Observer 인터페이스를 정의하고, 이 인터페이스를 구현하는 CurrentConditionsDisplay와 StatisticsDisplay 클래스를 생성합니다. Subject 인터페이스는 옵저버들을 관리하기 위한 메소드들을 정의하며, WeatherData 클래스가 이를 구현합니다. Main 클래스에서는 WeatherData의 온도가 변경될 때마다 등록된 옵저버들에게 알립니다.


스트래티지 패턴

스트래티지 패턴은 알고리즘을 정의하고, 각각을 캡슐화하여 서로 교환 가능하게 만드는 패턴입니다. 이를 통해 알고리즘의 변경이나 확장에 유연하게 대처할 수 있습니다.

스트래티지 패턴의 구현

스트래티지 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface SortingStrategy {
    void sort(int[] array);
}

public class BubbleSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // 버블 정렬 알고리즘 구현
    }
}

public class QuickSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // 퀵 정렬 알고리즘 구현
    }
}

public class SortingContext {
    private SortingStrategy sortingStrategy;
    
    public void setSortingStrategy(SortingStrategy sortingStrategy) {
        this.sortingStrategy = sortingStrategy;
    }
    
    public void sort(int[] array) {
        sortingStrategy.sort(array);
    }
}

public class Main {
    public static void main(String[] args) {
        SortingContext sortingContext = new SortingContext();
        int[] array = {5, 2, 7, 1, 9};
        
        sortingContext.setSortingStrategy(new BubbleSortStrategy());
        sortingContext.sort(array);
        
        sortingContext.setSortingStrategy(new QuickSortStrategy());
        sortingContext.sort(array);
    }
}

위의 예시 코드에서는 SortingStrategy 인터페이스를 정의하고, 이를 구현하는 BubbleSortStrategy와 QuickSortStrategy 클래스를 생성합니다. SortingContext 클래스는 SortingStrategy를 사용하여 배열을 정렬합니다. Main 클래스에서는 SortingContext를 생성하고, 배열을 버블 정렬로 정렬한 후 퀵 정렬로 다시 정렬합니다.


템플릿 메서드 패턴

템플릿 메서드 패턴은 상위 클래스에 알고리즘의 뼈대를 정의하고, 하위 클래스에서 알고리즘의 구체적인 내용을 구현하는 패턴입니다. 이를 통해 알고리즘의 일부분을 변경하거나 확장할 수 있으며, 코드의 중복을 줄일 수 있습니다.

템플릿 메서드 패턴의 구현

템플릿 메서드 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public abstract class AbstractClass {
    public void templateMethod() {
        operation1();
        operation2();
        hookMethod();
    }
    
    public void operation1() {
        // 기본 구현
    }
    
    public abstract void operation2();
    
    public void hookMethod() {
        // 훅 메서드, 필요에 따라 하위 클래스에서 오버라이딩 가능
    }
}

public class ConcreteClass1 extends AbstractClass {
    @Override
    public void operation2() {
        // 구체적인 동작 구현
    }
}

public class ConcreteClass2 extends AbstractClass {
    @Override
    public void operation2() {
        // 다른 구체적인 동작 구현
    }
}

public class Main {
    public static void main(String[] args) {
        AbstractClass abstractClass1 = new ConcreteClass1();
        AbstractClass abstractClass2 = new ConcreteClass2();
        
        abstractClass1.templateMethod();
        abstractClass2.templateMethod();
    }
}

위의 예시 코드에서는 AbstractClass를 추상 클래스로 정의하고, templateMethod()를 템플릿 메서드로 구현합니다. operation1()과 hookMethod()는 기본 구현이지만, operation2()는 하위 클래스에서 구체적으로 구현해야 합니다. ConcreteClass1과 ConcreteClass2는 AbstractClass를 상속받아 operation2()를 구체적으로 구현합니다. Main 클래스에서는 AbstractClass의 템플릿 메서드를 호출합니다.


상태 패턴

상태 패턴은 객체의 내부 상태에 따라 동작이 변화하는 것을 관리하는 패턴입니다. 객체의 상태를 클래스로 추상화하고, 각 상태에 따라 다른 동작을 수행할 수 있습니다. 이를 통해 상태 전이를 관리하고 객체의 상태 변화에 따른 동작을 캡슐화할 수 있습니다.

상태 패턴의 구현

상태 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface State {
    void handle();
}

public class ConcreteState1 implements State {
    @Override
    public void handle() {
        // 상태 1에 따른 동작 구현
    }
}

public class ConcreteState2 implements State {
    @Override
    public void handle() {
        // 상태 2에 따른 동작 구현
    }
}

public class Context {
    private State state;
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle();
    }
}

public class Main {
    public static void main(String[] args) {
        Context context = new Context();
        
        State state1 = new ConcreteState1();
        context.setState(state1);
        context.request();
        
        State state2 = new ConcreteState2();
        context.setState(state2);
        context.request();
    }
}

위의 예시 코드에서는 State 인터페이스를 정의하고, 이를 구현하는 ConcreteState1과 ConcreteState2 클래스를 생성합니다. Context 클래스는 현재 상태를 관리하고 request() 메서드를 사용하여 상태에 따른 동작을 수행합니다. Main 클래스에서는 Context 객체를 생성한 후 다양한 상태로 전이하여 동작을 수행합니다.


커맨드 패턴

커맨드 패턴은 요청을 객체의 형태로 캡슐화하여 요청을 수행하는 객체를 변경할 수 있는 패턴입니다. 이를 통해 요청을 호출하는 객체와 요청을 수행하는 객체를 분리할 수 있으며, 요청을 객체로 만들어서 파라미터화하고 연산의 실행을 취소할 수도 있습니다.

커맨드 패턴의 구현

커맨드 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public interface Command {
    void execute();
}

public class ConcreteCommand1 implements Command {
    private Receiver receiver;
    
    public ConcreteCommand1(Receiver receiver) {
        this.receiver = receiver;
    }
    
    @Override
    public void execute() {
        receiver.action1();
    }
}

public class ConcreteCommand2 implements Command {
    private Receiver receiver;
    
    public ConcreteCommand2(Receiver receiver) {
        this.receiver = receiver;
    }
    
    @Override
    public void execute() {
        receiver.action2();
    }
}

public class Receiver {
    public void action1() {
        // 동작 1 실행
    }
    
    public void action2() {
        // 동작 2 실행
    }
}

public class Invoker {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void executeCommand() {
        command.execute();
    }
}

public class Main {
    public static void main(String[] args) {
        Receiver receiver = new Receiver();
        
        Command command1 = new ConcreteCommand1(receiver);
        Command command2 = new ConcreteCommand2(receiver);
        
        Invoker invoker = new Invoker();
        invoker.setCommand(command1);
        invoker.executeCommand();
        
        invoker.setCommand(command2);
        invoker.executeCommand();
    }
}

위의 예시 코드에서는 Command 인터페이스를 정의하고, 이를 구현하는 ConcreteCommand1과 ConcreteCommand2 클래스를 생성합니다. Receiver 클래스는 실제 요청을 수행하는 역할을 담당합니다. Invoker 클래스는 요청을 호출하는 객체로써, setCommand() 메서드로 요청을 받고 executeCommand() 메서드로 요청을 실행합니다. Main 클래스에서는 Receiver 객체와 다양한 Command 객체를 생성하여 실행합니다.


체인 오브 리스폰더빌리티 패턴

체인 오브 리스폰더빌리티 패턴은 요청을 처리할 수 있는 객체를 체인 형태로 연결하여 처리하는 패턴입니다. 요청을 처리하기 위해 체인에 있는 객체가 차례로 시도하며, 요청을 처리할 수 있는 객체를 발견하면 해당 객체가 처리하고 체인을 종료합니다. 이를 통해 요청을 보내는 객체와 처리하는 객체를 분리하고, 동적으로 처리자를 지정할 수 있습니다.

체인 오브 리스폰더빌리티 패턴의 구현

체인 오브 리스폰더빌리티 패턴은 다음과 같은 구현 방법으로 사용될 수 있습니다. 예시 코드는 다음과 같습니다.


public abstract class Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public void handleRequest(Request request) {
        if (canHandle(request)) {
            processRequest(request);
        } else if (next != null) {
            next.handleRequest(request);
        } else {
            throw new RuntimeException("No handler found for request");
        }
    }

    protected abstract boolean canHandle(Request request);

    protected abstract void processRequest(Request request);
}

public class ConcreteHandler1 extends Handler {
    @Override
    protected boolean canHandle(Request request) {
        // 요청을 처리할 수 있는지 확인하는 로직
    }

    @Override
    protected void processRequest(Request request) {
        // 요청 처리 로직 구현
    }
}

public class ConcreteHandler2 extends Handler {
    @Override
    protected boolean canHandle(Request request) {
        // 요청을 처리할 수 있는지 확인하는 로직
    }

    @Override
    protected void processRequest(Request request) {
        // 요청 처리 로직 구현
    }
}

public class Request {
    // 요청 정보를 담고 있는 클래스
}

public class Client {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();

        handler1.setNext(handler2);

        Request request = new Request();

        handler1.handleRequest(request);
    }
}

위의 예시 코드에서는 Handler 추상 클래스를 정의하고, 이를 구현하는 ConcreteHandler1과 ConcreteHandler2 클래스를 생성합니다. Request 클래스는 요청 정보를 담고 있는 클래스입니다. Client 클래스에서는 Handler 객체들을 생성하고 setNext() 메서드로 체인을 구성합니다. 이후 handleRequest() 메서드를 호출하여 체인을 따라 요청을 처리합니다.


Leave a Comment