[2일차] Observer, Proxy, Iterator 패턴

sani·2023년 6월 8일

CS스터디

목록 보기
2/7

🧐 들어가기

  • 이번에도 디자인 패턴들에 대해 다룰 것이다. 디자인 패턴은 결국 경험적으로 발생한 문제들을 방지할 수 있는 코드 패턴인데, 1일차에서는 어떠한 문제들을 해결하기위한 것인지 주로 다루지 못했다. 이번에는 각 패턴들을 왜 쓰는지, 쓰지 않으면 어떤 문제가 발생하는지에 대해 더 집중해볼 것이다.

1️⃣ Observer 패턴

  • 객체의 상태 변화를 관찰하는 Observer들의 목록을 객체에 등록. event 발생할 때마다 notify를 통해 객체의 observer들에게 통지하는 패턴.
  • 객체 간의 1:N 의존 관계를 구현하는데에 사용.
  • 분산 이벤트 핸들링 시스템을 구현.
  • pub/sub model로도 알려져 있으나 차이 존재.

1-1. 필요성

  • 객체간의 느슨한 결합을 구현하기위해 고안된 패턴으로 Subject(관찰 대상) 객체는 Observer(관찰자) 객체에 대한 정보를 알지 못하더라도 observer들에게 자신의 상태 변화를 통지할 수 있다. 이러한 느슨한 결합을 사용하지 않는다면, 다음과 같은 문제가 발생할 수 있다.
  • 직접 의존성 : subject가 자신의 상태를 통지하려면 의존해야하는데, 직접 의존으로 구현하면 객체의 유연성과 재사용성이 떨어질 수 있다. 새로운 관찰자를 추가하거나 기존의 관찰자를 제거하려하면 subject 객체의 코드를 변경해야하기때문이다.
  • 일관성 문제 : subject가 자신의 상태변화를 모든 observer들에게 업데이트할때, 일관성이 깨질 수 있지만, observer 패턴은 subject가 observer들의 리스트를 갖고있고 각 observer마다 업데이트 함수를 호출함으로써 일관성을 보장할 수 있다.
  • 코드 복잡성 : 상태변화별로 다양한 event들을 처리해야하는데, observer 마다 각각 필요한 event를 처리하게하면 코드 가독성과 유지 보수성이 높아진다.
  • 따라서 Observer Pattern을 통해 객체간의 결합도를 낮추고, 유연성을 통한 코드 품질을 향상시킬 수 있다.

1-2. Java 예시

  • Java에서는 interface를 통해 Observer 패턴을 구현할 수 있다. subject 객체는 observer를 등록/해제하는 매서드를, observe 객체는 상태를
  • 주식 시장에서 특정 주식의 가격 변동을 여러명이 관찰한다고 가정하자. 그럼 이 주식의 변화를 관찰하는 Observer가 있고, Subject의 상태 변화가 발생하면, observer들에게 통지가 가야한다.
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

public interface Observer {
    void update(float price);
}

Subject의 구현체는 이제 observer들의 리스트를 갖게되고 등록된 observer들에게 해당 주식 가격의 변동을 notifyObservers()를 통해 알리게 된다.

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

public class StockMarket implements Subject {
    private List<Observer> observers;
    private float price;

    public StockMarket() {
        observers = new ArrayList<>();
    }

    @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(price);
        }
    }

    public void setPrice(float price) {
        this.price = price;
        notifyObservers();
    }
}

observer의 구현체는 notify 받은 상태변화에 따라 각자의 기준에 맞게 notify 받은 변화를 처리하게 된다.

public class Investor implements Observer {
    private float threshold;

    public Investor(float threshold) {
        this.threshold = threshold;
    }

    @Override
    public void update(float price) {
        if (price > threshold) {
            System.out.println("매도해야 합니다!!!");
        } else {
            System.out.println("존버하십시오.");
		}
    }
}

1-3. Pub/Sub pattern과의 비교

  • 'Head First Design Patterns' 책에서는 Observer pattern과 Pub/Sub pattern이 동일한 것으로 나와있다고 한다. 그러나 그 차이점이 명확하게 존재한다. 바로 Message Broker나 Event Channel과 같은 미들웨어의 존재 여부이다. Pub/Sub pattern의 경우는 위와 같이 중간 매개체로 Channel이 존재하고, 이를 구독하는 이들은 publisher가 아닌, 매개체를 reference하고 있다. 따라서 아래와 같은 차이점이 발생하게 된다.
  • 객체간 결합도 : subject가 observer들로 하여금 함수를 호출시키는 방법과 달리, Pub/Sub의 경우는 Broker에 던져놓거나 모니터링하면 되기에 서로를 알 필요가 없다. 따라서 객체간 결합도가 더 낮다.
  • 동기/비동기 방식 : Pub/Sub 패턴은 Message Broker를 이용하므로 event를 Broker에 보내기만 하면 다른 task를 진행할 수 있다. 따라서 비동기 방식이라는 점에서 Observer 패턴과 차이가 있다.
  • cross domain 허용 : Pub/Sub 패턴은 Broker라는 중간 매개체만 참조하면 되기에 다른 domain의 application에서 접근해 처리하는것이 가능하다.
  • 부가적으로 Pub/Sub pattern은 Broker와 같은 매개체를 이용하므로 직접 event를 전달하는 Observer 패턴과는 달리 event가 의도한대로 전달되지 못할 가능성이 존재한다.

