모르지 않다는 것은 아는것과 다르다.

객체지향

구조패턴 (디자인 패턴)

채마스 2022. 5. 15. 01:13

구조 패턴

  • 프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조를 설계하는데 활용 될 수 있는 패턴 클래스, 객체들의 구성을 통해서 더 큰 구조를 만들 수 있게 해준디.
  • 큰 규모의 시스템에서는 많은 클래스들이 서로 의존성을 가지게 되는데, 이런 복잡한 구조를 개발하기 쉽게 만들어주고, 유지 보수하기 쉽게 만들어 준다.
  • 종료
    • Decorator
    • Adapter
    • Bridge
    • Composite
    • Facade
    • Flyweight
    • Proxy

 

Decorator Pattern

  • 작식과 실제 내용을 동일시 하는 패턴이다.
  • 어떤 객체에 여러 기능을 추가해야될 경우, 보통 상속을 생각하는 경우가 많다.
  • 만약 자잘한 기능들이 많아 진다하면, hierarchy가 복잡해지고 지저분해지는 경우가 있다.
  • 예를들어, 상위클래스에 어떤기능을 추가한다면, 하위클래스에게 모두 적용되어야 하지만 그러고 싶지 않을 경우가 있다.
  • 상속을 사용하지 않고 기능의 유연한 확장이 가능한 패턴이다.
  • Decorator의 조합을 통해서 객체에 동적으로 새로운 서비스를 추가 할 수 있다.
  • 필요없는 경우 Decorator를 삭제할 수 있다.
  • Decorator와 실제 컴포넌트는 동일한 것이 아니며, Decorator 는 혼자 돌아갈 수 없기 때문에 Component 변수를 포함하고 있어야 한다.
  • 대표적인 예로 자바의 I/O 스트림 클래스가 있다.
  • 아래와 같은 경우가 데코레이터 패턴이 적용된 경우다.
Socket socket = new Socket();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
br.readLine();
  • 위 코드에서 실제 IO 가 일어나는 실제 객체(내용)는 socket.getInputStream() 이고, 나머지 new BufferedReader 와 new InputStreamReader 는 장식자 이다.

 

Class diagram

 

  • Component : 동적으로 추가할 서비스를 가질 수 있는 객체 정의한다.
  • ConcreteComponent
    • 추가적인 서비스가 필요한 실제 객체이다.
    • 어떤 Decorator 를 적용시키냐에 따라 기능이 늘어날 수 있다.
  • Decorator
    • Decorator 또한 Component 에서 상속받는다.
    • 결국 ConcreteComponent, Decorator 가 제공하는 메서드가 같다.
    • 그렇기 때문에 클라이언트 측에서는 ConcreteComponent를 사용하는지 Decorator를 사용하는지 모른다.
    • 장식(ConcreteComponent) 과 내용물(Decorator)를 동일시 하기 때문이다.
    • Component의 참조자를 포함하며, Component에 정의된 인터페이스를 만족하도록 정의한다.
  • ConcreteDecorator : 새롭게 추가되는 서비스를 실제 구현한 클래스로 addBehavior()를 구현한다.

 

예제

public abstract class Coffee {

    public abstract void brewing();
}
  • class diagram 에서 Component 에 해당한다.
public class EtiopiaAmericano extends Coffee{

    @Override
    public void brewing() {
        System.out.print("EtiopiaAmericano ");
    }

}
  • class diagram 에서 ConcreteComponent에 해당한다.
  • 실제 객체에 해당된다.
public class KenyaAmericano extends Coffee{

    @Override
    public void brewing() {
        System.out.print("KenyaAmericano ");
    }

}
  • class diagram 에서 ConcreteComponent에 해당한다.
public abstract class Decorator extends Coffee{

    Coffee coffee;
    public Decorator(Coffee coffee){
        this.coffee = coffee;
    }

    @Override
    public void brewing() {
        coffee.brewing();
    }

}
  • class diagram 에서 Decorator 에 해당한다.
public class Latte extends Decorator{

