[DesignPattern] 옵서버 패턴

옵서버 패턴


Observer 패턴에서는 관찰 대상의 상태가 변화하면 관찰자에게 알려준다.

Observer 패턴은 상태 변화에 따른 처리를 기술할 때 효과적이다.

옵서버 패턴은 데이터의 변경이 발생했을 경우 상대 클래스나 객체에 의존하지 않으면서 데이터 변경을 통보하고자 할 때 유용하다.

예를 들어 새로운 파일이 추가되거나 기존 파일이 삭제되었을 때 탐색기는 이를 즉시 표시할 필요가 있다. 탐색기를 복수 개 실행하는 상황이나 하나의 탐색기에서 파일 시스템을 변경했을 때는 다른 탐색기에게 즉각적으로 이 변경을 통보해야 한다. 다른 예로는 차량의 연로가 소진될 때까지의 주행 가능 거리를 출력하는 클래스, 연료량이 부족하면 경고 메시지를 보내는 클래스, 연료량이 부족하면 자동으로 근처 주유소를 표시하는 클래스 등에 연료량의 변화를 통보하는 경우가 있다. 이런 경우에 연료량 클래스는 연료량에 관심을 가지는 구체적인 클래스에 직접 의존하지 않는 방식으로 설계하는 것이 바람직하다.

--> 옵서버 패턴은 통보 대상 객체의 관리를 Subject 클래스와 Observer 인터페이스로 일반화한다. 그러면 데이터 변경을 통보하는 클래스(ConcreateSubject)는 통보 대상 클래스나 객체(ConcreateObserver)에 대한 의존성을 없앨 수 있다. 결과적으로 옵서버 패턴은 통보 대상 클래스나 대상 객체의 변경에도 ConcreateSubject 클래스를 수정없이 그대로 사용할 수 있도록 한다.



▶ 등장인물

 이름

 해설

 Observer

 관찰자를 나타내는 인터페이스

 NumberGenerator

 수를 생성하는 오브젝트를 나타내는 클래스

 RandomNumberGenerator

 랜덤으로 수를 생성하는 클래스

 DigitObserver

 숫자로 수를 표시하는 클래스

 GraphObserver

 간이 그래프로 수를 표시하는 클래스

 Main

 동작 테스트용 클래스


▶ 예제 프로그램 해설

Observer 패턴을 사용한 예제 프로그램을 살펴보자. 이번 예제 프로그램은 많은 수를 생성하는 오브젝트를 관찰자가 관찰해서 그 값을 표시하는 단순한 것이다.

단, 표시의 방법은 관찰자에 따라서 다르다. DigitObserver는 값을 숫자로 표시하지만, GraphObserver는 값을 간이 그래프로 표시한다.


1. Observer

- Observer(관찰자)의 역할: Observer는 Subject 역할로부터 '상태가 변했습니다.'라고 전달 받는 역할을 한다. 이를 위한 메서드는 update이다.

데이터의 변경을 통보 받는 인터페이스 즉, Subject에서는 Observer 인터페이스의 update 메서드를 호출함으로써 ConcreateSubject의 데이터 변경을 ConcreateObserver에게 통보한다.

package observer;


public interface Observer {

    public abstract void update(NumberGenerator generator);

}

Observer 인터페이스는 '관찰자'를 표현하는 인터페이스이고, 구체적인 관찰자는 이 인터페이스를 구현한다. 

update 메서드는 수를 생성하는 NumberGenerator 클래스에서 호출된다.(generator는 '생성하는 것', '발생장치'라는 의미이다.) update 메서드는 NumberGenerator 클래스가 '나의 내용이 갱신되었습니다. 표시 쪽도 갱신해 주십시오'라고 Observer에게 전달하기 위한 메서드이다.


2. NumberGenerator

- Subject(관찰 대상자)의 역할: Subject는 '관찰되는 대상'을 나타낸다. Subject 역할은 관찰자인 Observer 역할을 등록하는 메서드와 삭제하는 메서드를 가지고 있다. 또 '현재의 상태를 취득하는' 메서드도 선언되어 있다. ConcrerateObserver 객체를 관리하는 요소. Observer 인터페이스를 참조해서 ConcreateObserver를 관리하므로 ConcreateObserver의 변화에 독립적일 수 있다.

package observer;


import java.util.ArrayList;

import java.util.Iterator;


public abstract class NumberGenerator {

    private ArrayList observers = new ArrayList(); // Observer를 저장

    public void addObserver(Observer observer) { // Observer를 추가

        observers.add(observer);

    }

    public void deleteObserver(Observer observer) { // Observer를 삭제

        observers.remove(observer);

    }

    public void notifyObservers() { // Observer에 알림

        Iterator it = observers.iterator();      

        while (it.hasNext()) {                 

            Observer o = (Observer)it.next();  

            o.update(this);                   

        }                                     

    }                                      

    public abstract int getNumber(); // 수를 취득한다

    public abstract void execute(); // 수를 생성한다

}

NumberGenerator 클래스는 수를 생성하는 추상 클래스이다. 실제의 수의 생성(excute 메서드)과 수를 취득하는 부분(getNumber 메서드)은 하위 클래스에서 구현되도록 추상 메서드로 되어 있다.(subclass responsibilty). observers 필드는 NumberGenerator를 관찰하는 Observer를 보존하는 필드이다.

addObserver는 Observer를 추가하는 메서드이고, deleteObserver는 Observer를 삭제하는 메서드이다. notifyObservers 메서드는 Observer 전원에 대해서 '나의 내용이 갱신되었으므로, 당신의 표시를 갱신해 주십시오'라고 전한다. 이 메서드 안에서는 observer 안의 Observer들 한 사람 한 사함의 update 메서드를 호출하고 있다.


3. RandomNumberGenerator

- ConcreateSubject(구체적인 관찰 대상자)의 역할ConcreateSubject는 구체적으로 '관찰되는 대상'을 표현하는 역할이다. 상태가 변화하면 그것이 등록되어 있는 Observeer 역할에 전한다. 

변경 관리 대상이 되는 데이터가 있는 클래스. 데이터 변경을 위한 메서드인 excute가 있으며, excute에서는 자신의 데이터인 subjectObserver를 변경하고 Subject의 notifyObservers 메서드를 호출해서

ConcreateObserver 객체에 변경을 통보한다.

package observer;


import java.util.Random;


public class RandomNumberGenerator extends NumberGenerator {

    private Random random = new Random(); // 난수 발생기

    private int number; // 현재의 수

    public int getNumber() { // 수를 취득한다

        return number;

    }

    public void execute() {

        for (int i = 0; i < 20; i++) {

            number = random.nextInt(50);

            notifyObservers();           

        }

    }

}

RandomNumberGemerator 클래스는 NumberGenerator의 하위 클래스이고, 난수를 생성한다. 

execute 메서드는 난수(0~49의 범위의 정수)를 20개 생성하고, 그때마다 notifyObservers를 사용해서 관찰자에게 통지한다. 여기에서 사용되고 있는 nextInt 메서드는 java.util.Random 클래스의 메서드로 랜덤인 정수를 반환값으로 한다.


4. DigitObserver 

package observer;


public class DigitObserver implements Observer {

    public void update(NumberGenerator generator) {

        System.out.println("DigitObserver:" + generator.getNumber());

        try {

            Thread.sleep(100);

        } catch (InterruptedException e) {

        }

    }

}

DigitObserver 클래스는 Observer 인터페이스를 구현하는 클래스로 관찰한 수를 '숫자'로 표시하기 위한 것이다. update 메서드 안에서 인수로 주어진 NumberGenerator의 getNumber 메서드를 사용해서 수를 취득하고 그것을 표시한다.


