옵저버패턴

2023. 2. 12. 18:28카테고리 없음

옵저버 패턴 : 어떤 객체의 상태가 변할 때 그와 연관된 객체들에게 알림을 보내는 디자인 패턴

 

 

1.폴링 방식

 

LotteMart에  상품(value)가 현재 비어있다.

이 상품입고는 5초후에 입고가 될 예정이다.

package hello.hellospring.ex08polling;

public class LotteMart {
    //상품
    private  String value = null;

    public  String getValue(){
        return  value;
    }

    //상품 입고
    public  void received(){
        try {
            for (int i =0; i<5;i++){
                Thread.sleep(1000);

            }
            value ="상품";
        }catch (Exception e){
            System.out.println(e.getMessage());

        }

    }
}

손님1 : 해당 상품을 사고 싶어한다.

package hello.hellospring.ex08polling;

public class Customer1 {

    public  void update(String msg){
        System.out.println("고객1 이 받은 알림:"+msg);

    }
}

손님2: 해당 상품을 사고 싶어한다.

package hello.hellospring.ex08polling;

public class Customer2 {

    public  void update(String msg){
        System.out.println("고객2가 받은 알림:"+msg);

    }
}

 

 

 

main 쓰레드가 상품이 입고 되었는지 확인 하는 것을 새로운 스레드에게 작업을 시키고 

다음 작업을 순차적으로 이어간다. 이때 손님 1,손님2이 상품 언제 들어왔는지 3초에 한번씩 계속 물어보게된다 .

3초가 지나고 다시한번 물어볼때 쯤에는 상품입고를 확인하러간 스레드가 입고가 되었다고 알려준다.

상품이 null이 아니면 상품이 입고 되었을때  손님1,손님2에게 알림을 준다.

 

-> 여기서 단점 ! 손님1과 손님2는 해당 상품을 너무나도 사고 싶고 이 상품을 살려고하는 사람들이 많다면 손님1과 손님2는 해당 상품이 언제들어오는지 알지도 못한채 계속 하염없이 기다려야 한다. 근데 만약 물어보는 타임이 시간차가 크다면 

해당 상품이 이미 들어와서 다 나갔을 경우에 수가 있고 또 너무 자주 물어보면 점원은 다른일을 못하고 계속 응대를 해줘야 한다. 결국에는 손님1도 시간 낭비 점원도 (메인쓰레드)도 시간 낭비이다.

(여기서 쓰레드를 두개쓴 이유는 상품이 입고 될때까지 (5초 )지나기까지 손님1,손님2가 해당 자원을  기다리는것을 보여주기위해서 쓰레드 두개를 사용하였다)

 

이것은 프로그래밍 관점에서 보자면 

상품 입고 ->i/o작업이고 

i/o작업은  블라킹, 싱크로너스하게 동작이 된다 . 즉 그말은 cpu는 쓰레드가 해당 작업이 끝날때 까지 계속 기다릴 수 밖에

없다는 것이다.

그렇게 되면 cpu 자원을 효율적으로 사용하지 못하게 된다.

 

package hello.hellospring.ex08polling;

/*

폴링 방식
 */


public class App {
    public static void main(String[] args) {
       LotteMart lotteMart = new LotteMart();
       Customer1 customer1 = new Customer1();
       Customer2 customer2 = new Customer2();

       //다른 스레드에서 실행
       new Thread(()->{
           //메인 스레드가 새로운 스레드를 실행 시키고 다음 작업을 실해한다.
            //5초 후에 value값이 들어옴
            lotteMart.received();
       }).start();

       //메인 스레드에서 실행!!

       //한번 물어보면 해결되지 않는다

        while (true){

            //어느 정도 시간을 정해서 물어봐야 한다. 폴링!!
            try {
                System.out.println("상품 들어왔나요?.......");
                //물어보는 시간이 짧으면  상품이 들어온 것을 빠르게 알수 있다.( 장점: 누구보다 빠르게, 단점: 지친다)
                // 물어보는 시간이 길면 상품이 들어온 것을 빠르게 알수 없다.(장점: 덜 지친다,단점 : 구매가 어렵다)
                Thread.sleep(3000);
            }catch (Exception e){
                System.out.println(e.getMessage());

            }


            if(lotteMart.getValue() !=null){
                customer1.update(lotteMart.getValue()+"가 들어왔습니다");
                customer2.update(lotteMart.getValue()+"가 들어왔습니다");
                break;
            }else {
                System.out.println("상품이 아직 들어오지 않았습니다");//아직 상품이 들어오지 않은 상태에서 5초가 지나서 상품이 들어오다 보니 한번 물어볼수 없다.



            }



        }






    }
}

