[CS] 디자인 패턴 - Abstract Factory, Factory Method, Adapter, Mediator, Chain of Responsibility

Sungjin Cho·2025년 3월 21일
1

CS

목록 보기
7/9
post-thumbnail

Abstract Factory (추상 팩토리)

설명

추상 팩토리 패턴은 서로 관련된 객체들(예: 버튼, 텍스트 박스)을 한꺼번에 생성하는 방법을 제공한다. 직접 객체를 만들지 않고, Factory라는 중간 단계를 통해 일관된 스타일(예: Windows 스타일, Mac 스타일)의 객체를 만들 수 있다.

예제

  1. 적용되지 않은 코드
public class Client {
    public static void main(String[] args) {
        Button windowsButton = new WindowsButton();
        TextBox windowsTextBox = new WindowsTextBox();
        
        windowsButton.render();
        windowsTextBox.render();
    }
}

class WindowsButton {
    public void render() {
        System.out.println("Rendering a Windows Button");
    }
}

class WindowsTextBox {
    public void render() {
        System.out.println("Rendering a Windows TextBox");
    }
}

문제점: 클라이언트 코드에서 WindowsButtonWindowsTextBox를 직접 생성한다.
먄약 Mac 스타일로 바꾸고 싶다면, 클라이언트 코드를 전부 수정해야 한다. 즉, 코드가 특정 스타일(Windows)에 너무 의존적이다.

  1. 패턴이 적용된 코드
interface GUIFactory {
    Button createButton();
    TextBox createTextBox();
}

class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    public TextBox createTextBox() {
        return new WindowsTextBox();
    }
}

interface Button {
    void render();
}

interface TextBox {
    void render();
}

class WindowsButton implements Button {
    public void render() {
        System.out.println("Rendering a Windows Button");
    }
}

class WindowsTextBox implements TextBox {
    public void render() {
        System.out.println("Rendering a Windows TextBox");
    }
}

public class Client {
    public static void main(String[] args) {
        GUIFactory factory = new WindowsFactory();
        Button button = factory.createButton();
        TextBox textBox = factory.createTextBox();

        button.render();
        textBox.render();
    }
}
  • 수정 방법:
    1. GUIFactory 라는 인터페이스를 만들어 버튼과 텍스트 박스를 생성하는 규칙 정의
    2. WindowsFactory는 이 규칙을 따라 Windows 스타일의 객체를 만든다.
    3. 클라이언트는 이제 특정 클래스(WindowsButton)가 아닌 factory 를 통해 객체를 받는다.
  • 장점: Mac 스타일로 바꾸고 싶다면 WindowsFactory 대신 MacFactory를 사용하면 된다. 클라이언트 코드는 수정할 필요가 없다.

Factory Method (팩토리 메서드)

설명

팩토리 메서드 패턴은 객체를 만들 때, 어떤 객체를 만들지 결정하는 책임을 생성자 클래스에 맡긴다. 클라이언트는 객체를 직접 만들지 않고, 생성자 클래스에게 만들어 달라고 요청한다.

  1. 적용되지 않은 코드
public class Client {
    public static void main(String[] args) {
        Product product = new ConcreteProduct();
        product.use();
    }
}

class Product {
    public void use() {
        System.out.println("Using Product");
    }
}

class ConcreteProduct extends Product {
    public void use() {
        System.out.println("Using Concrete Product");
    }
}

문제점: 클라이언트가 직접 ConcreteProduct를 생성한다. 만약 다른 종류의 제품을 만들고 싶다면 클라이언트 코드를 수정해야 한다.

  1. 적용된 코드
interface Product {
    void use();
}

class ConcreteProduct implements Product {
    public void use() {
        System.out.println("Using Concrete Product");
    }
}

abstract class Creator {
    abstract Product createProduct();
    
    public void someOperation() {
        Product product = createProduct();
        product.use();
    }
}

class ConcreteCreator extends Creator {
    Product createProduct() {
        return new ConcreteProduct();
    }
}

public class Client {
    public static void main(String[] args) {
        Creator creator = new ConcreteCreator();
        creator.someOperation();
    }
}
  • 수정 방법:
    1. Creator라는 추상 클래스를 만들어 createProduct 메서드를 정의했다.
    2. ConcreteCreator가 실제로 어떤 제품(ConcreteProduct)을 만들지 결졍한다.
    3. 클라이언트는 Creator에게만 요청하고, 구체적인 제품은 신경쓰지 않는다.
  • 장점: 다른 제품을 만들고 싶다면 ConcreteCreator만 바꾸면 된다.