5. GraphObserver

- ConcreateObserver(구체적인 관찰자)의 역할ConcreateObserver는 구체적인 Observer이다. update 메서드가 호출되면 그 메서드안에서 Subject 역할의 현재 상태를 취득한다.

ConcreateSubject의 변경을 통보받는 클래스. Observer 인터페이스의 update 메서드를 구현함으로써 변경을 통보받는다. 변경된 데이터는 ConcreateSubject의 getNumber 메서드를 호출함으로써 변경을 조회한다.

package observer;


public class GraphObserver implements Observer {

    public void update(NumberGenerator generator) {

        System.out.print("GraphObserver:");

        int count = generator.getNumber();

        for (int i = 0; i < count; i++) {

            System.out.print("*");

        }

        System.out.println("");

        try {

            Thread.sleep(100);

        } catch (InterruptedException e) {

        }

    }

}

GraphObserver 클래스도 Observer 인터페이스를 구현하는 클래스이다. 이 클래스는 관찰한 수를 '간이 그래프'로 표시한다.


6. Main

package observer;


public class Main {

    public static void main(String[] args) {

        NumberGenerator generator = new RandomNumberGenerator();

        Observer observer1 = new DigitObserver();

        Observer observer2 = new GraphObserver();

        generator.addObserver(observer1);

        generator.addObserver(observer2);

        generator.execute();

    }

}

실행결과)

DigitObserver:26

GraphObserver:**************************

DigitObserver:3

GraphObserver:***

DigitObserver:4

GraphObserver:****

DigitObserver:37

GraphObserver:*************************************

DigitObserver:32

GraphObserver:********************************

DigitObserver:26

GraphObserver:**************************

DigitObserver:22

GraphObserver:**********************

DigitObserver:2

GraphObserver:**

DigitObserver:15

GraphObserver:***************

DigitObserver:10

GraphObserver:**********

DigitObserver:19

GraphObserver:*******************

DigitObserver:29

GraphObserver:*****************************

DigitObserver:42

GraphObserver:******************************************

DigitObserver:20

GraphObserver:********************

DigitObserver:39

GraphObserver:***************************************

DigitObserver:1

GraphObserver:*

DigitObserver:18

GraphObserver:******************

DigitObserver:43

GraphObserver:*******************************************

DigitObserver:2

GraphObserver:**

DigitObserver:21

GraphObserver:*********************

Main 클래스에서는 RandomNumberGenerator의 인스턴스를 한 개 만들고, 그 관찰자를 두 개 만든다. observer1은 DigitObserver, observer2는 GraphObserver의 인스턴스이다.

addObserver 메서드를 사용해서 관찰자를 등록한 후 generator.excute를 사용해서 수를 생성한다.


cp.) 디자인 패턴의 목적 중의 하나는 클래스를 재이용 가능한 부품으로 만드는 일이다. Observer 패턴에서는 상태를 가지고 있는 ConcreateSubject 역할과 상태변화를 전달 받는 ConcreateObserver 역할이 등장했다.

그리고 이 두 가지의 역할을 연결하는 것이 인터페이스인 Subject 역할과 Observer 역할이다.

RandomNumberGenerator 클래스는 현재 자신을 관찰하고 있는 것이 (자신이 알려주는 상대가) DigitObserver 클래스의 인스턴스인지 GraphObserver 클래스의 인스턴스인지 몰라도 상관없다.

그러나 observer 필드에 저장되어 있는 인스턴스들이 Obsever 인터페이스를 구현하고 있다는 것은 알고 있다. 이 인스턴스들은 addObserver에서 추가 된 것이므로 반드시 Observer 인터페이스를 구현하고 있으며 update 메서드를 호출할 수 있다. 한편, DigitObserver 클래스는 자신이 관찰하고 있는 것이 RandomNumberGenerator 클래스의 인스턴스인지, 다른 XXXNumberGenerator 클래스의 인스턴스인지 신경 쓰지 않는다.

단지 NumberGenarator의 하위 클래스의 인스턴스이고, getNumber 메서드를 가지고 있다는 것은 알고 있다.


cf.) MVC

Model/View/Controller(MVC) 안의 Model과 View의 관계는 Observer 패턴의 Subject 역할과 Observer 역할의 관계에 대응한다. Model은 '표시 형식에 의존하지 않는 내부 모델'을 조작하는 부분이다.

또 View는 Model이 '어떻게 보일 것인지'를 관리하고 있는 부분이다. 일반적으로 하나의 Model에 복수의 View가 대응한다.


[DesignPattern] 팩토리 메서드 패턴

팩토리 메서드 패턴


팩토리 메서드 패턴에서는 상위 클래스에서 처리의 골격을 만들고, 하위 클래스에서 구체적인 처리의 내용을 만들었다. 이 패턴을 인스턴스 생성의 장면에 적용한 것이 바로 팩토리 메서드 패턴이다. factory는 공장이라는 의미이다. 인스턴스를 생성하는 공장을 템플릿 메서드 패턴으로 구성한 것이 팩토리 메서드 패턴이다. 즉, 팩토리 메서드 패턴을 사용하면 객체 생성 기능을 제공하는 Factory 클래스를 정의하고 이를 활용하는 방식으로 설계하면 된다.

펙토리 메서드 패턴에서는 인스턴스를 만드는 방법을 상위 클래스 측에서 결정하지만 구체적인 클래스 이름까지는 결정하지 않는다. 따라서 인스턴스 생성을 위한 골격과 실제의 인스턴스 생성의 클래스를 분리해서 생각할 수 있다.

cp.) 팩토리 메서드 패턴은 객체의 생성 코드를 별도의 클래스/메서드로 분리함으로써 객체 생성의 변화에 대비하는 데 유용하다. 또한 팩토리 메서드 패턴은 객체 생성을 전담하는 별도의 클래스를 두는 대신 하위 클래스에서 적합한 클래스의 객체를 생성하는 방식으로도 적용할 수 있다.


▶ 등장인물

 패키지 

 이름

 해설

 framework

 Product

 추상메서드 use만 정의되어 있는 추상 클래스

 Factory

 메서드 create을 구현하고 있는 추상 클래스

 idcard

 IDCard

 메서드 use를 구현하고 있는 클래스

 IDCardFactory

 메서드 createProduct, registerProduct를 구현하고 있는 클래스

 Anonymous

 Main

 동작 테스트용 클래스


▶ 예제 프로그램 해설

아래의 예제 프로그램에서는 신분증명서 카드(ID 카드)를 만드는 공장을 소재로 하였다. 여기에는 다섯 개의 클래스가 있다. Product 클래스와 Factory 클래스는 framework라는 패키지에 속해 있다.

이 두 개의 클래스가 인스턴스 생성을 위한 골격(framework)의 역할을 한다.

IDCard 클래스와 IDCardFactory 클래스는 구체적인 내용을 구현하며 idcard라는 패키지에 속해 있다.

Main 클래스는 동작 테스트를 위한 클래스이다.


1. Product: 팩토리 메서드로 생성될 객체의 공통 인터페이스

- Product(제품)의 역할: 이것은 framework 쪽에 포함되어 있다. 이 패턴에서 생성되는 인스턴스가 가져야 할 인터페이스를 결정하는 것은 추상 클래스이다.

구체적인 내용은 하위 클래스의 ConcreateProduct 역할이 결정한다.

package framework;


public abstract class Product {

    public abstract void use();

}

