디자인 패턴(3)

이태곤·2023년 10월 26일
0

CS

목록 보기
9/23

1. Context

  • Context: 어떤 상황, 환경 또는 상태
    → 특정한 시간 또는 장소에서 일어나는 일 또는 일련의 사건에 대한 맥락을 제공하며, 해당 상황에서 필요한 정보와 상태를 캡슐화하고 저장한다.

  • Contextual Information: 주어진 컨텍스트 내에서 중요한 정보나 데이터
    → 해당 상황을 더 명확하게 이해하고 처리하는 데 도움이 될 수 있다.
    예를 들어, 병원에 가면 이름과 주민등록번호 앞자리가 contextual information이 되고 병원에 방문하는 상황 / 상태가 context가 된다.

  • Context 2가지 의미

    1. 어떤 종류의 상태나 환경을 캡슐화한 것
      → 컨텍스트는 특정한 시간이나 상황에서의 정보와 상태를 저장하고 나중에 그 정보와 상태를 재현하기 위해 사용된다.
      • 예를 들어, 워드 프로세서에서 긴 문서를 작성하고 있는데 갑자기 컴퓨터의 전원이 꺼져버린다.
        이때, 워드 프로세서는 현재 문서의 상태를 컨텍스트로 저장한다.
        → 해당 컨텍스트에는 문서의 내용, 커서 위치, 서식, 스크롤 위치 등이 포함된다.
        다음에 컴퓨터를 다시 켰을 때, 워드 프로세서는 이 컨텍스트 정보를 사용하여 마지막 저장된 지점에서 문서 작업을 계속할 수 있게 해준다.
    2. 작업을 중단하고 나중에 같은 지점에서 계속 진행하기 위해 저장하는 최소 데이터 집합
      → 프로세스 간 컨텍스트 스위치는 CPU가 다른 프로세스로 전환될 때 현재 프로세스의 상태를 저장하고, 나중에 해당 상태에서 작업을 계속할 수 있도록 한다.
  • Context API: React 라이브러리의 일부로, Context API를 사용하면 데이터를 컴포넌트 계층 구조를 통해 전달하지 않고도 컴포넌트 간에 데이터를 공유할 수 있다.


2. 전략 패턴

  • 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 필요에 따라 교체할 수 있게 만드는 디자인 패턴

      1. Context: 컨텍스트는 전략 패턴을 사용하는 주체
      1. Strategy: 전략은 컨텍스트에서 사용하는 다양한 알고리즘을 캡슐화한 인터페이스나 추상 클래스
      1. Concrete Strategy: 컨크리트 전략은 전략 인터페이스를 구현하는 구체적인 클래스
  • 전략 패턴은 컨텍스트와 전략 간의 느슨한 결합을 제공하며, 컨텍스트가 실행 시에 적절한 전략을 선택하고 교체할 수 있게 한다.
    또한, 컨텍스트는 동적으로 다양한 알고리즘을 사용할 수 있으며 코드 재사용과 유지보수성을 개선할 수 있다.

  • 예시

    • PaymentStrategy 인터페이스: PaymentStrategy 인터페이스는 다양한 결제 전략을 정의
    • KAKAO Card 및 LUNA Card 구체적인 전략: KAKAOCardStrategy 및 LUNACardStrategy는 PaymentStrategy 인터페이스를 구현하고, 각각의 결제 방법에 필요한 정보와 동작을 정의
    • ShoppingCart 클래스: PaymentStrategy를 사용하여 다양한 결제 방법을 수용할 수 있으며, pay 메서드가 전략을 사용하여 실제 결제를 수행
    • 메인 메서드: main 메서드에서 실제로 ShoppingCart를 사용하고, 다양한 전략(LUNACardStrategy 및 KAKAOCardStrategy)를 선택하여 결제를 진행
      → 전략 패턴을 통해 다른 결제 방법을 유연하게 사용할 수 있다.
// PaymentStrategy 인터페이스는 구체적인 전략들이 구현해야 하는 pay 메서드를 정의합니다.
interface PaymentStrategy {
    public void pay(int amount);
}

// KAKAO Card에 대한 구체적인 PaymentStrategy
class KAKAOCardStrategy implements PaymentStrategy {
    // ... (생성자 및 필드들)

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using KAKAOCard.");
    }
}

// LUNA Card에 대한 구체적인 PaymentStrategy
class LUNACardStrategy implements PaymentStrategy {
    // ... (생성자 및 필드들)

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using LUNACard.");
    }
}

class Item {
    private String name;
    private int price;

    public Item(String name, int cost) {
        this.name = name;
        this.price = cost;
    }

    public int getPrice() {
        return price;
    }
}

class ShoppingCart {
    List<Item> items;

    public ShoppingCart() {
        this.items = new ArrayList<Item>();
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    public int calculateTotal() {
        int sum = 0;
        for (Item item : items) {
            sum += item.getPrice();
        }
        return sum;
    }

    public void pay(PaymentStrategy paymentMethod) {
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        Item A = new Item("kundolA", 100);
        Item B = new Item("kundolB", 300);
        cart.addItem(A);
        cart.addItem(B);

        // LUNACard로 결제
        cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));

        // KAKAO Card로 결제
        cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
    }
}
  • 전략패턴 VS 의존성 주입
    • 공통점
      • 모듈화와 유연성: 시스템을 모듈화하여 각 모듈이 서로 독립적으로 변경 가능하게 하고 유연성을 높이는 데 중점을 둔다.
    • 차이점
      • 전략패턴: 어떠한 동일한 행동 계약을 기반으로 다양한 구현이 명시되어있는 인터페이스를 만드는 것을 포함
      • 의존성주입: 일부 동작만을 구현하고 의존성을 주입하기만 하는 패턴