상품이 아직 들어오지 않았습니다
상품 들어왔나요?.......
고객1 이 받은 알림:상품가 들어왔습니다
고객2가 받은 알림:상품가 들어왔습니다

Process finished with exit code 0

2.옵저버(push)방식

 

Mart 인터페이스를 만든다.

(옵저버 방식에 구독과 구독취소 알림 부분 구현을 위한 추상 메서드를 만든다)

package hello.hellospring.ex08.push.pub;

import hello.hellospring.ex08.push.sub.Customer;

public interface Mart {
   //고객 등록
    void add(Customer customer);
   //고객 등록 취소
    void remove(Customer customer);

   //상품 받기
   void received();


    //알림
    void notifyChange(String msg);


}

LotteMart가 Mart 인터페이스를 상속 받아 해당 추상 메서드를 구현한다.

List에 구독하는 손님을 추가하거나 , 지우고  상품이 입고 되었을때 상품 상태값이 변했음을 구독한 List(고객 명단)을

보고 알림을 보낸다.

(이와 똑같이 lgMart 클래스를 구현한다)

package hello.hellospring.ex08.push.pub;

import hello.hellospring.ex08.push.sub.Customer;

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

public class LotteMart implements Mart {

    //고객명단

   private  List<Customer> customerList = new ArrayList<>();
    @Override
    public void add(Customer customer) {
        customerList.add(customer);
    }

    @Override
    public void remove(Customer customer) {
        customerList.remove(customer);

    }

    @Override
    public void received() {

        try {
            for (int i = 0; i < 10; i++) {
                System.out.println(".");
                Thread.sleep(1000);
            }

            notifyChange("lotte 상품들어왔어 ");
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

    //모든 고객중에 등록된 손님에게 알림을 보내야 한다
    @Override
    public void notifyChange(String msg) {

        customerList.forEach((c)->{
            c.update(msg);
                });
    }
}

 

 

Customer 인터페이스를 만들고 손님들이 구현해야 하는 추상 메서드를 만든다. ( 알림을 받기위한 메서드)

package hello.hellospring.ex08.push.sub;

public interface Customer {
    void update(String msg);
}

Cus1과 똑같이 Cus2,Cus3을  만든다.

package hello.hellospring.ex08.push.sub;

public class Cus1 implements Customer{
    @Override
    public void update(String msg) {
        System.out.println("고객1이 받은 알림:" +msg);

    }
}

 

package hello.hellospring.ex08.push.sub;

/*
옵저버 패턴 (push)
 */

import hello.hellospring.ex08.push.pub.LgMart;
import hello.hellospring.ex08.push.pub.LotteMart;
import hello.hellospring.ex08.push.pub.Mart;

public class App {
    public static void main(String[] args) {
        Mart lotteMart = new LotteMart();
        Mart lgMart = new LgMart();

        Customer cus1 = new Cus1();
        Customer cus2 = new Cus2();
        Customer cus3 = new Cus3();

        //고객 등록(구독하기)
        lotteMart.add(cus1);
        lotteMart.add(cus2);

        lgMart.add(cus3);


  /*      //고객 취소 (구독취소)
        lotteMart.remove(cus2);*/

        //마트:롯데 상품 아 빨리 도착해
        //동기적으로 실행이 되기에 제대로된 테스트를 할 수 없다
        new Thread(()->{
            lotteMart.received();
        }).start();


        //마트:엘지  상품 아 빨리 도착해
        new Thread(()->{

            lgMart.received();
        }).start();


    }

}

.
.
.
.
.
.
.
.
.
고객3이 받은 알림:lg 상품들어왔어 
.
.
.
.
고객1이 받은 알림:lotte 상품들어왔어 
고객2이 받은 알림:lotte 상품들어왔어

가장큰 차이: 위에 폴링 방식은 상품입고 이후에 고객들에게 따로 알려줘야한다.

그때 까지 고객들은 기다려야 한다 수동적이다.

하지만 방금한 push 방식은 해당 상품이입고됨과 동시에 구독한 고객명단을 보고 거기에 알림을 보낸다.

능동적이다. 이 부분이 달라지는 것이다. 그러면 고객들은 다른 일을하고 있다가 알림을 받아도된다.

(논블락 , 어싱크) 

 

메인 스레드는  Lotte상품입고 확인을 다른 스레드에게 시키고 (위임)다른 일을 처리하러 간다 , 그다음에 Lg상품입고를

다른 스레드에게 시키고  다음에 코드를 계속 실행시키면 된다. 

그리고  상품입고를 확인하는 스레드들은 입고가 되면  구독한 고객 명단에 입고 되었음을 알림보내주면된다.

결론적으로 메인 스레드는  쉬지 않고 다른 일을 하면된다 기다릴 필요없다 

 

이것은 cpu 자원을 효율적으로 사용 할수있다.