본문 바로가기

디자인 패턴/디자인 패턴

[디자인 패턴] 옵저버 패턴 문제 - 건물 (옵저버, 스트래티지)

책을 공부하며 새로운 문제를 만들고 구현한것을 기록한 게시글입니다.

(이해한 내용을 바탕으로 작성했기 때문에, 내용이 정확하지않고 틀린부분이 있을수있으며, 예시가 패턴을 사용하기에 알맞지 않을수도있습니다)


옵저버 패턴

- 하나의 주제가 여러개의 옵저버에 바뀌는 내용을 전달함.

(한 객체가 바뀌면 객체에 의존하는 다른객체들의 내용이 갱신되는 1대다 패턴)

- 주제와 옵저버는 느슨한 결합으로 연결되어있어야함.

(옵저버가 추가되거나 옵저버의 내용이 바뀌어도 주제의 내용은 바뀌면 안됨)

- 옵저버들에게 연락을 돌리는 순서에 의존하면안됨.

(순서가바뀌면 옵저버가 잘못된 데이터를 받을수있음 이는 느슨한 결합이 아님)


문제

재석이의 건물

건물주가 된 재석이는 자신의 건물을 효과적으로 관리하고싶어한다.

 

지금까지 재석이는 새로운 공지사항이 있을때마다 각각 호실의 세입자를 불러 내용을 전달했다. (직접 가서 알리긴 귀찮기 때문이다.)

이에 불만을 느낀 세입자들은 방을 빼기 시작했고, 안좋은 소문이나 더 이상 재석이의 건물에 세입자가 들어오지 않기 시작했다.

 

위기감을 느낀 재석이는 이제는 새로운 공지사항이 있을때마다 각각의 호실에 내용을 전달해줄려한다.

(역시나 재석이는 이 또한 귀찮아서 직접 가지않고 프로그램으로 내용을 전달한다.)

 

재석이가 만들 프로그램에는 다음기능이 들어가야한다.

 

1. 재석이의 건물에 새로운 공지사항(집세가 올라간다던가..)이 있을때마다 재석이는 프로그램에 바뀌는 내용을 입력하고, 재석이의 건물에 등록되어있는 세입자들은 바뀐 내용을 자동으로 알게된다.

 

2. 세입자들을 호출할수있어야한다. 호출된 세입자들은 자신의 계약정보 (호실, 월세, 계약금, 옵션들)을 알려준다.

-세입자는 3종류가 있으며 필요에 따라 더 추가될수있다. (bosstenant, studenttenant, normaltenant)

 

공지사항은 2가지 종류가있다. (이후에 새로운 공지사항이 추가될수있다)

1. 집세올리기

2. 옵션추가하기

 

세입자들은 계약할때

1. 자신의 호실이름(꼭 숫자가 아니여도된다.)

2. 초기 집세

을 정하고 들어간다.

 

(계약시 기본옵션은 없다)

 

입력은 다음과 같다.

'addTenant'가 입력되면 이어서 '세입자 종류(b,s,n)' , '집세'와 '방번호'가 입력되며, 세입자를 추가한다.

'callTenant'이 입력되면 모든 세입자를 호출하며 세입자들은 자신의 계약정보를 알려준다.

'delTenant'이 입력되면 이어서 '세입자의 방 이름'이 입력되며 세입자를 제거한다. 

'addOption'이 입력되면 이어서 추가할 '옵션'이 주어지며 모든 세입자에게 옵션을 추가한다.

'increaseRent'가 입력되면 이어서 올릴 '월세'가 주어지며 모든 세입자에게 월세를 추가로 받기시작한다.


구현

 

우선 객체가 될만한것들을 생각해보면,

 

- 객체

 

1. Building

2. bossTenant, studentTenant, normalTenant (세입자들)

 

이렇게 두 가지가 있을것이다.

 

각각 객체들이 해야할 행동을 생각해보고, 이를 인터페이스로 만들자.

 

- 인터페이스

 

