ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 옵저버 패턴 (Observer Pattern)
    카테고리 없음 2017. 10. 3. 01:26

    위키백과 =  객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴이다. 


    즉, 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고, 자동으로 내용이 갱신되는 일대다(one-to-many) 의존성을 정의하는 디자인 패턴입니다.



    동작원리

    책에서는 신문이나 잡지를 구독을 예제로 들고 있습니다. 신문을 구독하는 과정을 나열해 보겠습니다.

    1.  신문을 구독 하기 위해 신문사에 구독 신청을 합니다.

    2. 신문사는 새로운 신문이 나오면 구독자에게 배달을 해 줍니다.
    구독을 하는 동안은 계속 해서 신문을 받을 수 있습니다.

    3. 더 이상 신문을 구독하고 싶지 않아서 해지 신청을 한다면, 더 이상 신문이 배달 되지 않습니다.


    이 프로세스를 이해 한다면 옵저버 패턴을 쉽게 이해할 수 있습니다. 옵저버 패턴에서는 신문사를 주제(Subject), 구독자를 옵저버(Observer)라고 부릅니다.





    카카오톡을 쓰시는 분이면 알겠지만, 플러스 친구 라는 기능이 있습니다. 


    플러스친구 목록 중에 소식을 받기를 원하는 기업을 친구 추가 하면, 새로운 소식이 업데이트 될 때 마다 메세지로 받아 볼 수 있습니다. 


    여기서 기업의 플러스 친구 계정은 Subject, 구독자는 Observer가 되겠습니다.


    이 기능을 가지고 옵저버 패턴의 예제를 작성해 보겠습니다.







    Company 클래스는 소식을 전해주는 Subject의 역할을 하므로 Subject 인터페이스를 구현합니다.


    Subscriber와 Subscriber2 클래스는 소식을 전달 받는 구독자 이므로, Observer 인터페이스를 구현합니다. 여기에 화면에 나타나기 위해 display 메소드를 사용해야 하므로 그런 기능을 명세해 놓은 DisplayElement 인터페이스를 함께 구현합니다.


    UML 다이어그램을 잘 모르시는 분들을 위해 좋은 포스팅 링크를 공유합니다.


    링크 보러 가기1


    링크 보러 가기2




    Subject 인터페이스



    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    public interface Subject {
        
        public void registerObserver(Observer o);
        public void removeObserver(Observer o);
        public void notifyObserver();
     
    }
     
    cs



    Observer 인터페이스



    1
    2
    3
    4
    5
    6
    public interface Observer {
     
        public void update(String photoUrl, String content);
        
    }
     
    cs



    DisplayElement 인터페이스



    1
    2
    3
    4
    5
    6
    public interface DisplayElement {
     
        public void display();
        
    }
     
    cs



    Company 클래스


    =소식을 옵저버 들에게 전해주는 Subject의 역할을 합니다. 새로운 이벤트가 있을 때 마다 회사는 이 Company 라는 클래스를 통해서 구독자들에게 소식을 전해줍니다.   


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import java.util.ArrayList;
     
    public class Company implements Subject {
     
        private ArrayList<Observer> observers; //Observer 들을 저장하는 ArrayList
        private String photoUrl;
        private String content;
     
        public Company() {
            observers = new ArrayList<>(); //생성자에서 초기화
        }
     
        @Override
        public void registerObserver(Observer o) { //Observer를 List에 추가하여 소식을 받는 객체로 등록
            // TODO Auto-generated method stub
            observers.add(o);
     
        }
     
        @Override
        public void removeObserver(Observer o) { //Observer를 List에 삭제하여 더이상 소식을 받지 못하도록 함
            // TODO Auto-generated method stub
            int i = observers.indexOf(o);
            if (i >= 0) {
                observers.remove(o);
            }
        }
     
        @Override
        public void notifyObserver() { ////Observer들에게 새로운 소식을 전해주는 메소드.
            // TODO Auto-generated method stub
            
            for(Observer ob : observers) {
                
                ob.update(photoUrl, content);
                
                
            }
     
        }
        
        public void messageChanged() { //새로운 소식이 들어왔다고 알려줌 (상태가 변했다고 알려주는 메소드)
            
            
            notifyObserver();
            
        }
        
        public void setMessage(String photoUrl, String content) { //새로운 소식이 들어오는 메소드
            
            this.photoUrl=photoUrl;
            this.content=content;
            messageChanged();
            
            
        }
     
    }
     
    cs



    Subscriber 클래스 (첫번째 구독자)



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    public class Subscriber implements Observer, DisplayElement {
     
        Subject company;
        private String photoUrl;
        private String content;
     
        public Subscriber(Subject company) {
     
            this.company = company; //Company 클래스의 레퍼런스를 들고와서 초기화 시켜준다. 
            company.registerObserver(this); //옵저버로 등록한다.
        }
     
        @Override
        public void display() { //받은 소식을 화면에 나타내어주는 메소드
            // TODO Auto-generated method stub
            System.out.println("1번째 구독자");
            System.out.println("photo link : " + photoUrl);
            System.out.println("content : " + content);
            System.out.println("\n");
        }
     
        @Override
        public void update(String photoUrl, String content) { //새로운 소식을 받는 메소드
            // TODO Auto-generated method stub
     
            this.photoUrl = photoUrl;
            this.content = content;
            display();
     
        }
     
    }
     
    cs



    Subscriber2 클래스 (두번째 구독자)


    = 구독자들(옵저버)은 생성자에서 옵저버로 등록을 하고 이벤트 정보를 화면에 표시하는 display 메소드를 DisplayElement 인터페이스를 통해 구현합니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    public class Subscriber2 implements Observer, DisplayElement {
        
     
        private Subject company;
        private String photoUrl;
        private String content;
     
        public Subscriber2(Subject company) {
     
            this.company = company; //Company 클래스의 레퍼런스를 들고와서 초기화 시켜준다. 
            company.registerObserver(this); //옵저버로 등록한다.
        }
     
        @Override
        public void display() { //받은 소식을 화면에 나타내어주는 메소드
            // TODO Auto-generated method stub
            System.out.println("2번째 구독자");
            System.out.println("photo link : " + photoUrl);
            System.out.println("content : " + content);
            System.out.println("\n");
        }
     
        @Override
        public void update(String photoUrl, String content) { //새로운 소식을 받는 메소드
            // TODO Auto-generated method stub
     
            this.photoUrl = photoUrl;
            this.content = content;
            display();
     
        }
    }
    cs


    Messanger 클래스



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    public class Messenger {
        
        public static void main(String[] args) {
            
            Company company = new Company(); //Subject 클래스를 생성합니다. 
            Subscriber subscriber1 = new Subscriber(company); //Observer 클래스를 생성합니다. 
            Subscriber2 subscriber2 = new Subscriber2(company); //Observer 클래스를 생성합니다. 
     
            
            company.setMessage("photo""이벤트"); //Subject에게 새로운 소식을 전달합니다. 
            
     
        }
     
    }
     
    cs



    컴파일 시킨 다면 다음과 같은 결과를 나타냅니다.






    Subject에게 전달된 결과가 Observer에게 전파 되는것을 확인할 수 있습니다.


    그럼 여기서 옵저버를 삭제 하면 어떻게 될까요?


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    public class Messenger {
        
        public static void main(String[] args) {
            
            Company company = new Company(); //Subject 클래스를 생성합니다. 
            Subscriber subscriber1 = new Subscriber(company); //Observer 클래스를 생성합니다. 
            Subscriber2 subscriber2 = new Subscriber2(company); //Observer 클래스를 생성합니다. 
     
            
            company.setMessage("photo""이벤트"); //Subject에게 새로운 소식을 전달합니다. 
            company.removeObserver(subscriber2);
            company.setMessage("photo2""새로운 이벤트"); //Subject에게 새로운 소식을 전달합니다. 
     
        }
     
    }
    cs


    MessageData 클래스에서 구현한 removeObserver 메소드를 호출하여 2번째 구독자를 제거합니다. 그리고 새로운 소식을 전달 한 후, 실행 시켜 보면




    첫번째는 두 명의 구독자 모두에게 내용이 전달되었지만, 두 번째는 ArrayList에서 두 번째 구독자가 삭제 되었으므로, 첫 번째 구독자에게만 새로운 소식이 전달 되게 됩니다. 


    옵저버 패턴의 장점은 무엇일까요? 바로 옵저버와 주제, 옵저버와 옵저버 간의 관계가 느슨하다는 것입니다. 이런 관계를 Loose Coupling 이라고 합니다. 서로에게 미치는 영향이 적다는 것입니다.


    관계가 느슨하면 옵저버는 서로를 알 필요도 없고, 누군가 새로운 옵저버로 추가 되거나 삭제되어도 알 필요가 없습니다.


    만약에, 다른 구독자가 나의 구독 정보를 훤하게 들여다 보거나,


    다른 구독자를 통해 소식을 들어야 하는 상황이 있다면 굉장히 껄끄럽고 불편할 것입니다.


    옵저버 패턴을 사용하면 이런 일은 일어나지 않습니다.


    또한 주제와 옵저버는 서로 독립적으로 재사용 할 수 있죠.


    다음 포스팅에는 자바 내장 객체를 통한 옵저버 패턴을 구현하는 것을 해 보겠습니다.

Designed by Tistory.