2️⃣ Proxy 패턴

  • 해당 객체를 참조할때, 직접 참조하지 않고 그 객체의 proxy(대행) 객체를 참조함으로써 객체에 접근하는 방식.

2-1. 필요성

  • 서비스 접근 흐름 제어 : proxy가 해당 객체 접근에 대한 모든 요청들을 처리하므로, 요청을 최대한 비동기적으로 처리할 수 있다. 예를 들어 client의 많은 요청으로 인해 DB 부하가 우려된다면, 이들의 흐름을 제어하여 적당한 traffic이 DB에 전해지도록 제어가 필요하다. 이때 proxy 객체에서 이를 제어해줄 수 있다.
  • lazy initialization : 객체 생성 cost가 높다면, 일단 proxy를 참조하게 함으로써 해당 객체의 생성 시점을 미룰 수 있다.
  • 권한 확인 : 객체가 민감한 정보를 담고 있다면 client가 접근할 권한이 있는지 확인해야하는데 proxy에서 이를 확인해줌으로써 보안을 유지할 수 있다.
  • 코드 수정 최소화 : 해당 메소드의 실행에 필요한 전처리 기능들과 같이 수정이 필요할 경우, proxy 객체를 수정함으로써 구현 객체 변경없이 기능 수정이 가능하다.

2-2. Java 예시

아래 코드를 통해, 실제 객체의 생성은 객체가 displayImage()라는 실제 기능을 할때까지 지연되고, 실제 메서드 실행도 proxy 객체를 통해 진행된다.

public interface Image {
   void displayImage();
}
public class Real_Image implements Image {

    private String fileName;
    
    public Real_Image(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }
    
    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }
    
    @Override
    public void displayImage() {
        System.out.println("Displaying " + fileName);
    }
}

public class Proxy_Image implements Image {
    private Real_Image realImage;
    private String fileName;
    
    public Proxy_Image(String fileName) {
        this.fileName = fileName;
    }
    
    @Override
    public void displayImage() {
        if (realImage == null) {
            realImage = new Real_Image(fileName);
        }
        realImage.displayImage();
    }
}

public class Proxy_Main {
    public static void main(String[] args) {
        Image image1 = new Proxy_Image("test1.png");
        Image image2 = new Proxy_Image("test2.png");
        
        image1.displayImage();
        System.out.println();
        image2.displayImage();
    }
}

3️⃣ Iterator 패턴

  • 자료구조에 대한 접근기능실제 자료구조 구현부를 분리시켜 객체화한다. 따라서 서로 다른 자료구조 구현부를 갖고 있더라도 동일한 interface를 통해 접근기능이 구현됐다면, 각각의 element에 대해 동일한 방식으로 접근이 가능하다.

3-1. 필요성

  • 접근 방식의 표준화 : Collection의 구현 방법을 노출시키지 않으면서도, 어떤 종류의 집합체에 대해서도 element들을 조회할 수 있는 다형적인 코드를 만들 수 있다.

3-2. Java 예시

public interface Collection<E> extends Iterable<E> {
    // Query Operations
 
    /**
     * Returns the number of elements in this collection.  If this collection
     * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
     * <tt>Integer.MAX_VALUE</tt>.
     *
     * @return the number of elements in this collection
     */
     
public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();
 

Java의 Collection들의 경우, 모두 Iterable을 상위 interface로 갖고 있기에 iterator()라는 메소드를 구현해야 한다. 해당 메소드의 코드는 다음과 같다.

public interface Iterator<E> {
    /**
     * Returns {@code true} if the iteration has more elements.
     * (In other words, returns {@code true} if {@link #next} would
     * return an element rather than throwing an exception.)
     *
     * @return {@code true} if the iteration has more elements
     */
    boolean hasNext();
 
    /**
     * Returns the next element in the iteration.
     *
     * @return the next element in the iteration
     * @throws NoSuchElementException if the iteration has no more elements
     */
    E next();
 
    /**
     * Removes from the underlying collection the last element returned
     * by this iterator (optional operation).  This method can be called
     * only once per call to {@link #next}.  The behavior of an iterator
     * is unspecified if the underlying collection is modified while the
     * iteration is in progress in any way other than by calling this
     * method.
     *
     * @implSpec
     * The default implementation throws an instance of
     * {@link UnsupportedOperationException} and performs no other action.
     *
     * @throws UnsupportedOperationException if the {@code remove}
     *         operation is not supported by this iterator
     *
     * @throws IllegalStateException if the {@code next} method has not
     *         yet been called, or the {@code remove} method has already
     *         been called after the last call to the {@code next}
     *         method
     */
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

hasNext(), next(), remove() 등의 메소드들을 통해 Collection class의 element들을 조회하는 방식을 표준화할 수 있다.


출처

https://zerocodings.com/22
https://jistol.github.io/software%20engineering/2018/04/11/observer-pubsub-pattern/
https://velog.io/@minsuk/Publish-Subscribe-패턴알림
https://coding-factory.tistory.com/711
https://esoongan.tistory.com/180
https://devlog-wjdrbs96.tistory.com/84
https://jusungpark.tistory.com/25

profile
블로그 이전했습니다. https://devsan.tistory.com/

0개의 댓글