Adapter (어댑터)

설명

어댑터 패턴은 서로 맞지 않는 두 시스템을 연결해주는 중간자 역할을 한다. 예를 들어, 오래된 시스템을 새 시스템에서 사용하려면 어댑터가 필요하다.

  1. 적용되지 않은 코드
public class Client {
    public static void main(String[] args) {
        OldSystem oldSystem = new OldSystem();
        oldSystem.oldRequest(); // 새 시스템과 맞지 않음
    }
}

class OldSystem {
    public void oldRequest() {
        System.out.println("Old System Request");
    }
}

문제점: OldSystemoldRequest라는 메서드를 사용하지만, 새 시스템은 request라는 메서드를 기대한다. 클라이언트가 직접 사용하려면 코드가 복잡해질 것이다.

  1. 적용된 코드
interface NewSystem {
    void request();
}

class OldSystem {
    public void oldRequest() {
        System.out.println("Old System Request");
    }
}

class Adapter implements NewSystem {
    private OldSystem oldSystem;

    public Adapter(OldSystem oldSystem) {
        this.oldSystem = oldSystem;
    }

    public void request() {
        oldSystem.oldRequest();
    }
}

public class Client {
    public static void main(String[] args) {
        OldSystem oldSystem = new OldSystem();
        NewSystem adaptedSystem = new Adapter(oldSystem);
        adaptedSystem.request();
    }
}
  • 수정 방법:
    1. NewSystem 인터페이스를 만들어 새 시스템의 규칙을 정의했다.
    2. Adapter 클래스가 OldSystem을 감싸고, request 호출을 oldRequest로 변환한다.
  • 장점: 클라이언트는 NewSystem 인터페이스만 알면 되고, 오래된 시스템을 쉽게 재사용 할 수 있다.

Mediator (중재자)

설명

Mediator 패턴은 객체들이 서로 직접 대화하지 않고, “중재자”를 통해 소통하게 한다. 객체 간의 복잡한 연결을 줄여준다.

  1. 적용되지 않은 코드
class ColleagueA {
    ColleagueB colleagueB;

    public void setColleagueB(ColleagueB colleagueB) {
        this.colleagueB = colleagueB;
    }

    public void doSomething() {
        System.out.println("ColleagueA is doing something");
        colleagueB.action();
    }
}

class ColleagueB {
    public void action() {
        System.out.println("ColleagueB is reacting");
    }
}

public class Client {
    public static void main(String[] args) {
        ColleagueA a = new ColleagueA();
        ColleagueB b = new ColleagueB();
        a.setColleagueB(b);
        a.doSomething();
    }
}

문제점: ColleagueAColleagueB를 직접 참조하고 호출한다. 객체들이 서로 얽혀 있어서, 하나를 수정하면 다른 것도 영향을 받는다.

  1. 적용된 코드
interface Mediator {
    void notify(Colleague colleague, String event);
}

class ConcreteMediator implements Mediator {
    private ColleagueA colleagueA;
    private ColleagueB colleagueB;

    public void setColleagueA(ColleagueA colleagueA) {
        this.colleagueA = colleagueA;
    }

    public void setColleagueB(ColleagueB colleagueB) {
        this.colleagueB = colleagueB;
    }

    public void notify(Colleague colleague, String event) {
        if (colleague == colleagueA) {
            colleagueB.action();
        }
    }
}

abstract class Colleague {
    protected Mediator mediator;

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }
}

class ColleagueA extends Colleague {
    public ColleagueA(Mediator mediator) {
        super(mediator);
    }

    public void doSomething() {
        System.out.println("ColleagueA is doing something");
        mediator.notify(this, "A");
    }
}

class ColleagueB extends Colleague {
    public ColleagueB(Mediator mediator) {
        super(mediator);
    }

    public void action() {
        System.out.println("ColleagueB is reacting");
    }
}

