Proxy 패턴은 단순하면서도 자주 쓰이는 패턴이고 활용 방식도 다양하다. 이 글에서는 Proxy 패턴과 함께 그 다양한 활용 방법에 대해서 이야기 해 보도록 하겠다.


Proxy 패턴의 클래스 다이어그램


Proxy 패턴의 구현(기본형)

interface Subject {

    public Object action();

}

class RealSubject implements Subject {

    public RealSubject(){}

    public Object action() { /* do something */ return null;}

}

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){

        realSubject = new RealSubject();

    }

    public Object action() {

        return realSubject.action();

    }

}


Proxy 패턴은 Proxy 패턴의 기본형을 어떤 방식으로 변형하느냐에 따라 달라진다.

Proxy 패턴의 변형에는 두가지 방향이 있다.


1. RealSubject의 생성 시점

2. Proxy 객체의 action()에서 하는 일


위의 두가지 변형을 조합함으로써 활용도를 다르게 가지고 갈 수 있다.


동적 생성 프록시

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        if(realSubject == null){

            realSubject = new RealSubject();

        }

        return realSubject.action();

    }

}

동적 생성 프록시는 프록시 객체가 실제 객체를 생성하지 않고 시작한다. 그리고 실제 요청(action() 메소드 호출)이 들어 왔을 때 실제 객체를 생성한다. 이 구현은 실제 객체의 생성에 많은 자원이 소모 되지만 사용 빈도는 낮을 때 쓰는 방식이다.


자원관리 프록시

class RealSubject implements Subject {

    private String fileName;

    @Override

    public void action() {

        System.out.println("Displaying " + fileName);

    }

    public void loadFromDisk(String fileName){

        this.fileName = fileName;

        System.out.println("Loading " + fileName);

    }

}

class Proxy implements Subject{

    private RealSubject realSubject;

    private String fileName = "";

    public Proxy(){

        realSubject = new RealSubject();

    }

   

    @Override

    public void action() {

        realSubject.action();

    }

   

    public void loadFromDisk(String fileName){

        if(this.fileName.equals(fileName)) return;

        this.fileName = fileName;

        realSubject.loadFromDisk(fileName);

    }

}

자원관리 프록시는 실제 객체가 비용이 많이 드는 자원에 대한 생성을 담당할 때 쓰인다. 실제 객체는 기본적인 자원 생성(위 예제에서는 loadFromDisk() 메소드)를 담당하고, 프록시는 이 자원이 이미 생성되었는지를 체크한다. 만약 이미 생성된 자원이라면 자원에 대한 생성을 스킵한다. 이를 통해서 불필요하게 자원을 중복으로 생성하는 것을 방지한다.


가상 프록시

class Proxy implements Subject{

//    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        return virtualAction();

    }

   

    private Object virtualAction(){ /* do something */ return null; }

}

실제 객체가 여러가지 이유로 존재하지 않을 경우에 사용되는 방식이다. 특히 여러 단위의 조직이 협업을 할 때 인터페이스는 정의되어 있으나 실제 구현은 아직 되어 있지 않은 경우에 이 가상 프록시를 사용한다. 가상 프록시에 시뮬레이션 된 동작을 하도록 구현해 두고 개발을 진행한 후, 실제 객체가 완성된 이후에 프록시를 실제 객체로 대체 시킨다. 이런 방식을 통해서 실제 객체를 붙여 보기 전에 발생할 수 있는 문제점들을 미리 테스트 해 볼 수 있기 때문에 통합 작업에서 발생하는 문제점들을 줄일 수 있다.


원격 프록시

class Proxy implements Subject{

//    private RealSubject realSubject;

    public Proxy(){}

    public Object action() {

        return remoteAction();

    }

   

    private Object remoteAction(){

        connect();

        Object result = getData();

        disconnect();

        return result;

    }

   

    private void connect(){/* connect to remote */}

    private Object getData(){/* data request and wait response */ return null;}

    private void disconnect(){/* disconnect from remote */}

}

프록시가 마치 원격에 있는 실제 객체처럼 동작하도록 하는 방법이다. 프록시 객체를 사용하는 객체들은 실제 객체와 동일한 인터페이스를 통해 프록시를 사용하고, 데이터 통신이나 변환과 같은 작업은 프록시 객체 내부에서 수행하도록 한다.


AOP(Aspect Oriented Programming) 프록시

class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(){

        realSubject = new RealSubject();

    }

    public Object action() {

        preProcess();

        Object result = realSubject.action();

        postProcess();

        return result;

    }

    private void preProcess(){/* 선행 작업 */}

    private void postProcess(){/* 후행 작업 */}

}

AOP는 모든 객체에 공통으로 적용되는 기능들을 구현하는 개발 방법을 말한다. 예를 들어 어떤 객체를 멀티 쓰레딩 환경에서 보호해야 한다거나, 어떤 메소드 호출이 걸리는 시간을 측정하거나, 어떤 동작에 대한 트랜젝션을 작성하는 등의 경우이다. 이런 것들은 실제 객체의 행위와 별개로 이루어질 수 있다.

