프로그램 내의 자료구조나 인터페이스 구조 등 프로그램 구조를 설계하는데 활용 될 수 있는 패턴 클래스, 객체들의 구성을 통해서 더 큰 구조를 만들 수 있게 해준디.
큰 규모의 시스템에서는 많은 클래스들이 서로 의존성을 가지게 되는데, 이런 복잡한 구조를 개발하기 쉽게 만들어주고, 유지 보수하기 쉽게 만들어 준다.
종료
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초간 대기한다.