framework 패키지의 Product 클래스는 '제품'을 표현한 클래스이다. 이 클래스에는 추상 메서드 use만이 선언되어 있다.

구체적인 use의 구현은 모두 Product의 하위 클래스에게 맡기고 있다.

이 framework에서는 제품이란 '무엇이든 use할 수 있는(사용할 수 있는) 것'으로 규정하고 있다.


2. Factory: 구체적으로 객체가 생성되는 클래스

- Creator(작성자)의 역할: Product 역할을 생성하는 추상 클래스는 framework 쪽에 가깝다. 구체적인 내용은 하위 클래스의 ConcreateCreator 역할이 결정한다.

Creator 역할은 실제로 생성하는 ConcreateProduct 역할에 가지고 있는 정보가 없다. Creator 역할이 가지고 있는 정보는 Product 역할과 인스턴스 생성의 메소드를 호출하면 Product가 생성된다는 것뿐이다. 예제 프로그램에서는 createProduct 메서드가 인스턴스 생성을 위한 메서드가 된다.

new를 사용해서 실제의 인스턴스를 생성하는 대신에, 인스턴스 생성을 위한 메서드를 호출해서 구체적인 클래스 이름에 의한 속박에서 상위 클래스를 자유롭게 만든다.

package framework;


public abstract class Factory {

    public final Product create(String owner) {

        Product p = createProduct(owner);

        registerProduct(p);

        return p;

    }

    protected abstract Product createProduct(String owner);

    protected abstract void registerProduct(Product product);

}

framework 패키지의 Factory 클래스에서는 템플릿 메서드 패턴이 사용되고 있다.

추상 메서드 createProduct에서는 '제품을 만들고', 만든 제품을 추상 메서드 registerProduct에서 '등록'한다. '제품을 만들고', '등록'하는 구현은 하위 클래스에서 수행한다.

이 framework에서 공장이란 'create 메서드에서 Product의 인스턴스를 생성하는 것'으로 규정하고 있다.

그리고 create 메서드는 'createProduct에서 제품을 만들어서 registerProduct에서 등록한다.'라는 순서로 구현되고 있다.

구체적인 구현 내용은 Factory Method 패턴을 적용한 프로그램에 따라 다르다. Factory Method 패턴에서는 인스턴스를 생성할 때 Templete Method 패턴을 사용한다.


3. IDCard: 구체적으로 객체가 생성되는 클래스

- ConcreateProduct(구체적인 제품)의 역할: 구제적인 제품을 결정하며, idcard 쪽에 해당된다.

package idcard;

import framework.*;


public class IDCard extends Product {

    private String owner;

    IDCard(String owner) {

        System.out.println(owner + "의 카드를 만듭니다.");

        this.owner = owner;

    }

    public void use() {

        System.out.println(owner + "의 카드를 사용합니다.");

    }

    public String getOwner() {

        return owner;

    }

}

내용을 실행하는 측(idcard 패키지)을 살펴보자. 인식번호 카드를 나타내는 IDCard라는 클래스를 만들어보자.

framework에서 분리된 것을 표시하기 위해 idcard 패키지라는 별도의 패키지를 만들고, IDCard 클래스를 제품 product 클래스의 하위 클래스로 정의한다.


4. IDCardFactory: 팩토리 메서드를 구현하는 클래스로 ConcreateProduct 객체를 생성한다.

- ConcreateCreator(구체적인 작성자)의 역할: 구체적인 제품을 만드는 클래스를 결정하며, idcard 쪽에 해당된다.

package idcard;

import framework.*;

import java.util.*;


public class IDCardFactory extends Factory {

    private List owners = new ArrayList();

    protected Product createProduct(String owner) { // 인스턴스 생성을 위한 메서드

        return new IDCard(owner);// IDCard의 인스턴스를 생성해서 제품을 만드는 일을 실현

// new를 사용해서 실제 인스턴스를 사용하는 대신에, 인스턴스 생성을 위한 메서드를 호출해서 

// 구체적인 클래스 이름에 의한 속박에서 상위클래스를 자유롭게 만든다.

    }

    protected void registerProduct(Product product) {

        owners.add(((IDCard)product).getOwner());

    }

    public List getOwners() {

        return owners;

    }

}

IDCardFactory 클래스에서는 createProduct와 registerProduct의 두 가지 메서드를 구현하고 있다. createProduct에서는 IDCard의 인스턴스를 생성해서 '제품을 만드는'을 실현하고 있다.

registerProduct에서는 IDCard의 owner(소유자)를 owners 필드를 추가해서 '등록'이라는 기능을 실현하고 있다.


5. Main

import framework.*;

import idcard.*;


public class Main {

    public static void main(String[] args) {

        Factory factory = new IDCardFactory();

        Product card1 = factory.create("홍길동");

        Product card2 = factory.create("이순신");

        Product card3 = factory.create("강감찬");

        card1.use();

        card2.use();

        card3.use();

    }

}

실행결과)

홍길동의 카드를 만듭니다.

이순신의 카드를 만듭니다.

강감찬의 카드를 만듭니다.

홍길동의 카드를 사용합니다.

이순신의 카드를 사용합니다.

강감찬의 카드를 사용합니다.


[DesignPattern] 템플릿 메서드 패턴

템플릿 메서드 패턴


Templete Method 패턴은 템플릿의 기능을 가진 패턴이다. 상위 클래스 쪽에 템플릿에 해당하는 메서드가 정의되어 있고, 그 메서드의 정의 안에는 추상 메서드가 사용되고 있다.

따라서 상위 클래스의 프로그램만 보면 추상 메서드를 어떻게 호출하고 있는지 알 수 있지만, 최종적으로 어떤 처리가 수행되는지는 알 수 없다.

추상 메서드를 실제로 구현하는 것은 하위 클래스이다. 하위 클래스 측에서 메서드를 구현하면 구체적인 처리가 결정된다. 서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 결정된다. 서로 다른 하위 클래스가 서로 다른 구현을 실행하면 서로 다른 처리가 실행될 것이다.

그러나 어떤 하위 클래스에서 어떤 구현을 하더라도 처리의 큰 흐름은 상위 클래스에서 결정한대로 이루어진다. 이와 같이 상위 클래스에서 처리의 뼈대를 결정하고, 하위 클래스에서 그 구체적인 내용을 결정하는 디자인 패턴을 템플릿 메서드 패턴이라고 한다.




▶ 등장인물

 이름

 해설

 AbstractDisplay

 메서드 display만 구현되고 있는 추상 클래스

 CharDisplay

 메서드 open, print, close를 구현하고 있는 클래스

 StringDisplay

 메서드 open, print, close를 구현하고 있는 클래스

 Main

 동작 테스트용 클래스


1. AbstractDisplay 

- AbstractClass(추상 클래스)의 역할AbstractClass는 템플릿 메서드를 구현한다. 또한 그 템플릿 메서드에서 사용하고 있는 추상 메서드를 선언한다. 이 추상 메서드를 선언한다.

이 추상 메서드는 하위 클래스인 ConcreateClass 역할에 의해 구현된다.

public abstract class AbstractDisplay { // 추상 클래스 AbstractDisplay

  public abstract void open(); // 하위 클래스에 구현을 맡기는 추상 메소드 (1) open

    public abstract void print(); // 하위 클래스에 구현을 맡기는 추상 메소드 (2) print

    public abstract void close(); // 하위 클래스에 구현을 맡기는 추상 메소드 (3) close