위의 코드에서 보면 action() 메소드가 preProcess() 메소드를 먼저 호출 한 후 realSubject의 action()을 호출한다. 그리고 이후에 postProcess() 메소드를 호출하도록 되어 있다. 이 preProcess()와 postProcess()에 어떤 작업을 구현해 넣느냐에 따라서 관점(Aspect)이 달라진다. 만약 mutex.lock()과 mutex.unlock()을 넣는다면 멀티쓰레드 안정성을 제공할 수 있다. 만약 preProcess()와 postProcess()의 호출 시각을 저장하고 둘의 차를 계산한다면 action() 메소드 실행에 걸린 시간을 측정할 수 있다.

이렇게 실제 객체는 그대로 두고 모든 객체들이 공통으로 수행해야 할 일들을 프록시 객체를 통해서 구현할 수 있다.(실제 Spring과 같은 프레임워크에서 AOP를 구현하는 방식은 reflection을 이용하는 방식이다. 단지 여기서는 같은 형태의 구현을 프록시로 할 수 있다는 점 만 보여주는 것이다.)

'5.디자인패턴' 카테고리의 다른 글

Iterator 패턴  (0) 2016.09.17
Enum Abstract Factory 패턴  (0) 2016.09.16
Observer 패턴  (0) 2016.09.16
Abstract Factory 패턴  (0) 2016.09.16
Factory Method 패턴  (4) 2016.09.16
Posted by 이세영2
,

Observer 패턴

5.디자인패턴 2016. 9. 16. 12:38

Observer 패턴은 관찰 대상 객체에 변경 사항이 발생했을 때 이벤트 형태로 알림을 받는 패턴이다. Push / Pool 방식의 데이터 처리 방식 중에서 Push에 해당한다. 소프트웨어에서 데이터 처리에서는 너무 자주 나타나는 패턴이기 때문에 자주 쓰이고, 그렇기 때문에 사용에 주의해야 하는 패턴이다. Observer 패턴을 너무 자주 사용하면 구조와 동작을 알아보기 힘들어진다.

일반적으로 Observer 패턴에서 관찰 대상이 되는 객체(Observerable) 한 개에 대하여 다수의 관찰자 객체(Observer)가 존재한다. 다수의 관찰자 객체들은 모두 같은 타입으로 취급되어야만 구현이 단순해진다. 따라서 관찰자들은 모두 같은 인터페이스나 상위 클래스를 상속 받아 구현된다. 관찰 대상으로부터 이벤트가 발생하여 관찰자 객체에게 전달되었을 때 이 데이터를 처리하는 내용은 개별 관찰자 객체들이 자신에 맞게 구현한다.


Observer 패턴의 클래스 다이어그램


Observer 패턴의 구현

interface IObserver{

    public void notify(Object data);

}

class Observable{

    private List<IObserver> observers = new ArrayList<IObserver>();

   

    public void registObserver(IObserver observer){ observers.add(observer); }

   

    String []table = {"a","b","c","d","e"};

    public void update(String data, int index){

        table[index] = data;

        onUpdate();

    }

    public void onUpdate(){

        for(IObserver observer : observers){

            observer.notify(table);

        }

    }

}

class Graph implements IObserver{

    public Graph(Observable obervable){ obervable.registObserver(this); }

    public void notify(Object data){

        String[] table = (String[])data;

        System.out.println("Graph : ");

        for(int i = 0; i < 5; i++) System.out.println(table[i]);

    }

}

class Display implements IObserver{

    public Display(Observable obervable){ obervable.registObserver(this); }

    public void notify(Object data){

        String[] table = (String[])data;

        System.out.println("Display : ");

        for(int i = 0; i < 5; i++) System.out.println(table[i]);

    }

} 


실행 방법

public static void main(String[] args) {

    Observable observable = new Observable();

    IObserver graph = new Graph(observable);

    IObserver display = new Display(observable);

    observable.update("abc", 1);

    observable.update("def", 2);

    observable.update("ghi", 3);

}


위의 코드에서 관찰 대상은 Observable이고, 관찰자 인터페이스는 IObserver로 선언되어 있다. 그리고 관찰자 구체 클래스는 Graph와 Display이다. 위의 코드에서는 우선 관찰 대상인 Observable 객체가 먼저 생성되어야 한다. 그리고 관찰자 객체들은 생성자에서 Observable 객체를 매개변수로 받은 후, Observable 객체에 자신을 관찰자로 등록한다.(Observable 객체에 관찰자를 등록 시키는 방법은 꼭 생성자를 이용한 방법이 아니어도 좋다.) 이후 관찰 대상에 이벤트가 발생하면 각 관찰자 객체들의 notify 함수를 통해서 발생한 이벤트를 전달해 준다.

'5.디자인패턴' 카테고리의 다른 글

Enum Abstract Factory 패턴  (0) 2016.09.16
Proxy 패턴과 그 활용  (0) 2016.09.16
Abstract Factory 패턴  (0) 2016.09.16
Factory Method 패턴  (4) 2016.09.16
Memento 패턴  (2) 2016.09.13
Posted by 이세영2
,