public class Client {
    public static void main(String[] args) {
        ConcreteMediator mediator = new ConcreteMediator();
        ColleagueA a = new ColleagueA(mediator);
        ColleagueB b = new ColleagueB(mediator);

        mediator.setColleagueA(a);
        mediator.setColleagueB(b);

        a.doSomething();
    }
}
  • 수정 방법:
    1. Mediator 인터페이스를 통해 중재자를 정의하고, ConcreteMediator가 객체 간 소통을 관리한다.
    2. ColleagueAColleagueB는 서로 직접 참조하지 않고, mediator를 통해 메시지를 전달한다.
  • 장점: 객체 간 연결이 단순해지고, 중재자만 수정하면 동작을 쉽게 바꿀 수 있다.

Chain of Responsibility (책임 연쇄)

설명

Chain of Responsibility 패턴은 요청을 처리할 수 있는 객체들을 줄줄이 연결해서, 요청이 처리될 때까지 순서대로 전달한다. 요청을 누가 처리할지 미리 정하지 않는다.

  1. 적용되지 않은 코드
public class Client {
    public static void main(String[] args) {
        Handler handler = new Handler();
        handler.handleRequest(5);
    }
}

class Handler {
    public void handleRequest(int request) {
        if (request > 0) {
            System.out.println("Request handled");
        } else {
            System.out.println("Request not handled");
        }
    }
}

문제점: 하나의 Handler가 모든 요청을 처리한다. 요청 종류가 많아지면 코드가 복잡해지고, 새로운 조건을 추가하려면 Handler를 계속 수정해야 한다.

  1. 적용된 코드
abstract class Handler {
    protected Handler nextHandler;

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

    abstract void handleRequest(int request);
}

class PositiveHandler extends Handler {
    void handleRequest(int request) {
        if (request > 0) {
            System.out.println("PositiveHandler: Request handled");
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

class NegativeHandler extends Handler {
    void handleRequest(int request) {
        if (request < 0) {
            System.out.println("NegativeHandler: Request handled");
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        Handler positiveHandler = new PositiveHandler();
        Handler negativeHandler = new NegativeHandler();

        positiveHandler.setNext(negativeHandler);

        positiveHandler.handleRequest(5);  // PositiveHandler 처리
        positiveHandler.handleRequest(-3); // NegativeHandler 처리
    }
}
  • 수정 방법:
    1. Handler라는 추상 클래스를 만들어 체인의 기본 구조를 정의했다.
    2. PositiveHandlerNegative가 각각 양수와 음수 요청을 처리하고, 처리할 수 없으면 다음 핸들러로 넘긴다.
  • 장점: 새로운 처리 조건을 추가하려면 새 핸들러를 체인에 추가하기만 하면 된다.
// Handler 추상 클래스
abstract class Handler {
    protected Handler nextHandler;

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

    abstract void handleRequest(int request);
}

// 양수를 처리하는 핸들러
class PositiveHandler extends Handler {
    void handleRequest(int request) {
        if (request > 0) {
            System.out.println("PositiveHandler: Handling " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.println("PositiveHandler: No handler available for " + request);
        }
    }
}

// 음수를 처리하는 핸들러
class NegativeHandler extends Handler {
    void handleRequest(int request) {
        if (request < 0) {
            System.out.println("NegativeHandler: Handling " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.println("NegativeHandler: No handler available for " + request);
        }
    }
}

// 새로 추가된 핸들러: 0을 처리
class ZeroHandler extends Handler {
    void handleRequest(int request) {
        if (request == 0) {
            System.out.println("ZeroHandler: Handling " + request);
        } else if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } else {
            System.out.println("ZeroHandler: No handler available for " + request);
        }
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        // 핸들러 객체 생성
        Handler positiveHandler = new PositiveHandler();
        Handler negativeHandler = new NegativeHandler();
        Handler zeroHandler = new ZeroHandler();

        // 체인 구성: positive -> negative -> zero
        positiveHandler.setNext(negativeHandler);
        negativeHandler.setNext(zeroHandler);

        // 다양한 요청 테스트
        System.out.println("Request: 5");
        positiveHandler.handleRequest(5);  // PositiveHandler가 처리

        System.out.println("\nRequest: -3");
        positiveHandler.handleRequest(-3); // NegativeHandler가 처리

        System.out.println("\nRequest: 0");
        positiveHandler.handleRequest(0);  // ZeroHandler가 처리

        System.out.println("\nRequest: 10");
        positiveHandler.handleRequest(10); // PositiveHandler가 처리
    }
}
  • Handler 추가 예시
    1. 예를 들어 0에 대한 처리를 하고 싶다면 ZeroHandler를 추가하고 체인에 추가(setNext)하면 된다.

0개의 댓글