디자인 패턴에 관하여

지니🧸·2024년 10월 9일
2

CS 저장소

목록 보기
47/48

디자인 패턴이란?

디자인 패턴은 개발하면서 반복적으로 발생하는 문제들에 대한 해결책을 제시한다.

선배 개발자도 현재 우리가 만난 문제와 똑같거나 비슷한 문제를 겪고, 해결하는 과정에서 이를 패턴으로 만들어간 것이다.

디자인 패턴의 분류

디자인 패턴은 용도에 따라 분류할 수 있다: 생성, 행동, 구조

생성 패턴 (Creational Pattern)

객체 인스턴스를 생성하는 패턴.

(ex) Singleton, Factory, Prototype, Builder etc.

1. 싱글턴 패턴 (Singleton Pattern)

특정 클래스에 객체 인스턴스가 하나만 만들어지도록 하는 패턴

전역 변수 쓰듯이 이 객체 인스턴스를 어디서든지 접근할 수 있도록 메서드를 제공한다

생성자의 접근자를 private로 제한하고, 클래스 내에서 private static한 객체를 하나 생성한 다음, 외부에서 이 객체에 접근할 수 있는 getter 메서드를 제공한다.

public class HouseDaoImpl {
	private static HouseDaoImpl impl = new HouseDaoImpl();
    
    private HouseDaoImpl() {}
    
    public static HouseDaoImpl getImpl() { return impl; }
    
    ...

}

용도

  • 공유 자원(데이터베이스 등)에 대한 접근 제한 등을 위해 클래스의 인스턴스 수를 하나로 제한할 때

장점

  • 클래스가 하나의 인스턴스만 갖도록 한다
  • 단일 인스턴스에 대한 전역 접근 지점을 제공한다

단점

  • 다중 스레드 환경에서 여러 스레드가 싱글턴 객체를 여러 번 생성하지 않도록 하기 위해서는 별도의 처리가 필요하다
  • 유닛 테스트하기 까다로울 수 있다

2. 추상 팩토리 패턴 (Abstract Factory Pattern)

구상(implementation) 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공한다.

추상 팩토리 패턴의 요소

  • 추상 제품: 제품군를 구성하는 각 연관 제품 집합에 대한 인터페이스
    • (예) Char 인터페이스는 VictorianChair, ModernChair 구상클래스에 의해 구현된다
  • 구상 제품: 추상 제품의 구현
  • 추상 팩토리 인터페이스: 각각의 추상 제품을 생성하기 위한 여러 메서들의 집합을 선언한다
  • 구상 팩토리들: 추상 팩토리의 생성 메서드를 구현한다
    • 각 구상 팩토리는 제품들의 특정 변형들에 해당한다 => 해당 특정 변형들만 생성한다
    • (예) VictorianFactoryVictorianChair, VictorianTable 등만 생성한다
  • 구상 팩토리들은 구상 제품들을 인스턴스화하지만, 그 제품들의 생성 메서드들의 시그니처들은 그에 해당하는 추상제품들을 반환해야 한다
    • 왜냐? 그래야지 팩토리를 사용하는 클라이언트 코드가 팩토리에서 받은 제품의 특정 변형과 결합되지 않는다
    • 클라이언트는 추상 인터페이스를 통해 팩토리/제품 변형의 객체들과 소통하는 한 그 어떤 구상 팩토리/제품 변형과 작업할 수 있다

용도

  • 한 제품군의 다양한 패밀리와 상호작용하되, 확장성을 위해서 각 제품의 구상 클래스에 의존하고 싶지 않을 때

장점

  • 팩토리에서 생성되는 제품들의 상호 호환을 보장한다
  • 구상 제품들과 클라이언트 코드 간 결합도를 낮춘다
  • 단일 책임 원칙: 제품 생성 코드를 한 곳으로 추출하여 코드를 쉽게 유지보수할 수 있다
  • 개방 폐쇠 원칙: 기존 클라이언트 코드를 훼손하지 않고 제품의 새로운 변형들을 생성할 수 있다

단점

  • 패턴과 함께 여러 인터페이스와 구상 클래스를 도입하기 때문에 코드가 복잡해질 수 있다