    public final void display() { // 추상 클래스에서 구현되고 있는 메소드 display

        open(); // 우선 open하고…

        for (int i = 0; i < 5; i++) { // 5번 print을 반복하고…

            print();                    

        }

        close(); // … 마지막으로 close한다. 이것이 display 메소드에서 구현되고 있는 내용.

    }

}


2. CharDisplay 

- ConcreateClass(구현 클래스)의 역할AbstractClass 역할에서 정의되어 있는 추상 메서드를 구체적으로 구현한다. 여기에서 구현한 메서드는 AbstractClass역의 템플릿 메서드에서 호출된다. 예제 프로그램에서는 CharDisplay 클래스나 StringClass 클래스가 이 역할을 한다.

public class CharDisplay extends AbstractDisplay {  // CharDisplay는 AbstractDisplay의 하위 클래스.

   private char ch;  // 표시해야 할 문자

   public CharDisplay(char ch) { // 생성자에서 전달된 문자 ch을

       this.ch = ch; // 필드에 기억해 둔다.

   }

   public void open() { // 상위 클래스에서는 추상 메소드였다. 여기에서 오버라이드해서 구현.

       System.out.print("<<");                   

   }

   public void print() { // print 메소드도 여기에서 구현한다. 이것이 display에서 반복해서 호출된다.

       System.out.print(ch); // 필드에 기억해 둔 문자를 1개 표시한다.

   }

   public void close() { // close 메소드도 여기에서 구현.

       System.out.println(">>");                  

   }

}


3. StringDisplay 

public class StringDisplay extends AbstractDisplay {  // StringDisplay도 AbstrctDisplay의 하위 클래스.

    private String string;  // 표시해야 할 문자열.

    private int width; // 바이트 단위로 계산한 문자열의 「폭」.

    public StringDisplay(String string) { // 생성자에서 전달된 문자열 string을

        this.string = string; // 필드에 기억.

        this.width = string.getBytes().length; // 그리고 바이트 단위의 폭도 필드에 기억해 두고 나중에 사용한다.

    }

    public void open() { // 오버라이드해서 정의한 open 메소드.

        printLine(); // 이 클래스의 메소드 printLine에서 선을 그리고 있다.

    }

    public void print() { // print 메소드는

        System.out.println("|" + string + "|"); // 필드에 기억해 둔 문자열의 전후에 “|”을 붙여서 표시.

    }

    public void close() { // close 메소드는

        printLine(); // open 처럼 printLine 메소드에서 선을 그리고 있다.

    }

    private void printLine() { // open과 close에서 호출된 printLine 메소드이다. private이기 때문에 이 클래스 안에서만 사용된다.

        System.out.print("+"); // 테두리의 모서리를 표현하는”+” 마크를 표시.

        for (int i = 0; i < width; i++) { // width개의 “-“을 표시하고

            System.out.print("-"); // 테두리 선으로 이용한다.

        }

        System.out.println("+"); // 테두리의 모서리를 표현하는 “+” 마크를 표시.

    }

}


4. Main 

public class Main {

    public static void main(String[] args) {

        AbstractDisplay d1 = new CharDisplay('H'); // 'H'을 가진 CharDisplay 인스턴스를 1개 만든다.

        AbstractDisplay d2 = new StringDisplay("Hello, world."); // “Hello, world.”을 가진 StringDisplay의 인스턴스를 1개 만든다.

        AbstractDisplay d3 = new StringDisplay("안녕하세요."); // “안녕하세요.”를 가진 StringDisplay의 인스턴스를 1개 만든다.

        d1.display(); // d1, d2, d3 모두 AbstractDisplay의 하위클래스의 인스턴스이기 때문에

        d2.display(); // 상속한 display메소드를 호출할 수 있다.

        d3.display(); // 실제 동작은 CharDisplay나 StringDisplay에서 결정한다.

    }

}


실행결과)

<<HHHHH>>

+-------------+

|Hello, world.|

|Hello, world.|

|Hello, world.|

|Hello, world.|

|Hello, world.|

+-------------+

+----------------+

|안녕하세요.|

|안녕하세요.|

|안녕하세요.|

|안녕하세요.|

|안녕하세요.|

+----------------+


[DesignPattern] 프록시 패턴

프록시 패턴


proxy는 대리인이라는 의미이다.

대리인이란 일을 해야 할 본인을 대신(대리)하는 사람이다. 본인이 아니라도 가능한 일을 맡기기 위해서 대리인을 세운다.

대리인은 어디까지나 대리에 지나지 않기 때문에 할 수 있는 일에는 한계가 있다.

대리인이 할 수 있는 범위를 넘는 일이 발생하면, 대리인은 본인한테 와서 상담을 한다.

오브젝트(객체) 지향에서는 '본인'도 '대리인'도 오브젝트(객체)가 된다. 바빠서 일을 할 수 없는 오브젝트 대신에 대리인 오브젝트가 어느 정도 일을 처리하게 된다.




▶ 등장인물

 

 

 Printer

 이름있는 프린터를 나타내는 클래스(본인)

 Printable

 Printer와 PrinterProxy 공통의 인스턴스

 PrinterProxy

 이름있는 프린터를 나타내는 클래스(대리인)

 Main

 동작 테스트용 클래스


▶ 예제 프로그램 해설

아래는 Proxy 패턴을 사용한 예제이다.

이번 예제 프로그램은 '이름이 있는 프린터'이다. 프린터라고 해도 실제로는 화면에 문자열을 표시할 뿐이다. Main 클래스는 PrinterProxy 클래스의 인스턴스(대리인)를 생성한다.

그 인스턴스에 'Alice'라는 이름을 붙이고 그 이름을 표시한다. 그 후 'Bob'이라는 이름으로 변경해서 그 이름을 표시한다. 이름의 설정과 취득에서는 아직 실제 Printer 클래스의 인스턴스(본인)는 생성되지 않는다. 이름의 설정과 취득 부분은 PrinterProxy 클래스가 대리로 실행한다. 마지막에 print 메소드를 호출해서 실제로 프린터를 실행하는 단계가 되어서야 비로소 Printer 클래스는 Printer 클래스의 인스턴스를 생성한다. 그리고 PrinterProxy 클래스와 Printer 클래스를 동일시 하기 위해 Printable 인터페이스가 정의되어 있다.

여기에서는 Printer 클래스의 인스턴스 생성에 많은 시간이 걸린다는 것을 전제로 프로그램을 만든다. 시간이 걸린다는 것을 표현하기 위해서 생성자로부터 heavyJob 메서드를 호출해서 일부러 '무거운일'을 실행시킨다.


1. Printer 클래스

- RealSubject(실제의 주체)의 역할: '대리인'인 Proxy 역할에서 감당할 수 없는 일이 발생했을 때 등장하는 것이 '본인'인 RealSubject 역할이다. 이 역할도 Proxy 역할과 마찬가지로 Subject 역할에서 정해져 있는 인터페이스(API)를 구현한다.

public class Printer implements Printable {

    private String name;

    public Printer() {

        heavyJob("Printer의 인스턴스를 생성 중");

    }

    public Printer(String name) { // 생성자

        this.name = name;

        heavyJob("Printer의 인스턴스 (" + name + ")을 생성 중");

    }

    public void setPrinterName(String name) { // 이름의 설정

        this.name = name;

    }

    public String getPrinterName() { // 이름의 취득

        return name;

    }

    public void print(String string) { // 이름을 붙여 표시

        System.out.println("=== " + name + " ===");

        System.out.println(string);

    }

    private void heavyJob(String msg) {// 무거운 일(의 예정)

        System.out.print(msg);

        for (int i = 0; i < 5; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

            }

            System.out.print(".");

        }

