구조 패턴
- 프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조를 설계하는데 활용 될 수 있는 패턴 클래스, 객체들의 구성을 통해서 더 큰 구조를 만들 수 있게 해준디.
- 큰 규모의 시스템에서는 많은 클래스들이 서로 의존성을 가지게 되는데, 이런 복잡한 구조를 개발하기 쉽게 만들어주고, 유지 보수하기 쉽게 만들어 준다.
- 종료
- 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
- 박은종님의 디자인패턴
'객체지향' 카테고리의 다른 글
생성 패턴 (디자인 패턴) (0) | 2022.05.15 |
---|---|
행위 패턴 (디자인 패턴) (0) | 2022.05.15 |
클래스 다이어그램 (0) | 2022.04.23 |
리플렉션을 활용한 동적 프락시 구현 (0) | 2022.03.10 |
데코레이터 패턴 활용해서 관심사 분리하기 (0) | 2022.03.10 |