1. 집은 세입자들을 관리한다. (세입자들은 재석이에게 속하는것이 아닌 집에 속함)  따라서 집은 자신에게 세입자를 추가하는 addObserver 메소드, 세입자를 없애는 delObserver 메소드, 세입자들에게 공지사항을 알려주는 notice 메소드, 자신에게 속한 세입자들을 부르는 call 메소드가 필요할것이다.

 

2. 세입자들은 집에 속한다. 집 객체가 자신에게 집세를 올리거나, 옵션을 추가할때 사용할수있는 update메소드, 자신의 정보를 알리기위한 speakOption, speakMonthly, speakRoom메소드들이 필요할것이다.

 

따라서, 구조는 다음과 같을것이다.

 

- 집 (세입자들의 Subject)

-- 세입자 (집의 Observer)

 

내가 구현한 객체들의 특징은 다음과 같다.

 

건물

- 건물은 자신의 세입자들이 observer인터페이스와 tenantBehavior인터페이스를 구현한다는것만 알고있다. -> 느슨하게 연결되어있다.

- 코드를 고치지않고, 새로운 세입자를 동적으로 추가할수있다.

건물의 코드는 다음과 같다.

 

package magicBuilding.Building;

import java.util.ArrayList;
import magicBuilding.Tenants.*;

public class Building implements subject{
    
    private ArrayList<observer> tenantList = new ArrayList<observer>();
    
    public void addObserver(observer tenant){
        tenantList.add(tenant);
    }
    
    public void delObserver(String room){
        room = "my Room : " + room + "\n";
        for(int i = 0; i < tenantList.size(); i++){
            tenantBehavior tenant = (tenantBehavior)tenantList.get(i);
            if(!room.equals(tenant.speakRoom())) continue;
            tenantList.remove(i);
            break;
        }
    }
    
    public void notice(String option, int monthly){
        for(int i = 0; i < tenantList.size(); i++){
            observer tenant = tenantList.get(i);
            tenant.update(option, monthly);
        }
    }
    
    public void call(){
        for(int i = 0; i < tenantList.size(); i++){
            observer tenant = tenantList.get(i);
            tenant.call();
            // System.out.println();
        }
    }
    
}

 

세입자

- 세입자들은 자기 자신에 대해서만 알고있다.

 

세입자들중, studentTenant의 코드는 다음과 같다.

package magicBuilding.Tenants;

import java.util.ArrayList;
import magicBuilding.Building.*;

public class studentTenant implements tenantBehavior, observer{
    
    private int monthly;
    private String room;
    private ArrayList<String> options = new ArrayList<String>();
    
    public studentTenant(subject Building, int monthly, String room){
        this.monthly = monthly;
        this.room = room;
        Building.addObserver(this);
    }
    
    public void update(String option, int monthly){
        this.monthly += monthly;
        if(!option.equals("")) options.add(option);
    }
    
    public void call(){
        System.out.print("Hi! im Students! " + speakRoom() + speakMonthly() + speakOptions());
    }
    
    public String speakRoom(){
        return "my Room : " + room + "\n";
    }
    
    public String speakMonthly(){
        return "my Monthly : " + String.valueOf(monthly) + "\n";
    }
    
    public String speakOptions(){
        String ret = "my Options : \n";
        int size = options.size();
        for(int i = 0; i < size; i++){
            ret += "-" + options.get(i);
            if(i < size) ret += "\n";
        }
        return ret;
    }
    
}

옵저버 패턴을 잘 구현했나 체크해보자.

 

1. 객체들이 일대다 관계다. -> O

 

2. Subject는 동일한 인터페이스를 써서 observer에 연락을 한다. -> O

 

3. 느슨한 결합을 하고있다. -> O


전체 소스코드

커밋전에 폴더를 정리하고 올리기때문에, 커밋하는 과정에서 경로가 변경되어 코드 실행이 안될수도있다. (모두 실행되는걸 확인하고올리지만) 컴파일시 경로를 변경해서 실행하도록하자.

 

https://github.com/devxb/DesignPatterns/tree/main/DesignPatterns/prob2.building

 

devxb/DesignPatterns

디자인 패턴 🤔. Contribute to devxb/DesignPatterns development by creating an account on GitHub.

github.com