    public Latte(Coffee coffee) {
        super(coffee);
    }

    public void brewing() {
        super.brewing();
        System.out.print("Adding Milk ");
    }
}
  • class diagram 에서 ConcreteDecorator에 해당한다.
public class Mocha extends Decorator{

    public Mocha(Coffee coffee) {
        super(coffee);
    }

    public void brewing() {
        super.brewing();
        System.out.print("Adding Mocha Syrup ");
    }
}
  • class diagram 에서 ConcreteDecorator에 해당한다.
public class WhippedCream extends Decorator{

    public WhippedCream(Coffee coffee) {
        super(coffee);
    }

    public void brewing() {
        super.brewing();
        System.out.print("Adding WhippedCream ");
    }
}
  • class diagram 에서 ConcreteDecorator에 해당한다.
public class CoffeeTest {

    public static void main(String[] args) {

        Coffee kenyaAmericano = new KenyaAmericano();
        kenyaAmericano.brewing();
        System.out.println();

        Coffee kenyaLatte = new Latte(kenyaAmericano);
        kenyaLatte.brewing();
        System.out.println();

        Mocha kenyaMocha = new Mocha(new Latte(new KenyaAmericano()));
        kenyaMocha.brewing();
        System.out.println();

        WhippedCream etiopiaWhippedMocha = 
                new WhippedCream(new Mocha(new Latte( new EtiopiaAmericano())));
        etiopiaWhippedMocha.brewing();
        System.out.println();

    }
}
  • 먼저 kenyaAmericano 를 만든다.
  • 만약 라떼를 추가하고 싶다면 Latte 인스턴스를 생성할때 kenyaAmericano를 넘겨주면 된다.
  • 이런식으로 원하는 기능들을 계속해서 붙일 수 있다.

 

Adapter Pattern

  • 서로 다른 인터페이스를 중간에서 연결해주는 기능이 필요할때 사용하는 패턴이다.
  • 이미 사용중이거나 정의된 인터페이스들을 중간에서 맞춰서 적용해 줄 수 있다.
  • Adpter를 사용함으로써 클라이언트가 사용하는 방식은 동일하면서 여러 기능이 제공될 수 있다.
  • 서로 일치하지 않는 인터페이스를 변경하지 않고 중간에서 호출하여 사용할 수 있도록 제공한다.

 

class diagram

상속

  • Target : 클라이언트가 사용할 인터페이스를 정의 하고 있는 클래스다.
  • Client : Target 인터페이스를 사용하는 객체이다.
  • Adaptee : 실제의 새롭거나 적용될 기능이 제공되는 클래스다.
  • Adapter : Target 인터페이스에 Adaptee의 인터페이스를 맞춰주는 클래스다.

 

합성

  • adaptee 를 상속하지 않고 변수로 포함하는 방식을 합성이다.
  • adaptee 의 하위클래스가 있다고 했을때, 핸들링하는 것이 상속보다 쉽다.
  • 그렇기 때문에 합성이 좀더 유연하다. -> 재사용의 측면에선 합성이 좋다.

 

예제 코드

public interface Print {
    public abstract void printWeak();
    public abstract void printStrong();
}
  • class diagram 에서 target 에 해당한다.
public class Banner {
    private String string;
    public Banner(String string) {
        this.string = string;
    }
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}
  • class diagram 에서 Adaptee 에 해당한다.
public class PrintBanner implements Print {
    private Banner banner;  //Composition
    public PrintBanner(String string) {
        this.banner = new Banner(string);
    }
    public void printWeak() {
        banner.showWithParen();
    }
    public void printStrong() {
        banner.showWithAster();
    }
}
  • class diagram 에서 adapter에 해당한다.
  • Print 를 상속받고, Banner 를 합성함으로써, Print 와 Banner 를 연결해주었다.
  • 이렇게 되면 Print 와 Banner 는 건드리지 않고, 둘을 연결 할 수 있다.
public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printWeak(); 
        p.printStrong();
    }
}