        System.out.println("완료");

    }

}

Printer 클래스는 '본인'을 표시하는 클래스이다. 생성자에서는 앞서 말했듯이 더미의 '무거운 일' 즉, heavyJob을 실행하고 있다. setPrinterName은 이름을 설정하는 메서드이고, getPrinterName은 이름을 취득하는 메서드이다. print 메서드는 프린터의 이름을 붙여서 문자열을 표시하고 있다. heavyJob 메서드는 실행에 5초가 걸리는 무거운 일을 표현하고 있다.


2. Printable 인터페이스

- Subject(주체)의 역할: Proxy 역할과 RealSubject 역할을 동일시 하기 위한 인터페이스(API)를 결정한다.

Subject 역할이 있는 덕분에 Client 역할은 Proxy 역할과 RealSubject 역할의 차이를 의식할 필요가 없다. 

public interface Printable {

    public abstract void setPrinterName(String name); // 이름의 설정

    public abstract String getPrinterName(); // 이름의 취득

    public abstract void print(String string); // 문자열 표시(프린트 아웃)

}

Printable 인터페이스는 PrinterProxy 클래스와 Printer 클래스를 동일시 하기 위한 것이다. setPrinterName 메서드는 이름의 설정, getPrinterName 메서드는 이름의 취득, 그리고 print 메서드는 프린트 아웃(문자열 표시)을 위한 것이다.


3. PrinterProxy 클래스

- Proxy(대리인)의 역할: Proxy의 역할은 Client 역할의 요구를 할 수 있는 만큼 처리를 한다. 만약, 자신만으로 처리할 수 없으면 Proxy 역할은 RealSubject 역할에게 처리를 맡긴다. Proxy 역할은 정말로 RealSubject 역할이 필요해지면 그때 RealSubject 역할을 생성한다. Proxy 역할은 Subject 역할에서 정해지는 인터페이스(API)를 구현한다.

public class PrinterProxy implements Printable {

    private String name; // 이름

    private Printer real; // 「본인」

    public PrinterProxy() {

    }

    public PrinterProxy(String name) { // 생성자

        this.name = name;

    }

    public synchronized void setPrinterName(String name) {  // 이름의 설정

        if (real != null) {

            real.setPrinterName(name);  // 「본인」에게도 설정한다

        }

        this.name = name;

    }

    public String getPrinterName() {  // 이름의 설정

        return name;

    }

    public void print(String string) { // 표시

        realize();

        real.print(string);

    }

    private synchronized void realize() { // 「본인」을 생성

        if (real == null) {            

            real = new Printer(name);

        }                           

    }

}

Proxy 패턴의 중심은 PrinterProxy 클래스이다. 

PrinterProxy 클래스는 대리인의 역할을 수행하며, Printable 인터페이스를 구현한다. name 필드는 이름을 저장하고, real 필드는 '본인'을 저장한다.

생성자는 이름을 설정한다.(이 시점에서 '본인'은 만들어지지 않는다.)

setPrinterName 메서드는 새로운 이름을 설정한다. 만약,real이 null이 아니면(즉, '본인'이 이미 만들어져 있으면), 본인에 대해서도 그 이름을 설정한다.

그러나 real이 null이면(즉, '본인'이 아직 만들어져 있지 않으면), 자신(PrinterProxy의 인스턴스)의 name 필드에만 이름을 설정한다.

getPrinterName 메서드는 자신의 name 필드의 값을 반환할 뿐이다.

print 메서드는 대리인이 가능한 일의 범위를 넘어서기 때문에 여기에서 realize 메서드를 호출해서 본인을 생성한다. realize는 '현실화하다'라는 의미이다.

realize 메서드를 실행한 후 real 필드에는 본인(Printer 클래스의 인스턴스)이 저장되어 있기 때문에 real.print를 호출한다. 이것은 위임이다.

setPrinterName과 getPrinterName을 여러 차례 호출해도, Printer의 인스턴스는 생성되지 않는다.

Printer의 인스턴스가 생성되는 것은 '본인'이 정말로 필요할 때이다.(본인이 생성되었는지 아닌지를 PrinterProxy의 이용자는 전혀 알 수 없고, 알 필요도 없다.)

realize 메서드는 단순하다. real 필드가 null이면 new Printer에 의해 Printer의 인스턴스를 만든다. 그리고 real 필드가 null이 아니면 (즉, 이미 만들어져 있으면) 아무 처리도 하지 않는다.

기억해야 할 점은 Printer 클래스는 PrinterProxy의 존재를 모른다는 점이다. 자신이 PrinterProxy을 경유해서 호출되고 있는지 아니면 직접 호출되고 있는지 Printer 클래스는 모른다.

반면에 PrinterProxy 클래스는 Printer 클래스를 알고 있다. 왜냐하면 PrinterProxy 클래스의 real 필드는 Printer형이고, PrinterProxy 클래스의 소스 코드안에는 Printer 클래스 이름이 기술되어 있기 때문이다. 이처럼 PrinterProxy 클래스는 Printer 클래스와 깊이 관련되 부품이다.


cf.) PrinterProxy 클래스에서 setPrinterName 메서드와 realize 메서드가 synchronized 메서드로 되어 있는 이유

synchronized 메서드로 하지 않은 경우, 복수의 스레드로부터 setPrintName과 realize가 개별적으로 호출되면, PrinterProxy 클래스의 name과 Printer 클래스의 name에 차이가 생길 경우가 있다.

최초에 PrinterProxy의 name 필드의 값이 'Alice'이고, real 필드의 값이 null(즉, Printer 클래스의 인스턴스는 아직 생성되어 있지 않다.)이라고 가정하자.

스레드 A가 setPrinterName("Bob")을 실행함과 동시에 스레드 B가(print 메서드 경유로) realize 메서드를 호출했다고 하자. 만약 스레드가 교체되면 PrinterProxy 클래스의 name 필드의 값은 "Bob"이 되지만, Printer의 name 필드의 값은 "Alice"로 되고 만다.

setPrintName 메서드와 realize 메서드를 synchronized 메서드로 하면 이와 같은 스레드 교체가 발생하지 않는다. synchronized 메서드에 의해 real 필드의 값에 대한 판단과 값의 변경이 제각각 실행되지 않도록 하고 있다. synchronized 메서드를 이용해서 real 필드를 지키고 있는 것이 된다.


4. Main 클래스

- Client(의뢰인)의 역할: Proxy 패턴을 이용하는 역할이다.

public class Main {

    public static void main(String[] args) {

        Printable p = new PrinterProxy("Alice");

        System.out.println("이름은 현재 " + p.getPrinterName() + "입니다.");

        p.setPrinterName("Bob");

        System.out.println("이름은 현재 " + p.getPrinterName() + "입니다.");

        p.print("Hello, world.");

    }

}

Main 클래스는 PrinterProxy를 경유해서 Printer를 이용하는 클래스이다. 이 클래스는 처음에 PrinterProxy를 생성하고, getPrinterName을 이용해서 이름을 표시한다.

그리고 나서 setPrinterName으로 이름을 설정하고, 마지막에 print로 "Hello, world"라고 표시한다.

실행결과를 보고 이름의 설정과 표시를 하는 동안에는 Printer의 인스턴가 생성되지 않고, print 메서드를 호출한 후에 생성되고 있는 점을 확인하자.

실행결과)

이름은 현재 Alice입니다.

이름은 현재 Bob입니다.

Printer의 인스턴스 (Bob)을 생성 중.....완료

=== Bob ===

Hello, world.



[DesignPattern] 싱글턴 패턴

싱글턴 패턴