3. 빌더 패턴 (Builder Pattern)

빌더 패턴의 구성 요소

  • 빌더 인터페이스: 모든 유형의 빌더들에 공통적인 제품 생성 단계들을 선언한다
  • 구상 빌더: 생성 단계들의 다양한 구현을 제공한다
    • 공통 인터페이스를 따르지 않는 제품들도 생산할 수 있다 (필드 하나는 포함하지 않는다던지)
  • 제품: 구상 빌더의 결과로 만들어진 객체들
  • 디렉터 클래스: 생성 단계들을 호출하는 순서를 정의한다
    • 필수적이지는 않다

용도

  • 점층적 생성자를 제거한다
  • 일부 제품의 다른 표현들을 생성하고 싶을 때

점층적 생성자란 다음과 같다

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    ...

Spring의 @Builder

  • 빌더 패턴이 객체의 단계적인 생성을 지원한다
  • 클래스에 대해@Builder 어노테이션을 선언하면 Lombok이 해당 클래스에 대한 빌더 클래스를 자동으로 생성한다
import lombok.Builder;

@Builder
public class User {
    private String name;
    private int age;
}

...

User user = User.builder()
                .name("Alice")
                .age(30)
                .build();

행동 패턴 (Behavioral Pattern)

클래스와 객체들이 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴.

(ex) Template method, Singleton, Observer, State, Visitor, etc.

1. 옵저버 패턴 (Observer Pattern)

하나의 객체에 발생하는 모든 이벤트에 대하여 여러 객체에 이를 알리는 구독 메커니즘을 정의한다

옵저버 패턴의 구성 요소

  • 주제(subject): 시간에 따라 변경될 수 있는 주요한 상태를 가진 객체
    • 구독자 객체들에 대한 참조 리스트를 저장한다 (배열 등)
    • 그 리스트에 구독자를 추가하거나 제거할 수 있는 public 메서드를 갖는다

용도

  • event-driven 프로그래밍 (예) 메시지 알림 등
  • 한 객체의 상태가 타 객체들에게 알려져야 할 때

장점

  • 개방 폐쇄 원칙: 주제 객체 코드를 변경하지 않고도 새 구독자 클래스를 도입할 수 있다
    • 구독자 클래스들은 공통 인터페이스를 구현한다고 가정한다

단점

  • 구독자는 무작위로 알림을 받는다

구조 패턴 (Structural Pattern)

구조를 유연하고 효율적으로 유지하면서, 더 큰 구조로 조립하는 방법을 다루는 패턴.

(ex) Adapter, Proxy, Decorator, Composite, etc.

1. 데코레이터 패턴 (Decorator Pattern)

객체의 구조를 건들 필요 없이 객체에 새 행위를 추가한다.

장점

  • 클래스의 기능을 유연하게 확장하고 싶을 때
  • 해당 클래스와 연관된 타 클래스에 영향을 주지 않으면서, 해당 클래스를 확장하고 싶을 때
  • 개방 폐쇄 원칙
    • 기존 클래스는 수정되지 않는다 => 수정에 닫혀있다
    • decorator 클래스가 기능을 확장한다 => 확장에는 열려있다
  • 단일 책임 원칙: 각 데코레이터 클래스는 한 가지 기능을 추가하는 책임을 다한다
    • 각 클래스를 간단하고, 유지보수하기 용이한 형태로 유지한다
// Base component
public interface Coffee {
    String getDescription();
    double cost();
}

// Concrete component
public class BasicCoffee implements Coffee {
    public String getDescription() {
        return "Basic Coffee";
    }

    public double cost() {
        return 2.0;
    }
}

// Decorator
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    public double cost() {
        return decoratedCoffee.cost();
    }
}

// Concrete decorators
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Milk";
    }

    public double cost() {
        return decoratedCoffee.cost() + 0.5;
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Sugar";
    }

    public double cost() {
        return decoratedCoffee.cost() + 0.2;
    }
}

// Usage
public class CoffeeShop {
    public static void main(String[] args) {
        Coffee coffee = new BasicCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);

        System.out.println(coffee.getDescription());
        System.out.println("Total Cost: " + coffee.cost());
    }
}

참고

profile
우당탕탕

0개의 댓글