Bridge Pattern

  • 기능의 계층과 구현의 계층을 분리하는 패턴이다.
  • 추상화와 구현부를 분리하여 각각을 독립적으로 변겨할 수 있게 한다.
  • 기능에 대한 여러가지 구현을 다양하게 적용할 수 있다.
  • 두 계층을 분리하기 때문에 서로의 사이에 Bridge가 필요하다.
  • 기능과 구현의 결합도 약하므로, 기능이 구현 방식에 얽매이지 않는다.
  • 기능의 구현 클래스를 런타임때 지정할 수도 있다.
  • 구현이 변경되더라도 기능 클래스 부분에 대한 컴파일은 필요없다.
  • 기능과 구현은 독립적으로 확장되며, 클라이언트는 기능의 인터페이스를 사용하므로 구체적인 구현내용은 숨길 수 있다.

 

예제

  • List는 선형 자료구조 입니다.
  • 그 중에는 Stack, Queue, Deque와 같이 특정 기능을 제공하는 자료구조가 있습니다.
  • List를 구현하는 방법은 크게 Array와 LinkedList 가 있습니다.
  • 가령 하나의 Stack을 구현한다고 할 때 Array로 구현 할 수도 있고, LinkedList로 구현할 수도 있습니다.

 

Class diagram

  • List (Abstraction): 추상화 개념의 상위 클래스이고 객체 구현자(Implemntor)에 대한 참조자를 관리한다.
  • Stack, Queue (RefinedAbstraction): 추상화 개념의 확장된 기능을 정의한다.
  • AbstractList (Implementor) : 하위 클래스가 구현해야 하는 기능들을 선언한다.
  • Array, LinkedList (ConcreteImplementor) : Implementor에 선언된 기능을 실제로 구현하며, 여러 구현방식의 클래스가 만들어 질 수 있다.

 

예제코드

public class List<T>{ 

    AbstractList<T> impl;

    public List(AbstractList<T> list) {
        impl = list;
    }

    public  void add(T obj) {
        impl.addElement(obj);
    }
    public  T get(int i) {
        return impl.getElement(i);
    }
    public  T remove(int i) {
        return impl.deleteElement(i);
    }
    public  int getSize() {
        return impl.getElementSize();
    }

}
  • AbstractList 형 변수 impl 을 가지고 있으므로써 다리(Bridge)가 형성된다.
import impl.AbstractList;

public class Queue<T> extends List<T> {

    public Queue(AbstractList<T> list) {
        super(list);
        System.out.println("Queue를 구현합니다.");
    }

    public void enQueue(T obj) {
        impl.addElement(obj);
    }

    public T deQueue() {
        return impl.deleteElement(0);
    }

}
public  class Stack<T> extends List<T> {

    public Stack(AbstractList<T> list) {
        super(list);
        System.out.println("Stack을 구현합니다.");
    }

    public void push(T obj) {
        impl.insertElement(obj, 0);
    }

    public T pop() {
        return impl.deleteElement(0);
    }

}
public interface AbstractList<T> {

    public void addElement(T obj);
    public T deleteElement(int i);
    public int insertElement(T obj, int i);
    public T getElement(int i);
    public int getElementSize();
}
public class ArrayImpl<T> implements AbstractList<T> {

    ArrayList<T> array;
    public ArrayImpl(){
        array = new ArrayList<T>();
        System.out.println("Array로 구현합니다.");

    }

    @Override
    public void addElement(T obj) {
        array.add(obj);
    }

    @Override
    public T deleteElement(int i) {

        return array.remove(i);
    }

    @Override
    public int insertElement(T obj, int i) {
        array.add(i, obj);
        return i;
    }

    @Override
    public int getElementSize() {
        return array.size();
    }

    @Override
    public T getElement(int i) {

        return array.get(i);
    }
}
public class LinkedListImpl<T> implements AbstractList<T>{

    LinkedList<T> linkedList;

    public LinkedListImpl() {
        linkedList = new LinkedList<T>();
        System.out.println("LinkedList로 구현합니다.");
    }

    @Override
    public void addElement(T obj) {
        linkedList.add(obj);

    }