3. 옵저버 패턴

  • Observer Pattern은 주체(Subject)와 옵저버(Observer)들 간의 관계를 나타내는 디자인 패턴
    → 트위터의 메인 로직, 그리고 MVC패턴에 적용

    • 주체 객체가 상태 변화를 관찰하다가 상태가 변할 때마다 옵저버 목록에 등록된 옵저버들에게 변화를 알려주는 메커니즘
      • 주체(Subject): 상태를 관찰하는 대상 객체
      • 옵저버(Observer): 상태가 변경되면 주체로부터 통지를 받아 적절한 동작을 수행
  • 예시: 옵저버 패턴을 구현하여 주체(Topic)와 옵저버(TopicSubscriber) 간의 상호작용 보여준다.

// Subject 인터페이스는 옵저버들을 등록, 해지하고, 옵저버들에게 알릴 메서드를 정의
interface Subject {
    public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);
}

// Observer 인터페이스는 주체로부터 업데이트를 받기 위한 update 메서드를 정의
interface Observer {
    public void update();
}

// Topic 클래스는 Subject를 구현하여 주제의 상태를 추적하고 옵저버에게 업데이트를 알림
class Topic implements Subject {
    private List<Observer> observers;
    private String message;

    public Topic() {
        this.observers = new ArrayList<>();
        this.message = "";
    }

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj)) observers.add(obj);
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }

    @Override
    public void notifyObservers() {
        this.observers.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    public void postMessage(String msg) {
        System.out.println("Message sent to Topic: " + msg);
        this.message = msg;
        notifyObservers();
    }
}

// TopicSubscriber 클래스는 Observer를 구현하여 주제의 업데이트를 수신
class TopicSubscriber implements Observer {
    private String name;
    private Subject topic;

    public TopicSubscriber(String name, Subject topic) {
        this.name = name;
        this.topic = topic;
    }

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        System.out.println(name + " received a message: " + msg);
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        Topic topic = new Topic();
        Observer a = new TopicSubscriber("a", topic);
        Observer b = new TopicSubscriber("b", topic);
        Observer c = new TopicSubscriber("c", topic);
        topic.register(a);
        topic.register(b);
        topic.register(c);
        topic.postMessage("Amumu is an OP champion!");
    }
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/

4. 프록시 패턴

  • 객체가 어떤 대상 객체에 접근하기 전, 그 접근에 대한 흐름을 가로채서 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 디자인패턴

  • 다른 객체에 대한 인터페이스를 제공하여 해당 객체에 대한 접근을 제어하거나 중개하는 역할을 하는 객체를 생성하는 데 사용된다.
    → 다양한 상황에서 객체의 동작을 제어하거나 보안, 로깅, 캐싱, 지연 로딩 등을 구현할 때 유용하게 활용 될 수 있다.

  • 예시: 프록시 패턴을 활용하여 이미지 파일이 실제로 필요할 때만 로딩하도록 한다.

// 서브젝트 인터페이스
interface Image {
    void display();
}

// 실제 서브젝트 클래스
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// 프록시 클래스
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

public class ProxyPatternExample {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("image1.jpg");
        Image image2 = new ProxyImage("image2.jpg");

        // 이미지 파일은 실제로 로딩되지 않음
        image1.display();

        // 이미지 파일은 이미 로딩되었으므로 다시 로딩되지 않음
        image1.display();
    }
}
  • 활용 : 서비스 앞단에 프록시 서버로 cloudflare를 둬서 불필요한, 공격적인 트래픽을 차단


5. flux 패턴

  • 데이터 흐름을 단방향으로 관리하는 디자인 패턴

  • 예시

    • 페이스북은 메시지를 읽었는데 다른 페이지에서는 메시지가 안 읽었다고 뜨는 읽은 표시(mark seen)에 대한 기능장애를 경험했다.
      → 뷰가 모델에 영향을, 반대 상황에서도 영향을 미치는 경우가 발생하여 데이터를 일관성 있게 뷰에 공유하기가 어려웠다.
      또한, 뷰와 모델 사이의 양방향 데이터 흐름으로 인한 복잡성으로 인해 버그 파악 및 수정이 어려웠다.
  • 위와 같은 상황에 따라 페이스북에서 flux 패턴을 개발하였다.

    • Action: 사용자의 이벤트 담당하며, 이벤트에 관한 객체를 만들어내 dispatcher에게 전달
      → 마우스 클릭, 글 작성
    • Dispatcher: 들어오는 Action 객체 정보를 기반 어떠한 행위를 할 것인가를 결정하며, action객체의 type를 기반으로 로직을 수행하고 이를 Store에 전달
      → 코드 레벨 수준에서는 swtich로 구현
    • Store: 애플리케이션 상태를 관리하고 저장하며 도메인의 상태, 사용자의 인터페이스 등의 상태 등을 모두 저장
    • View: 데이터를 기반으로 표출이 되는 사용자 인터페이스
  • 장점

    1. 데이터 일관성의 증대: 데이터 흐름이 단방향으로만 흐르기 때문에 데이터 일관성을 높아진다.
    2. 버그를 찾기가 쉬워짐: 데이터 흐름이 단순하고 예측 가능하게 되므로 버그를 찾고 수정하기가 쉬워진다.
    3. 단위테스팅이 쉬워짐: 각 구성 요소(디스패처, 스토어, 뷰)를 독립적으로 테스팅 할 수 있다.
  • Flux 패턴을 사용하여 개발된 상태 관리 라이브러리 Redux

0개의 댓글