Abstract Factory 패턴은 연관성이 있는 객체군이 여러벌 있을 경우 각각의 구체 Factory 클래스를 통해 동일한 객체군을 생성하는 패턴이다.

예를 들어 윈도우 화면을 만든다고 생각해 보자. 윈도우에는 버튼도 들어갈 수 있고 에디터 박스도 들어갈 수 있다. 실행 환경에 따라서 구분해 보면 Linux 환경, 윈도우 XP 환경 등 다양하다. 이 실행 환경에 따라서 윈도우의 모양(테마)은 바뀔 수 있다. 이 경우 버튼과 에디터 박스는 공통으로 생성될 수 있지만 테마는 Linux냐 Xp냐에 따라서 다르다. 이런 경우 Linux와 연관된 객체군들은 Linux 객체군을 생성하는 클래스가, Xp와 연관된 객체군들은 Xp 객체군을 생성하는 클래스가 생성하도록 할 수 있다. 이런 상황을 구현하는 패턴이 Abstract Factory 패턴이다.

Abstract Factory 패턴을 설명하기 앞서서 Factory Method 패턴과의 관계를 알아보자.

유사점 : 객체를 생성하고, 구체적인 타입을 감춘다.

차이점

1. Factory Method 패턴은 생성 이후 해야 할 일의 공통점을 정의하는데 촛점을 맞추는 반면, Abstract Factory 패턴은 생성해야 할 객체군의 공통점에 촛점을 맞춘다.

2. 따라서 Factory Method 패턴은 생성해야 할 객체가 한 종류이고, Abstract Factory 패턴은 여러 종류이다.


물론 유사점과 차이점을 조합해서 복합 패턴을 구성하는 것도 가능하다.


그러면 Abstract Factory 패턴의 구현을 알아보자.

Abstract Factory 패턴 클래스 다이어그램

Abstract Factory 패턴의 구현

interface IFactory

{

    public IButton createButton();

    public IEdit createEdit();

};

interface IButton {};

interface IEdit   {};

class XPFactory implements IFactory

{

    public IButton createButton() {/// XpButton을 생성하는 함수.

        return new XpButton();

    }

    public IEdit createEdit() {/// XpEdit를 생성하는 함수.

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

        return new XpEdit();

    }

}

class LinuxFactory implements IFactory

{

    public IButton createButton() {/// LinuxButton을 생성하는 함수.

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

        return new LinuxButton();

    }

    public IEdit createEdit() {/// LinuxEdit를 생성하는 함수.

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

        return new LinuxEdit();

    }

}

class XpEdit implements IEdit {

    public XpEdit(){System.out.println("XpEdit()");}

};

class XpButton implements IButton {

    public XpButton(){System.out.println("XpButton()");}

};

class LinuxEdit implements IEdit {

    public LinuxEdit(){System.out.println("LinuxEdit()");}

};

class LinuxButton implements IButton {

    public LinuxButton(){System.out.println("LinuxButton()");}

};


사용 방법

public static void main(String[] args) {

    IFactory factory = null;

    factory = new LinuxFactory();

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

    factory.createButton();

    factory.createEdit();

    factory = new XPFactory();

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

    factory.createButton();

    factory.createEdit();

}


위의 예제에서는 객체 군에는 Xp와 Linux가 있고, 객체의 종류에는 Edit와 Button이 있다. 그리고 각각을 구현한 XpEdit와 XpButton, LinuxEdit와 XpButton이 있다.

Abstract Factory 패턴의 목적은 연관성 있는 객체군을 생성할 수 있도록 제어하는 것이다. Xp용 윈도우를 만들고 싶다면 XpEdit와 XpButton을 생성할 수 있도록 하고, Linux용 윈도우를 만들고 싶다면 LinuxEdit와 LinuxButton을 생성할 수 있도록 하는 것이다. 이를 위해서는 연관성 있는 객체군을 생성할 수 있는 객체를 만들어야 한다. 그리고 또 하나 중요한 부분은 객체가 생성된 이후에는 Xp인지 Linux인지를 알 수 없도록 하는 것이다.

이를 위해서 객체군 생성을 담당하는 Factory를 선언한다. 객체군이 여럿(Xp, Linux)이기 때문에 IFactory라는 인터페이스를 먼저 만든다. 그리고 Xp 윈도우 용 객체들을 생성할 수 있는 XpFactory, Linux용 객체들을 생성할 수 있는 LinuxFactory를 만든다. 그런 다음 각각의 객체군들에 맞게 객체를 생성하는 메소드를 구현해 주면 된다.

이 과정에서 XpButton과 LinuxButton은 객체 생성 이후에 구분되어서는 안된다. 따라서 공통 인터페이스인 IButton을 구현하도록 한다. Edit쪽도 마찬가지로 IEdit를 구현하도록 만들어 준다.

'5.디자인패턴' 카테고리의 다른 글

Proxy 패턴과 그 활용  (0) 2016.09.16
Observer 패턴  (0) 2016.09.16
Factory Method 패턴  (4) 2016.09.16
Memento 패턴  (2) 2016.09.13
Bridge 패턴  (0) 2016.09.13
Posted by 이세영2
,