    @Override
    public T deleteElement(int i) {
        return linkedList.remove(i);
    }

    @Override
    public int insertElement(T obj, int i) {
        linkedList.add(i, obj);
        return i;
    }

    @Override
    public int getElementSize() {
        return linkedList.size();
    }

    @Override
    public T getElement(int i) {
        return linkedList.get(i);
    }

}
public class BridgeTest {

    public static void main(String[] args) {

        Queue<String> arrayQueue = new Queue<String>(new ArrayImpl<String>());

        arrayQueue.enQueue("aaa");
        arrayQueue.enQueue("bbb");
        arrayQueue.enQueue("ccc");

        System.out.println(arrayQueue.deQueue());
        System.out.println(arrayQueue.deQueue());
        System.out.println(arrayQueue.deQueue());
        System.out.println("=========================");

        Queue<String> linkedQueue = new Queue<String>(new LinkedListImpl<String>());
        linkedQueue.enQueue("aaa");
        linkedQueue.enQueue("bbb");
        linkedQueue.enQueue("ccc");

        System.out.println(linkedQueue.deQueue());
        System.out.println(linkedQueue.deQueue());
        System.out.println(linkedQueue.deQueue());
        System.out.println("=========================");

        Stack<String> arrayStack = new Stack<String>(new ArrayImpl<String>());
        arrayStack.push("aaa");
        arrayStack.push("bbb");
        arrayStack.push("ccc");

        System.out.println(arrayStack.pop());
        System.out.println(arrayStack.pop());
        System.out.println(arrayStack.pop());
        System.out.println("=========================");

        Stack<String> linkedStack = new Stack<String>(new LinkedListImpl<String>());
        linkedStack.push("aaa");
        linkedStack.push("bbb");
        linkedStack.push("ccc");

        System.out.println(linkedStack.pop());
        System.out.println(linkedStack.pop());
        System.out.println(linkedStack.pop());
        System.out.println("=========================");
    }

}

 

Composite Pattern

  • 재귀적인 구조의 패턴이다.
  • 기본 객체는 복합 객체에 포함이 되고, 복합 객체 역시 또 다른 복합 객체에 포함되야 할 경우 사용되는 패턴이다.
  • 클라이언트가 개별 객체와 복합 객체를 동일하게 다룰 수 있는 인터페이스를 제공한다.
  • 그렇기 때문에, 클라이언트 코드는 기본객체와 복합객체에 대한 일관된 프로그래밍을 할 수 있다.
  • 부분과 전체에 대한 복합 객체의 트리구조를 나타낼 수 있다.
  • 기본 객체가 증가하여도 전체 객체의 코드에 영향을 주지 않는다.
  • 새로운 요소의 추가가 편리하고 범용성 있는 설계가 가능하다.

 

class diagram

  • Component
    • 전체와 부분 객체에서 공통적으로 사용할 인터페이스 선언한다.
    • 전체와 부분 객체에서 공통으로 사용할 기능 구현한다.
    • 전체 클래스가 부분요소들을 관리하기 위해 필요한 인터페이스 선언한다.
  • Leaf
    • 집합 관계에서 다른 객체를 포함할 수는 없고 포함되기만 하는 객체로 가장 기본이 되는 기능을 구현한다.
  • Composite
    • 여러 객체를 포함하는 복합 객체에 대한 기능 구현한다.
    • 포함한 여러 객체를 저장하고 관리하는 기능을 구현한다.
  • Client
    • Component에 선언된 인터페이스를 통하여 부분과 전체를 동일하게 처리한다.

 

예제 코드

public abstract class ProductCategory {

    int id;
    String name;
    int price;

    public ProductCategory(int id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public abstract void addProduct(ProductCategory product);
    public abstract void removeProduct(ProductCategory product);
    public abstract int getCount();
    public abstract String getName();
    public abstract int getPrice();
    public abstract int getId();

}
  • diagram 에서 상위 클래스인 Component 에 해당된다.
public class Product extends ProductCategory{