프로그램을 실행할 때 보통은 많은 인스턴스가 생성된다. 예를 들어 문자열을 표시하는 java.lang.String 클래스의 인스턴스는 문자열 1개에 대해서 1개가 생성되기 때문에 문자열이 1000개 등장하는 프로그램이라면, 1000개의 인스턴스가 만들어진다.

그러나 클래스의 인스턴스가 단 하나만 필요한 경우도 있다. 그것은 시스템 안에서 1개밖에 존재하지 않는 것을 프로그램으로 표현하고 싶을 때이다.

즉, 지정한 클래스의 인스턴스 절대로 1개 밖에 존재하는 않는 것을 보증하고 싶을 때 사용한다. Singleton이란 요소를 1개 밖에 가지고 있지 않은 집합을 의미한다.



Sington 클래스에서는 인스턴스를 1개 밖에 만들 수 없으며, sington은 static 필드로서 Singleton 클래스의 인스턴스에서 초기화된다. 이 초기화는 Sington 클래스를 로드할 때 1회만 실행된다.

Sington 클래스의 생성자는 private로 되어 있다. 이것은 Sington 클래스 외부에서 생성자의 호출을 금지하기 위해서이다.

Singleton 클래스의 유일한 인스턴스를 얻는 메서드로서 getInstance가 준비되어 있다. 예제 프로그램에서는 메서드의 이름을 getInstance로 했지만, 반드시 이 이름일 필요는 없다. 그러나 유일한 인스턴스를 얻을 메서드는 필요하다.


public class Singleton {

private static Singleton singleton = null;

// 외부에서는 new Singleton()를 이용하여 생성자를 호출할 수 없다.

private Singleton(){

System.out.println("인스턴스를 생성하였습니다.");

}


// getInstance 메서드는 singleton 인스턴스가 이미 생성되어 있는지를 검사한다.

// 만약 처음 호출되어 아직 인스턴스가 생성되지 않은 상황이라면 생성자를 호출해 인스턴스를 생성한다.

// 이렇게 생성된 인스턴스는 정적 변수 singleton에 의해 참조가 된다.

// 만약 인스턴스가 생성되었다면 singleton 변수에서 참조하는 인스턴스를 반환한다.

public static Singleton getInstance(){

if(singleton == null){

singleton = new Singleton();

}

return singleton;

}

}

이 코드에서 주의 깊게 살펴봐야 할 점은 getInstance 메서드와 singleton 변수가 static 타입으로 선언되었다는 점이다.

이와 같이 static으로 선언된 메서드나 변수를 각각 정적 메서드, 정적 변수라고 한다.

정적 메서드나 정적 변수는 이들이 구체적인 인스턴스에 속하는 영역이 아니고 클래스 자체에 속한다는 의미이다. 또한 클래스의 인스턴스가 생성될 때마다 생성되는 것이 아니라 딱 한 번만 생성되며 클래스의 인스턴스가 생성되기 전에 초기화된다. 그래서 정적 메서드, 정적 변수는 클래스에서 생성된 모든 인스턴스들에게 공유된다. 따라서 클래스의 인스턴스를 통하지 않고서도 메서드를 실행할 수 있고 변수를 참조할 수 있다.

우리의 목적은 단 하나의 객체만 생성해 어디에서든지 참조할 수 있게 하는 것으로 처음에 객체를 만들려면 getInstance 메서드가 정적 메서드로 선언되어 있어야 한다.


public class Main {

public static void main(String[] args){

System.out.println("Start");

Singleton obj1 = Singleton.getInstance();

Singleton obj2 = Singleton.getInstance();

if(obj1 == obj2){

System.out.println("obj1과 obj2는 같은 인스턴스 입니다.");

} else {

System.out.println("obj1과 obj2는 다른 인스턴스 입니다.");

}

System.out.println("End");

}

}

실행결과)

Start

인스턴스를 생성하였습니다.

obj1과 obj2는 같은 인스턴스 입니다.

End


Singleton 패턴에는 Singleton의 역할만이 존재한다. Singleton 역할은 유일한 인스턴스을 얻기 위한 static 메서드를 가지고 있다. 이 메서드는 언제나 동일한 인스턴스를 반환한다.

Singleton 패턴에서는 인스턴스의 수를 제한하고 있다. 일부러 제한적인 프로그래밍을 하는 이유는 제한을 한다는 것은 전제가 되는 조건을 늘린다는 의미이다.

복수의 인스턴스가 존재하면 인스턴스들이 서로 영향을 미치고, 뜻하지 않은 버그가 발생할 가능성이 있다. 그러나 인스턴스가 1개 밖에 없다라는 보증이 있다면 그 전제조건 아래에서 프로그래밍을 할 수 있다.


cf.) 유일한 인스턴스는 언제 생성되는가?

예제 프로그램의 실행결과 'Start'를 표지하고 나서 '인스턴스를 생성했습니다.'라고 표시하고 있다. 프로그램의 실행 개시 후 최초로 getInstance 메서드를 호출했을 때 Sington 클래스는 초기화된다. 그리고 이 때 static 필드의 초기화가 이루어지고 유일한 인스턴스가 만들어진다.


[DesignPattern] 디자인 패턴의 분류

디자인 패턴의 분류


많은 디자인 패턴을 크게 범주별로 분리를 할 수 있다.

범주별로 분리를 해보면서 패턴의 특징을 쉽게 파악할 수 있다.



1. 생성, 행동, 구조 관련 패턴으로 분류


- 생성 관련 패턴 (Creational Pattern) : 객체 인스턴스 생성을 위한 패턴으로, 클라이언트와 그 클라이언트에서 생성해야 할 객체 인스턴스 사이의 연결을 끊어주는 패턴


싱글턴, 팩토리 메소드, 추상 팩토리, 프로토타입, 빌더 패턴


- 행동 관련 패턴 (Behavioral Pattern) : 클래스와 객체들이 상호작용하는 방법 및 역할을 분담하는 방법과 관련된 패턴


스트래티지, 옵저버, 스테이트, 커맨드, 이터레이터, 템플릿 메소드, 인터프리터, 미디에이터, 역할 변경, 메멘토, 비지터


- 구조 관련 패턴 (Structural Pattern) : 클래스 및 객체들을 구성을 통해서 더 큰 구조로 만들 수 있게 해 주는 것과 관련된 패턴


데코레이터, 어댑터, 컴포지트, 퍼사드, 프록시, 브리지, 플라이웨이트



2. 클래스, 객체 패턴으로 분류


- 클래스 패턴 (Class Pattern) : 클래스 사이의 관계가 상속을 통해서 어떤 식으로 정의되는지를 다룬다. 클래스 패턴은 컴파일시에 관계가 결정된다.


템플릿 메소드, 팩토리 메소드, 어댑터, 인터프리터


- 객체 패턴 (Object Patterns) : 객체 사이의 관계를 다루며, 객체 사이의 관계는 보통 구성을 통해서 정의된다. 객체 패턴에서는 일반적으로 실행 중에 관계가 생성되기 때문에 더 동적이고 유연하다.


스트래티지, 옵저버, 데코레이터, 프록시, 컴포지트, 이터레이터, 스테이트, 추상 팩토리, 싱글턴, 비지터, 메멘토, 역할 사슬, 브리지, 미디에이터, 플라이웨이트, 프로토타입, 빌더



[DesignPattern] 객체지향 모델링

객체지향 모델링



1. 모델링의 역할

(1) 서로의 해석을 공유해 합의를 이루거나 해석의 타당성을 검토한다.