    @Override
    public int getCount() {
        return 1;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getPrice() {
        return price;
    }

    @Override
    public int getId() {
        return id;
    }

    public Product(int id, String name, int price) {
        super(id, name, price);
    }

    @Override
    public void addProduct(ProductCategory product) {

    }

    @Override
    public void removeProduct(ProductCategory product) {

    }

}
  • diagram 에서 leaf에 해당된다.
public class Category extends ProductCategory{


    ArrayList<ProductCategory> list;

    public Category(int id, String name, int price) {
        super(id, name, price);
        list = new ArrayList<ProductCategory>();
    }

    @Override
    public void addProduct(ProductCategory productCategory) {
        list.add(productCategory);
    }

    @Override
    public void removeProduct(ProductCategory productCategory) {

        for(ProductCategory temp : list) {
            if(temp.getId() == productCategory.getId()) {
                list.remove(temp);
                return;
            }
        }
        System.out.println("카테고리가 없습니다.");
    }

    @Override
    public int getCount() {
        int count = 0;

        for(ProductCategory temp : list) {
            count += temp.getCount(); 
        }    
        return count;
    }

    @Override
    public String getName() {
        return list.toString();
    }

    @Override
    public int getPrice() {
        int price = 0;

        for(ProductCategory temp : list) {
            price += temp.getPrice(); 
        }

        return price;
    }

    @Override
    public int getId() {
        return 0;
    }
}
  • diagram 에서 하위 Component 에 해당된다.
  • 상위 Component 객체를 포함하고 있다.
  • 만약 하위 객체가 또 Compnent 이면 getCount, getPrice 의 경우 재귀적으로 호출된다.
public class CategoryClient {

    public static void main(String[] args) {

        ProductCategory womanCategory = new Category(1234, "Woman", 0);
        ProductCategory manCategory = new Category(5678, "Man", 0);

        ProductCategory clothesCategoryW = new Category(2345, "Clothes", 0);
        ProductCategory bagCategoryW = new Category(3456, "Bag", 0);
        ProductCategory shoesCategoryW = new Category(9876, "Shoes", 0);

        womanCategory.addProduct(clothesCategoryW);
        womanCategory.addProduct(bagCategoryW);
        womanCategory.addProduct(shoesCategoryW);

        ProductCategory clothesCategoryM = new Category(23450, "Clothes", 0);
        ProductCategory bagCategoryM = new Category(34560, "Bag", 0);
        ProductCategory shoesCategoryM = new Category(98760, "Shoes", 0);

        manCategory.addProduct(clothesCategoryM);
        manCategory.addProduct(bagCategoryM);
        manCategory.addProduct(shoesCategoryM);

        ProductCategory shoes1 = new Product(121, "Nike", 100000);
        ProductCategory shoes2 = new Product(122, "ADIDAS", 200000);
        ProductCategory shoes3 = new Product(123, "GUCCI", 300000);
        ProductCategory shoes4 = new Product(124, "BALENCIA", 400000);
        ProductCategory shoes5 = new Product(125, "PRADA", 500000);
        ProductCategory shoes6 = new Product(126, "BALLY", 600000);

        shoesCategoryW.addProduct(shoes1);
        shoesCategoryW.addProduct(shoes2);
        shoesCategoryW.addProduct(shoes3);

        shoesCategoryM.addProduct(shoes4);
        shoesCategoryM.addProduct(shoes5);
        shoesCategoryM.addProduct(shoes6);


        ProductCategory bag1 = new Product(121, "HERMES", 500000);
        ProductCategory bag2 = new Product(122, "LOUISVUITTON", 500000);
        ProductCategory bag3 = new Product(123, "GUCCI", 500000);
        ProductCategory bag4 = new Product(124, "BALENCIA", 500000);
        ProductCategory bag5 = new Product(125, "PRADA", 500000);
        ProductCategory bag6 = new Product(126, "MULBERRY", 500000);

        bagCategoryW.addProduct(bag1);
        bagCategoryW.addProduct(bag2);
        bagCategoryW.addProduct(bag3);

        bagCategoryM.addProduct(bag4);
        bagCategoryM.addProduct(bag5);
        bagCategoryM.addProduct(bag6);

        System.out.println(womanCategory.getCount());
        System.out.println(womanCategory.getPrice());
        System.out.println(manCategory.getCount());
        System.out.println(manCategory.getPrice());

    }

}
  • 위의 코드를 보면 남자와 여자 카테고리(Component)안에 각각 옷, 가방, 신발 카테고리(Component)가 포함되고, 그 안에 Product(leaf) 들이 포함된 구조인 것을 확인할 수 있다.

 

Facade Pattern

  • 간단한 창구 역할을 하는 패턴이다.
  • 서브시스템이 여러개인 경우 이를 통합한 하나의 인터페이스를 제공한다.
  • 서브시스템을 좀 더 편하게 이용하기 위한 높은 수준의 인터페이스를 정의한다.
  • 각 서브시스템의 역할이나 의존관계를 내부에서 올바를 순서로 사용할 수 있도록 외부에는 간단한 인터페이스만을 오픈한다.
  • 서브시스템을 합성하여 사용하는 다수 객체의 집합에 대한 하나의 일관된 인터페이스를 제공함으로써 사용하기 쉽게 한다.
  • 복잡한 서브시스템들에 대한 단순하고 기본적인 인터페이스를 앞에서 제공한다,
  • 클라이언트와 서브시스템간의 결합도를 줄인다.

 

Class diagram

  • Facade : 하나의 일관된 인터페이스 제공한다.

 

Flyweight Pattern

  • 공유 할 수 있는 객체는 공유하여 사용하는 패턴이다.
  • 규모가 작고, 인스턴스마다 특성이 따로 없다면 공유해서 사용할 수 있다.
  • 공유를 통하여 인스턴스의 수를 절약할 수 있나, 부가적인 정보가 많은 경우는 비효율적일 수 있다.
  • 따라서, 인스턴스마다의 특성이 거의 없는 객체에 사용하는 것이 효율적이다.

 

Class diagram

  • Flyweight
    • 각 객체가 사용할 인터페이스를 정의한다.
  • CocreteFlyweight
    • 공유될 수 있는 실제적 객체를 구현한다.
  • UnSharedCocreteFlyweight
    • 각 인스턴스마다 가지게 되는 부가적인 특성이 있다면 구현한다.
    • UnsharedConcreteFlyweight(공유할 수 없는 정보)는 따로 관리해야 하는데, 따로 관리해야되는 정보가 많다면 이 패턴은 바람직하지 못하다.
  • FlyweightFactory
    • Flyweight에 pool을 관리한다. 각 Flyweight 객체는 Singleton으로 생성한다.
    • 싱글톤으로 만들어진 정보들이 pool에 존재한다.
    • 찾는 정보가 pool에 있다면 반환해주고, 없다면 pool에 넣어준 뒤 반환해 준다.

 

예제 코드

public class BigChar {

    private char charname;
    private String fontdata;

    public BigChar(char charname) {
        this.charname = charname;
        try {
            BufferedReader reader = new BufferedReader(
                new FileReader("big" + charname + ".txt")
            );
            String line;
            StringBuffer buf = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                buf.append(line);
                buf.append("\n");
            }
            reader.close();
            this.fontdata = buf.toString();
        } catch (IOException e) {
            this.fontdata = charname + "?";
        }
    }
    // 큰 문자를 표시한다.
    public void print() {
        System.out.print(fontdata);
    }
}
public class BigCharFactory {

    private HashMap<String,BigChar> pool = new HashMap<String,BigChar>();

    private static BigCharFactory singleton = new BigCharFactory();

    private BigCharFactory() {
    }

    public static BigCharFactory getInstance() {
        return singleton;
    }