(2) 현재 시스템 또는 앞으로 개발할 시스템의 원하는 모습을 가시화한다.

(3) 시스템의 구조와 행위를 명세할 수 있으며 시스템을 구축하는 틀을 제공한다.


2. UML 일반

(1) UML 정의

- 소프트웨어 청사진을 작성하는 표준언어

- 소프트웨어 중심 시스템의 산출물을 가시화하고, 명세화하고,구축하고, 문서화하는데 사용

- 가시화 언어 : UML은 소프트웨어의 개념모델을 가시적인 그래픽 형태로 작성하여 참여자들의 오류없고 원활한 의사소통이 이루어지게 하는 언어

- 명세화 언어 : UML은 소프트웨어 개발과정인, 분석, 설계, 구현 단계의 각 과정에서 필요한 모델을 정확하고 완전하게 명세할 수 있게하는 언어

- 구축 언어 :  UML 언어는 다양한 객체지향 프로그래밍 언어로 변환 가능 UML로 명세된 설계 모델은 구축하려는 프로그램 코드로 순변환하여 (순공학) 구축에 사용기 구축된 코드를 UML모델로 역변환(역공학) 하여 분석하게 할 수도 있다.

- 문서화 언어 :  UML은 여러 개발자들 간의 통제, 평가 및 의사소통에 필요한 문서화를 할 수 있는 언어


(2) UML 필요성

소프트웨어 시스템을 만들기 위해서 어휘와 규칙을 두어 시스템을 개념적/물리적으로 표현하는 모델이 필요

시스템의 구조적 문제와 프로젝트 팀내의 의사 소통, 소프트웨어 구조의 재사용 문제를 해결


(3) UML 구성요소

사물 : 추상적 개념으로써 모델 구성의 기본 요소 시스템의 구조, 행위를 표현하고 개념들을 그룹화 하는 것 부가적인 설명을 위한 것들이 있다.

관계 : 사물들 간의 연결 관계를 추상화 한 것

다이어그램 : 관련성이 있는 사물들 간의 상호관계를 도형 형태로 표현


3. 클래스 다이어그램

클래스 다이어그램은 시간에 따라 변하지 않는 시스템의 정적인 면을 보여주는 대표적인 UML 구조 다이어그램이다. 클래스 다이어그램은 시스템을 구성하는 클래스와 그들 사이의 관계를 보여준다. 주요 구성요소는 클래스와 관계다.

(1) 클래스


1) 구체적인 가시화

public class Cource{

private String id;

private String name;

private int numOfStudents = 0;

public void addStudents(Student student{}

public void deleteStudents(integer id){}

}


2) 접근제어자

- private : 이 클래스에서 생성된 객체들만 접근 가능

+ public : 어떤 클래스의 객체이든 접근 가능

# protected : 이 클래스와 동일 패키지에 있거나 상속 관계에 있는 하위 클래스의 객체들만 접근 가능

~ package : 동일 패키지에 있는 클래의 객체들만 접근 가능


3) 다중성 표시


4. 관계

클래스 하나로만 이루어지는 시스템은 존재하지 않는다. 한 사함이 모든 일을 처리할 때 보다 여러 사람이 모였을 때 일을 좀더 효과적으로 처리할 수 있듯이 다수의 클래스가 모인 시스템이 훨씬 효율적이다. 객체지향 시스템도 여러 개의 클래스가 서로 긴밀한 관계를 맺어 기능을 수행한다.


(1) 연관관계 

 

실선: 양방향 연관관계- 두 클래스 객체들이 서로의 존재를 인식한다는 의미이다.

화살표: 단방향 연관관계 - 한쪽은 다른 객체의 존재를 알지만, 다른쪽은 그 존재를 모른다.

클래스들이 개념상 서로 연결되었음을 나타낸다. 실선이나 화살표로 표시하며 보통은 한 클래스가 다른 클래스에서 제공하는 기능을 사용하는 상황일 때 표시한다.

연관관계의 역할 이름은 연관된 클래스 객체들이 서로를 참조할 수 있는 속성의 이름으로 활용할 수 있다. 일회성이 아닌 지속적으로 유지되는 구조적 관계이다.


예제1) 단방향 연관관계

public class Person {

private Phone homePhone;

Private Phone OfficePhone;

public Phone getHomePhone() {

return homePhone;

}


public void setHomePhone(Phone homePhone) {

this.homePhone = homePhone;

}


public Phone getPhone() {

return Phone;

}


public void setPhone(Private phone) {

Phone = phone;

}

}

person은 phone의 존재를 알지만, phone은 person의 존재를 모른다.

따라서 person 객체는 phone 객체들을 참조할 수 있도록 구성해야 하지만, phone 클래스는 person 객체를 참조할 속성이 존재하지 않아도 된다.


예제2) 양방향 연관관계

class Student{

private Professor advisor;


public void setAdvisor(Professor advisor) {

this.advisor = advisor;

}

public void advise(String msg){

System.out.println(msg);

}

}


public class Professor {

private Student student;

public void setStudent(Student student) {

this.student = student;

student.setAdvisor(this);

}

public void advise(){

student.advise("상담내용은 여기에");

}

public static void main(String[] args){

Professor gildong = new Professor();

Student bap = new Student();

gildong.setStudent(bap);

gildong.advise();

}

}

이 코드의 연관관계는 양방향 연관관계이므로 Professor  클래스 객체에서 Student 클래스 객체를 참조할 수 있는 속성(student)이 있고, Student 클래스 객체에서 Professor 클래스 객체를 참조할 수 있는 속성이 있다. 또한 이 속성의 이름이 역할 이름을 활용한 것임을 알 수 있다.


예제3) 다대다 연관관계

import java.util.ArrayList;


public class Student {

private String name;

private ArrayList<Course> courses;

public Student(String name){

this.name = name;

courses = new ArrayList<Course>();

}

public void registerCourse(Course course){

courses.add(course);

course.addStudent(this);

}

public void dropCourse(Course course){

if (courses.contains(course)){

courses.remove(course);

course.removeStudent(this);

}

}

public ArrayList<Course> getCourses(){

return courses;

}

}


class Course{

private String name;

private ArrayList<Student> students;

public Course(String name){

this.name = name;

students = new ArrayList<Student>();

}

public void addStudent(Student student){

students.add(student);

}

public void removeStudent(Student student){

students.remove(student);

}

public ArrayList<Student> getStudent(){

return students;

}

public String getName(){

return name;

}

}

이 코드는 Student 클래스와 Course 클래스의 연관관계가 양방향 연관 관계이기 때문에 양쪽 클래스에서 서로를 참조할 수 있는 속성을 정의했다.  Studnet 객체 하나에 하나 이상의 Course 객체가 연관 되어 있기 때문에 다중성을 구현했으며(반대도 같음), Student, Course 클래스에 대표적인 컬렉션 자료구조인 ArrayList를 이용해 여러 개의 Student, Course 클래스 객체를 참조할 수 있게 했다. 


(2) 일반화관계

한 클래스가 다른 클래스를 포함하는 상위 개념일 때 두 클래스 사이에는 일반화 관계가 존재한다. 일반화 관계가 존재할 때 자식이라 불리는 클래스는 부모라 불리는 클래스로부터 속성과 연산을 물려받을 수 있다.특수화, 일반화된 사물간의 관계를 표현한다. 이를 'is a kind of' 관계라고도 한다. 상위 클래스를 상속받는 하위 클래스에서는 각자의 특수한 스킬이나 액션에 따라서 다른 멤버변수나 메서드를 추가하여 구현을 하거나, 상속받은 추상 메서드를 오버라이드해서 구현하면 된다.