    public synchronized BigChar getBigChar(char charname) {
        BigChar bc = pool.get("" + charname);
        if (bc == null) {
            bc = new BigChar(charname); // 여기에서 BigChar의 인스턴스를 생성
            pool.put("" + charname, bc);
        }
        return bc;
    }
}
  • 싱글톤으로 구현된 것을 확인할 수 있다.
  • getBigChar 메서드를 보면 pool에 해당 인스턴스가 존재하면 반환하고 없으면, 새로 생성해서 pool 에 넣어준 뒤 반환한다.
public class BigString {
    // "큰 문자"의 배열
    private BigChar[] bigchars;
    // 생성자
    public BigString(String string) {
        bigchars = new BigChar[string.length()];
        BigCharFactory factory = BigCharFactory.getInstance();
        for (int i = 0; i < bigchars.length; i++) {
            bigchars[i] = factory.getBigChar(string.charAt(i));
        }
    }
    // 표시
    public void print() {
        for (int i = 0; i < bigchars.length; i++) {
            bigchars[i].print();
        }
    }
}
  • 위의 코드를 보면 팩토리에서 인스턴스를 가져와서 출력하는 것을 확인 할 수 있다.
public class Main {
    public static void main(String[] args) {

        BigString bs = new BigString("123abc123");
        bs.print();
    }
}

 

Proxy Pattern

  • 만약 10개의 메서드중 3개만 자주 사용된다면, 원격지 프록시를 만들어 3개의 기능을 처리하도록하고, 나머지 7개의 기능은 사용할때 만든다.

 

예제 코드

public interface Printable {
    public abstract void setPrinterName(String name);   // 이름의 설정
    public abstract String getPrinterName();            // 이름의 취득
    public abstract void print(String string);          // 문자열 표시(프린트아웃)
}
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) {       // 이름의 설정
        System.out.println("real : setPrinterName()");
        this.name = name;
    }
    public String getPrinterName() {                // 이름의 취득
        System.out.println("real : 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("완료");
    }
}
  • 실제 객체가 만들어 질때는 위와 같이 무거운 작업(heavyJob)이 실행된다.
public class PrinterProxy implements Printable {
    private String name;            // 이름
    private Printer real;           // "본인"
    public PrinterProxy() {
    }
    public PrinterProxy(String name) {      // 생성자
        this.name = name;
    }
    public String getPrinterName() {    // 이름의 취득
        System.out.println("proxy : getPrinterName()");
        return name;
    }
    public synchronized void setPrinterName(String name) {  // 이름의 설정
        if (real != null) {
            real.setPrinterName(name);  // "본인"에게도 설정한다.
        }
        System.out.println("proxy : setPrinterName()");
        this.name = name;
    }
    public void print(String string) {  // 표시
        realize();
        real.print(string);
    }
    private synchronized void realize() {   // "본인"을 생성
        if (real == null) {
            real = new Printer(name);
        }
    }
}
  • real 이라는 실제 객체를 가지고 있다.
  • Proxy 와 Printer 모두 Printable 을 상속받은 것을 확인할 수 있다.
  • 실제로 print 메서드를 호출할때 실제 객체를 호출한다. 그 전까지는 가짜 객체가 일을 한다.
  • realize 메서드에서 real 이 없다면 새로 생성한다. -> 있다면 굳이 생성하지 않는다. -> 시간 절약
  • heavyJob 에서 5초 동안 객체를 만드는데, 그 동안 interrupt가 발생하면 안되기 때문에 synchronized 를 걸어준다.
  • synchronized를 걸어두면 해당 작업이 실행되는 동안은 락이 걸어서 다른 스레드가 접근하지 못한다.
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.");

        p.print("test");
        p.setPrinterName("Tomas");
        System.out.println("현재의 이름은" + p.getPrinterName() + "입니다.");

    }
}
  • 위의 코드를 돌려보면, print 메서드가 실행되기 전까지는 프록시가 일을하고, print 메서드가 호출되는 시점에 실제 객체가 생성되며, heavyJob 을 처리하느라 5초간 대기한다.
  • 그 다음의 print가 호출되면 이미 만들어져 있기때문에 바로 호출된다.






REFERENCES

  • 박은종님의 디자인패턴