ex.) 세탁기 is kind of 가전제품

       프로그래머 is kind of 사람


예제)

public abstract class HomeAppliances {

private int serialNo;

private String manufacturer;

private int year;

public abstract void turnOn();

public abstract void turnOff();

}


public class Washer extends HomeAppliances{

public void turnOn(){

// 내용 구현

}

public void turnOff(){

// 내용 구현

}

}


(3) 집합관계

집합관계는 UML 연관 관계의 특별 경우로 전체와 부분의 관계를 명확하게 명시하고자 할 때 사용한다.

집약관계와 합성관계 두 종류의 집합관계가 존재한다.


1) 집약관계

한 객체가 다은 객체를 포함하는 것을 나타낸다. '전체', '부분'과의 관계이며 '전체'를 가리키는 클래스 방향에 빈 마름모 표시를 한다.

부분 객체를 여러 전체 객체가 공유할 수 있다. 이때 전체 객체의 라이프타임과 부분 객체의 라이프타임은 독립적이다.

즉, 전체 객체가 메모리에서 사라진다 해도 부분 객체는 사라지지 않는다.


public class Computer {

private Mainboard mb;

private CPU c;

private Memory m;

private PowerSupply ps;

public Computer(Mainboard mb, CPU c, Memory m, PowerSupply ps){

this.mb = mb;

this.c = c;

this.m = m;

this.ps = ps;

}

}

Computer 객체가 사라져도 부품을 구성하는 Mainboard 객체, CPU 객체, Memory 객체, PowerSupply 객체는 사라지지 않는다.

외부에서 이들 객체에 대한 참조만 받아 사용했기 때문이다. 즉, 전체를 표현하는 Computer 객체의 라이프타임과 부분 객체의 라이프타임은 무관하다.


2) 합성관계

합성관계는 전체를 가리키는 클래스 방향에 체워진 마름모로 표시되면 부분 객체가 전제 객체에 속하는 관계이다.

따라서 전체 객체가 사라지면 부분 객체도 사라지는 경우를 의미한다.  부분 객체를 여러 전체 객체가 공유할 수 없다.

이때 부분 객체의 라이프타임은 전체 객체의 라이프타임에 의존한다. 즉, 전체 객체가 없어지면 부분 객체도 없어진다.


public class Computer {

private Mainboard mb;

private CPU c;

private Memory m;

private PowerSupply ps;

public Computer(){

this.mb = new MainBoard();

this.c = new CPU();

this.m = new Memory();

this.ps = new PowerSupply();

}

}

이 코드에서 생성자가 컴퓨터의 부품이 되는 Coumputer 객체들을 생성해 적절한 속성에 바인딩한다는 점이다.

가령 c1 = new Computer()으로 Computer 객체가 생성되면 c1의 부품을 이루는 MainBoard 객체, CPU 객체, Memory 객체, PowerSupply객체가 생성된다.

이러한 부품 객체들은 Cumputer 클래스의 객체 c1이 사라지면 같이 사라진다. 즉, 부품 객체들의 라이프타임이 Computer 객체의 라이프타임에 의존하는 관계가 형성된다.


(4) 의존관계

클래스가 연관, 상속, 집합 관계로 엮여 있는 것은 아니지만, 한 객체가 다른객체를 소유하지는 않지만, 다른객체가 변경되면 그것을 사용하는 다른 곳도 같이 변경해줘야 하는 관계를 표현할 때 주로 사용한다. 단, 주의해야 할 점은 연관관계와 달리 의존관계의 경우에는 클래스 인스턴스의 레퍼런스를 유지하고 있지 않다는 점이다. 

레퍼런스를 계속적으로 유지하게 되면 이는 연관관계로 표현해야 한다.


주로 다음과 같은 세 가지 경우에 의존 관계로 표현한다.

1 한 클래스의 메소드가 다른 클래스의 객체를 인자로 받아 그 메소드를 사용한다.( 가장 일반적 ) 

2 한 클래스의 메소드가 또 다른 클래스의 객체를 반환한다.

3 다른 클래스의 메소드가 또 다른 클래스의 객체를 반환한다. 이때 이 메소드를 호출하여 반환되는 객체의 메소드를 사용한다.


public class Car{

...

public void fillGas(GasPump p){

p.getGas(amount);

...

}

}

자동차에 주유할 때 특정 주유소에 있는 특정 주유기만 고집해 매번 주유할 수는 없을 것이다. 이런 경우라면 주유 서비스를 받을 때 마다 이용하는 주유기가 매번 달라지는 것을 의미하며

객체지향 프로그램에서는 사용되는 주유기를 인자난 지역 객체로 생성해 구현할 것이다.


cf.) 의존관계와 연관관계

- 의존관계: 클래스의 인스턴스의 레퍼런스를 유지하지 않음

- 집합관계: 클래스의 인스턴스의 레퍼런스를 유지함


한 클래스의 객체를 다른 클래스 객체의 속성에서 참조하는 경우에는 참조하는 객체가 변경되지 않는 한 두 클래스의 객체들이 오랜 기간 동안 협력 관계를 통해 기능을 수행한다고 볼 수 있다.

예를 들면, 자동차(Car 클래스)를 소유한 사람(Person 클래스)이 자동차를 이용해 출근한다고 할 경우 다음 날 출근할 때도 어제 사용한 자동차를 타고 출근할 것이다.

매번 출근할 때마다 다른 자동차를 사용하는 경우는 거의 없을 것이다. 이런 경우 사람과 자동차의 관계가 연관 관계이며 Person 클래스의 속성으로 Car 객체를 참조한다.


public class Person{

private Car owns; // 이 속성으로 연관 관계가 설정된다.

public void setCar(Car car){

this.owns = car;

}


public Car getCar(){

return this.owns;

}

}

--> 연관 관계는 오랜 시간 동안 같이할 객체와의 관계며 의존 관계는 짧은 기간 동안 이용하는 관계다.


(5) 실체화 관계

- 인터페이스를 구현하는 관계를 표현한다.

- 상속의 개념과 비슷하지만 상속과 다른점은, 

-> 상속 : 직접 상위 클래스를 상속받아서 Unit 클래스의 기능을 포함한다.(멤버변수 및 메소드 모두 상속됨)

-> 인터페이스 : 서로 다른 클래스라도 인터페이스만 준수하면(인터페이스의 함수들을 모두 구현하면) 동일한 기능들이 구현될수가 있다. (메소드 같은 기능들만 구현이 됨)

즉, 완전히 다른 클래스에 공통적인 기능(메서드)을 부여하는 것이다.

Building이라는 것에서 Barraks,Factory,Bunker가 상속되어서  멤버 변수인 Health와 Ammor의 데이터와 Contruct(건물짓기), UnderAttack(공격받기) 기능이 부여가 됩니다. 여기에서 테란건물의특수기능인 건물을 상공으로 띄워서 이동하고 다시 땅으로 착지하는 기능을 추가하려고 했을때, Building의 상위 클래스에 메서드를 추가하게 되면 Bunker라는 건물은 이동이 원래 불가능한데 그 기능을 갖게 되서 문제가 발생한다. ( 단순히 Overide해서 아무 기능이 없게 해도 되지만 상속받는 모든것에 그렇게 처리를 하는것도 효율적이지 못한거 같다. ) 이때 인터페이스를 구현해서 이동이 가능한 건물에만 인터페이스를 구현하게 되면 된다. 인터페이스를 구현하는 곳에서는 Move,Land,Fly 메서드를 반드시 구현해야 한다.