컴포지트 패턴을 이용하면 객체들을 트리구조로 구성하여 계층구조로 나타낼 수 있습니다.

중요한 것은 트리구조에서 자식이 있는 노드와 자식이 없는 노드를 구분하지 않고

마치 하나처럼 사용 할 수 있다는 것입니다.

자식이 있는 것을 노드라고 부르고, 없는 것을 리프(leaf)라고 부르겠습니다.

컴포지트 패턴에서는 노드와 
리프가 하나의 추상 클래스를 상속받습니다.

노드와 리프를 하나처럼(똑같은 방법) 다루기 위함이죠.. 이 말은 뒤에서 다시 언급해보겠습니다.

쇼핑몰을 위해서 카테고리를 만들려고 합니다.

일단 제일 상위에 "전체 카테고리"가 존재하고 그 밑으로

"의류"와 "전자제품" 카테고리가 있습니다. 그리고 "의류" 밑에는 "점퍼" 라는 하위 카테고리가 존재합니다.



public abstract class CategoryComponent {
 public void add(CategoryComponent component) {
  throw new UnsupportedOperationException();
 }
 
 public CategoryComponent getChild(int i) {
  throw new UnsupportedOperationException();
 }
 
 public void print() {
  throw new UnsupportedOperationException();
 }
 
 public void getDeparName() {
  throw new UnsupportedOperationException();
 }
}


카테고리와 그 밑으로 들어가게 될 상품 모두 이 CategoryComponent를 상속해야 합니다.

그래야 노드와 리프를 같은 객체처럼 다룰 수 있습니다.

그리고 메소드마다 exception을 던지는 이유는, 노드 같은 경우는 자식을 가질 수 있기 때문에 add 메소드를 사용하지만

리프 같은 경우는 자식을 가질 수 없기 때문에 add는 사용하지 않는 메소드입니다.

그래서 상위 클래스인 CategoryComponent에서 위와 같이 구현을 해 놓으면 이 클래스를 상속받는 클래스들은

자신이 사용 할 메소드만 구현하면 됩니다.

그러면 Category를 보겠습니다.


import java.util.ArrayList;
import java.util.Iterator;

public class Category extends CategoryComponent {
 private String name;
 private ArrayList <CategoryComponent> components = new ArrayList<CategoryComponent>();
 
 public Category(String name) {
  this.name = name;
 }
 
 public String getName() {
  return name;
 }
 
 public void add(CategoryComponent component) {
  this.components.add(component);
 }
 
 public CategoryComponent getChild(int i) {
  return components.get(i);
 }
 
 public void print() {
  System.out.println("카테고리 이름 : " + getName());
  System.out.println("-----------------------------");
  Iterator<CategoryComponent> iterator = components.iterator();
  while(iterator.hasNext()) {
   CategoryComponent component = iterator.next();
   component.print();
  } 

 }
}



카테고리는 하위 자식을 가질 수 있습니다. 바로 상품들이죠.. 그리고 카테고리는 하위 카테고리도 자식으로 가질 수 있습니다.

노드나 리프나 모든 CategoryComponent를 상속합니다.

그리고, print를 보시면...

Iterator를 사용해서 재귀호출로 자기가 가진 자식들의 print()를 호출하고 있습니다.

스택을 그려놓고 저 재귀호출이 어떻게 작동하는지 보시는 것도

좋을 것입니다.


public class Product extends CategoryComponent {
 String name;
 String price;
 
 public Product(String name, String price) {
  this.name = name;
  this.price = price;
 }
 
 public String getName() {
  return name;
 }
 
 public String getPrice() {
  return price;
 }
 
 public void print() {
  System.out.println("상품명 : " + getName());
  System.out.println("가격 : " + getPrice());
  System.out.println("-----------------------------");
 }

}

리프를 구성 할 Product 클래스입니다. 마찬가지로, CategoryComponent를 상속받고 있습니다.

그리고 print()메소드에서는 자식이 없기 때문에 그냥 자기 자신의 정보를 print합니다.

위 Category에서 재귀호출로 불려진 print() 메소드는 자기가 가진 최하위 
리프까지 호출되어와서

그 
리프의 print() 메소드부터 다시 실행되며 처리 되는 것입니다.

테스트 클래스입니다.

public class Test {
 public static void main(String[] args) {
  CategoryComponent category = new Category("의류");
  CategoryComponent category2 = new Category("전자제품");
  
  CategoryComponent categoryAll = new Category("전체 카테고리");
  categoryAll.add(category);
  categoryAll.add(category2);
  
  category.add(new Product("스웨터","10000"));
  category.add(new Product("청바지","30000"));
  category.add(new Product("면바지","2000"));
  category.add(new Product("가디건","110000"));
 
  //앗 점퍼 카테고리..
  CategoryComponent category3 = new Category("점퍼");
  category.add(category3);
  category3.add(new Product("지오다노 패딩 점퍼","30000"));
  category3.add(new Product("니 패딩 점퍼","50000"));
  
  category2.add(new Product("아이팟터치","280000"));
  category2.add(new Product("디오스 냉장고","2280000"));
  category2.add(new Product("에어컨","1280000"));
  
  categoryAll.print();
 }
}

트리구조로 상품들을 넣고 있습니다.

전체 밑에 의류와 전자제품이 있고 의류 밑에는 점퍼라는 하위 카테고리가 존재합니다.

그리고 각각의 카테고리는 상품을 가지고 있습니다.

그런데 이것을 모두 출력하는데는 단 한줄의 코드만 있으면 됩니다.

categoryAll.print();

입니다.

카테고리와 상품을 구분해서 출력할 필요도 없습니다.

출력해야 하는 정보는 자기 자신들이 각각 가지고 있을뿐이죠..


사실 Composite 패턴을 사용해서 트리를 제대로 구현하려면 이것보다 더 복잡합니다.

저작자 표시
신고
Posted by 용식

각각의 콜렉션 객체에 대해서 for문이나 뭐 이런식으로 콜렉션안에 들어가 있는

객체들을 참조 할 수 있지만, Iterator를 사용하면 소스의 구분없이

컬렉션안의 객체들을 참조 할 수 있습니다.

예를 들어서 ArrayList를 사용해서 객체를 관리하는 프로그램과 (혹은 클래스)

배열을 사용해서 객체를 관리하는 프로그램(혹은 클래스) 두개가 있는데..

이 두개를 합치는 경우를 생각해 보면

ArrayList와 배열에 대해서 각각 참조하고 있는 객체의 리스트를 얻기 위해서는

각 클래스에 맞는 형식으로 순환문을 돌려야 합니다.

ArrayList라면..

for(int i = 0; i < list.size(); i++) {
...
list.get(i);
...
}

배열이라면
for(int i = 0; i < 배열.length; i++) {
 ...
 배열[i];
...
}


이런식이 되겠죠..

이것을 Itreration을 사용하면 일괄적인 소스로 관리가 가능합니다.

그리고 또 하나는 컬렉션 객체를 숨길 수가 있습니다.

위의 예제 같은 경우에는 마음대로 list나 배열을 만질 수가 있습니다.

add도 할 수 있고 move도 할 수 있고요..

하지만 Iteration을 만들어 그것을 넘겨주게 되면

사용하는 측에서도 Iteration에 대한 사용만 고려하면 되고

객체를 숨겨서 직접 접근하지 못 하도록 할 수도 있습니다.

간단한 예제를 보겠습니다.

일단 관리하고 싶은 클래스입니다.

public class Character {

 private String name;
 private String age;
 private String sex;
 
 public Character(String name, String age, String sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getAge() {
  return age;
 }
 public void setAge(String age) {
  this.age = age;
 }
 public String getSex() {
  return sex;
 }
 public void setSex(String sex) {
  this.sex = sex;
 }
}




그리고 위 클래스의 객체를 관리하게 될 클래스들입니다.

public class CharacterArray {
 static final int MAX_VALUES = 6;
 Character[] characters;
 int index = 0;
 
 public CharacterArray() {
  characters = new Character[MAX_VALUES];
  addItem("홍길동","22","남");
  addItem("성춘향","19","여");
  addItem("강감찬","45","남");
  addItem("제다이","?","남");
 }
 
 public void addItem(String name, String age, String sex) {
  if(index >= MAX_VALUES) {
   System.out.println("array is full");
  } else {   
   Character c = new Character(name, age, sex);
   characters[index] = c;
   index++;
  }
 }
 
 public Character[] getCharacters() {
  return characters;
 }
}



 

import java.util.ArrayList;

public class CharacterList {
 ArrayList<Character> characters;
 
 public CharacterList() {
  characters = new ArrayList<Character>();
  addItem("탐크루즈","41","남");
  addItem("안젤리나졸리","33","여");
  addItem("디아나","31","여");
  addItem("러셀크로","41","남");
 }
 
 public void addItem(String name, String age, String sex) {
  Character c = new Character(name, age, sex);
  characters.add(c);
 }
 
 public ArrayList<Character> getCharacters() {
  return characters;
 }
}



두개의 클래스는 각각 내부적으로 ArrayList와 배열을 사용해서 관리하고 있습니다.

여기서 각각의 클래스가 가지고 있는 Character의 객체들을 조회하는 소스를 만들어 보겠습니다.


import java.util.ArrayList;

public class Test {
 public static void main(String[] args) {
  
  CharacterList cl = new CharacterList();
  CharacterArray ca = new CharacterArray();
  
  ArrayList<Character> list = cl.getCharacters();
  Character[] array = ca.getCharacters();

  

  for(int i = 0; i < list.size(); i++) {
   Character c = list.get(i);
   System.out.println(c.getName());
   System.out.println(c.getAge());
   System.out.println(c.getSex());
  }


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

  for(int i = 0; i < array.length; i++) {
   Character c = array[i];
   System.out.println(c.getName());
   System.out.println(c.getAge());
   System.out.println(c.getSex());
  }


 }
}



굵은 글씨로 된 부분을 보시면 직접 배열객체와 ArrayList를 받아오고 있습니다.

그리고 색깔이 다른 부분을 보면 각 캐릭터에 대해서 서로 다른 순환문을 사용하고 있습니다.

그래서 이 반복을 캡슐화 하는 것이 Itrerator 패턴입니다.

Iterator는 java.util에서 인터페이스가 제공됩니다.

그것을 사용해보겠습니다. 그러면 ArrayList에서는 이미 이 Iterator를 구현하고 있기 때문에 CharacterList 클래스는

그 Iterator를 return해주는 부분만 만들어주면 됩니다.

그리고, ArrayList를 리턴해주던 메소드는 삭제합니다.

import java.util.ArrayList;
import java.util.Iterator;

public class CharacterList {
 private ArrayList<Character> characters;
 
 public CharacterList() {
  characters = new ArrayList<Character>();
  addItem("탐크루즈","41","남");
  addItem("안젤리나졸리","33","여");
  addItem("디아나","31","여");
  addItem("러셀크로","41","남");
 }
 
 public void addItem(String name, String age, String sex) {
  Character c = new Character(name, age, sex);
  characters.add(c);
 }
 

 public Iterator getIterator() {
  return characters.iterator();
 }

}


 


하지만 배열은 Iterator가 제공되지 않기 때문에 java.util.Iterator를 구현한 Iterator 클래스를 만들어야 합니다.

간단하게 만들어 보겠습니다.

import java.util.Iterator;

public class CharacterArrayIterator implements Iterator {

 Character[] c;
 int index = 0;
 
 public CharacterArrayIterator(Character[] c) {
  this.c = c;
 }
 
 public boolean hasNext() {
  if(index >= c.length || c[index] == null) {
   return false;
  } else {
   return true;
  }
 }

 public Object next() {
  Character character = c[index];
  index++;
  return character;
 }

 public void remove() {
  throw new UnsupportedOperationException();
 }

}



본래대로라면 remoce() 메서드도 구현을 해줘야 합니다.

하지만 그냥 예외처리 하고 넘어가겠습니다.

그리고 CharacterArray에서 Iterator를 넘겨주는 부분을 구현해야 합니다. 그리고, 배열은 private으로 숨겨야겠죠..

import java.util.Iterator;

public class CharacterArray {
 static final int MAX_VALUES = 4;
 private Character[] characters;
 int index = 0;
 
 public CharacterArray() {
  characters = new Character[MAX_VALUES];
  addItem("홍길동","22","남");
  addItem("성춘향","19","여");
  addItem("강감찬","45","남");
  addItem("제다이","?","남");
 }
 
 public void addItem(String name, String age, String sex) {
  if(index >= MAX_VALUES) {
   System.out.println("array is full");
  } else {   
   Character c = new Character(name, age, sex);
   characters[index] = c;
   index++;
  }
 }
 
 public Iterator getIterator() {
  return new CharacterArrayIterator(characters);
 }

}




그러면 이제 조회를 하는 클래스를 보겠습니다.

import java.util.ArrayList;
import java.util.Iterator;

public class Test {
 public static void main(String[] args) {
  
  CharacterList cl = new CharacterList();
  CharacterArray ca = new CharacterArray();
  
  Iterator i = cl.getIterator();
  Iterator i2 = ca.getIterator();

  
  showList(i);
  showList(i2);
  ArrayList<Character> list = cl.getCharacters();
  Character[] array = ca.getCharacters();
 }
 
 public static void showList(Iterator i) {
  while(i.hasNext()) {
   Character c = (Character)i.next();
   System.out.println(c.getName());
   System.out.println(c.getAge());
   System.out.println(c.getSex());
  }

 }
}



구현체에 따라서 나누어져있던 로직이 Iterator를 사용해서 처리가 되었습니다.

그리고 실제로 이 Test 클래스에서는 자기가 조회하고 있는 객체가

배열인지 ArrayList인지 Map인지 몰라도 상관이 없습니다.

단지 Iterator만 사용 할 뿐입니다.

자바에서 제공되는 API중 Collection 객체들은 이 Iterator를 구현하고 있습니다.


만약에 Character 클래스를 다른 방법으로 관리하는 또다른 클래스가 추가 된다고 하더라도

저 Iterator 인터페이스를 구현한 Iterator 클래스만 만들어주면 됩니다.

컬렉션 객체안에 있는 모든 항목에 접근 하는 방식이 통일 되면, 어떤 종류의 집합체에 대해서도 사용 할 수 있는

코드를 만들 수 있습니다.
저작자 표시
신고
Posted by 용식

이번에 정리 할 패턴은 템플릿 메소드 패턴입니다.

이 패턴은 팩토리패턴이나 싱글턴 패턴과 함께 일반적으로 상당히 많이 사용되는 패턴입니다.

템플릿이라는 그 이름 그대로 상위 클래스에서 알고리즘을 규약하고 일부 공통된 메소드를 직접 구현하여

가지고 있으며, 하위 클래스에서 처리 해야 할 메소드를 추상 메소드로 가지고 있습니다.

한번 예제를 들어보겠습니다.

검색 엔진에서는 검색을 할 수 있도록 데이터 소스에 접근하여

데이터를 읽어오고, 이를 색인하고 최적화의 단계를 거쳐 색인 파일을 완성합니다.

그런데 이 데이터 소스라는 것이

DB일수도 있고, 파일들 일 수도 있을 것입니다.

아무튼, 데이터 소스가 다르고 파일을 읽어오는 것이 다르다는 것만 제외하면

아래와 같은 절차를 가질 것 입니다.

1. 데이터소스에 접근한다.
2. 데이터를 읽어온다.
3. 색인을 실시한다.
4. 최적화를 실시한다.

그래서 우선 각각 두개의 클래스를 만들어보겠습니다.

public class DBIndexing {
 public void connectDataBase() {
  System.out.println("데이터베이스에 접속");
 }
 
 public void selectData() {
  System.out.println("데이터를 셀렉트한다.");
 }
 
 public void indexing() {
  System.out.println("색인실시");
 }

 
 public void optimize() {
  System.out.println("최적화 실시");
 }

}
---------------------------------------------------------
public class FileIndexing {
 public void connectFileServer() {
  System.out.println("파일서버에 접속");
 }
 
 public void readFile() {
  System.out.println("파일을 읽는다.");
 }
 
 public void indexing() {
  System.out.println("색인실시");
 }

 
 public void optimize() {
  System.out.println("최적화 실시");
 }

}

클래스를 보시면 아시겠지만 색으로 처리가 되어있는 녀석들은 중복이 되는 부분입니다.

DB건 파일서버건 상관없이 일단 데이터를 가져온 후에는 똑같이 색인과 최적화를 수행하죠.

그렇다면, 나머지 두개의 메소드는 어떨까요?

파일서버와 DB에 접속하여 데이터를 가져오는 것이지만.. 이 메소드 자체를 조금 추상화 시켜버리면

connectDataSource와 readData 정도로 추상화 시킬 수 있을 것입니다.

대상과 방법이 조금 다를뿐이지, 일이 진행되는 흐름에 있어서

그 내용 자체가 크게 다른 것은 아닙니다.

이렇게 추상화된 메소드를 사용해서 템플릿 메소드 패턴으로 구현해보겠습니다.


public abstract class DataIndexing {
 final void indexingSequence() {
  connectDataSource();
  readData();
  indexing();
  optimize();
 }

 
 abstract void connectDataSource();
 abstract void readData();

 
 void indexing() {
  System.out.println("색인을 실시한다.");
 }
 
 void optimize() {
  System.out.println("최적화 실시");
 }
 
 void hook() {
  
 }
}


이것이 추상화 시킨 클래스입니다.
분홍색으로 표시된 부분이 바로 템플릿 메소드입니다.
색인 절차에 대한 템플릿을 지정하고 final로 선언하여 하위 클래스에서 재정의 할 수 없도록 만들어져있습니다.
이 클래스를 상속하여 구현하는 클래스들은 반드시 저 템플릿 메소드에서 지정되어있는 순서에 따라서
작동합니다. 일종의 프레임워크인 셈입니다.

그리고 추상 메소드가 두개있습니다.

이것이 바로 하위 클래스에서 자기 자신이 할 일에 맡게 오버라이드 해줘야 하는 메소드입니다.

그리고 색인과 최적화는 어느 클래스에서도 공통으로 사용되므로, 상위 클래스에서 구현하고 있습니다.

public class DBIndexing extends DataIndexing {

 @Override
 public void connectDataSource() {
  System.out.println("데이터베이스에 접속");
 }

 @Override
 public void readData() {
   System.out.println("데이터를 셀렉트한다.");
 }
}
-----------------------------------------------------------

public class FileIndexing extends DataIndexing {
 @Override
 public void connectDataSource() {
   System.out.println("파일서버에 접속");
 }

 @Override
 public void readData() {
  System.out.println("파일을 읽는다.");
 }
}



이런식으로 하위클래스가 구현됩니다.

사용하는측에서는

DataIndexing dataIndexing = new FileIndexing();
dataIndexing.indexingSequence();

이렇게 사용하면 되겠죠. 그럼 지정된 절차에 의해서 메소드가 동작 할 것입니다.

위에서 상위 클래스를 보면 hook()라는 메소드가 있습니다.

이것은 필요에 따라서 하위클래스에서 구현해도 되고 하지 않아도 되는 메소드입니다.

템플릿 메소드 자체에 들어가서 영향을 주지는 않지만,

하위클래스에서 알고리즘을 구현하는데 조금 유연성을 줄 수 있습니다.

예를 들어서 최적화를 실시하는데 부하가 오기 때문에 무조건 최적화를 시키는 것이 아니라 옵션에 따라서 실시를 한다.. 라고 하면.. 템플릿 메소드를 이렇게 고칠 수도 있을 것입니다.

public abstract class DataIndexing {
 final void indexingSequence() {
  connectDataSource();
  readData();
  indexing();
  if(doOptimize()) {
   optimize();
  }

 }
 
 abstract void connectDataSource();
 abstract void readData();
 
 void indexing() {
  System.out.println("색인을 실시한다.");
 }
 
 void optimize() {
  System.out.println("최적화 실시");
 }
 
 boolean doOptimize() {
  return true;
 }

}

doOptimize는 무조건 true를 리턴하도록 되어있습니다.

하위에서 오버라이드를 하지 않으면 optimize까지 실시합니다. 하위에서 오버라이드해서 특정 조건을 만족했을 때만

(뭐 현재 사용량을 보거나.. 시간을 확인하거나..등등)

true를 리턴하도록 오버라이드 할 수도 있을 것입니다.

이런식으로, 제공되는 메소드가 hook 메소드입니다.

이 hook메소드가 상위 클래스에서 제공되는 이유는 모두 아시겠지만..

하위 클래스에서 그냥 쑥 나와버리면..

DataIndexing dataIndexing = new FileIndexing();
dataIndexing.indexingSequence();
이런식의 사용해서는 이  hook() 메소드를 사용 할 수 없기 때문입니다.


이 패턴에서 가장 중요한 부분은 템플릿 메소드가 알고리즘의 골격을 정의하고 있다는 점입니다.

저작자 표시
신고
Posted by 용식

이번에 정리 해 볼 패턴은

어댑터 패턴과 퍼사드 패턴입니다.

어댑터 패턴은 하나의 클래스를 다른 클래스로 변경해주는 역할을 합니다.

이게 무슨 말인고하니..

플러그를 생각해보겠습니다.

구멍이 두개짜리가 우리가 쓰는 것이고, 유럽에서는 동그란 구멍두개와 네모 모양의 구멍이

하나있는 플러그를 사용합니다.

여기서 중간에 어댑터를 사용하여 국산 전자 제품도 유럽에서 사용 할 수 있습니다.

(물론 정격전압이 같아야 합니다 -_-)

이런 역할을 해주는 것이 어댑터 패턴입니다.

바로 예제 코드를 보겠습니다.

import java.util.Enumeration;
import java.util.Iterator;

public class EnumAdapter implements Iterator {
 Enumeration enumeration;
 
 public EnumAdapter(Enumeration enumeration) {
  this.enumeration = enumeration;
 }
 
 public boolean hasNext() {
  return enumeration.hasMoreElements();
 }
 
 public Object next() {
  return enumeration.nextElement();
 }
 
 public void remove() {
  throw new UnsupportedOperationException();
 }
}

<코드출처 : Head First Design Pattern>

Enumeration을 Itertator로 변경시킨 예제입니다.

이런식으로 서로 비슷한 행동을 하지만 (정격 전압은 맞아야 하는 것 처럼)

서로 완전히 다른 클래스 형일때 이런식으로 어댑터 패턴을 적용하여 사용 할 수 있습니다.

위의 반대의 경우도 가능하겠지요.

이 어댑터는 사용하는 측에서는 Iterator처럼 보여야 하기 때문에 Iterator를 구현하고

있습니다. (유럽에서 플러그가 유럽식처럼 생겨야 사용이 가능한 것 처럼요..)

remove() 메서드 같은 경우는 나름대로 구현을 하면 굳이 예외를 던지지 않아도 될지 모릅니다.


그리고 퍼사드 패턴이 있습니다.

이 패턴은 복잡한 행동들을 인터페이스를 사용해서 간단하게 만들어버리는 패턴입니다.

예를 들어서 자동차 원격 시동장치를 한번 생각해 보겠습니다.

원격 시동장치의 버튼을 누르면

시동이 걸리고, 사이드미러가 열리고, 히터가 작동하고, 네비게이션이 켜집니다.

사이드미러, 히터, 네비게이션을 클래스로 추출해내겠습니다.

public class SideMirror {
 public void open() {
  System.out.println("사이드 미러가 열립니다.");
 }
 
 public void close() {
  System.out.println("사이드 미러가 닫힙니다.");
 }
}

public class Navigation {
 public void turnOn() {
  System.out.println("네비게이션이 켜집니다.");
 }
 
 public void turnOff() {
  System.out.println("네비게이션이 꺼집니다.");
 }
}

public class Heater {
 public void on() {
  System.out.println("히터가 작동합니다.");
 }
 
 public void off() {
  System.out.println("히터가 꺼집니다.");
 }
}

Car 클래스입니다.

public class Car {
 public void startup() {
  System.out.println("시동을 겁니다.");
 }
 
 public void startoff() {
  System.out.println("시동을 끕니다.");
 }
}

그럼 한번 원격시동장치를 만들어보겠습니다.

public class RemoteControl {
 private Car car;
 private Heater heater;
 private Navigation navi;
 private SideMirror sideMirror;
 
 public RemoteControl(Car car, Heater heater, Navigation navi, SideMirror sideMirror) {
  this.car = car;
  this.heater = heater;
  this.navi = navi;
  this.sideMirror = sideMirror;
 }
 
 public void remoteStartUp() {
  car.startup();
  heater.on();
  navi.turnOn();
  sideMirror.open();
 }
}

사실 사이드미러나 히터등은 Car의 구성요소로 들어가는 것이 맞을지도 모르겠습니다.
아무튼, 하나의 행동을 하기위해 해야하는 여러 복잡한 행동을 하나의 메소드로 통합해서
제공하고 있습니다.

즉, 자동차의 시동은 켠다. 라는 것을 단순화 시켜서 제공하고 있는 것입니다.

그리고 그 작업은 실제 서브시스템에 들어있는 구성요소등에게 위임을 하고 있습니다.

실제로 위 리모컨을 사용하는 클라이언트 클래스에서는

Car라던가, SideMirror 등의 클래스에 대해서 전혀 몰라도 상관 없습니다. 단지

RemoteControl의 remoteStartup() 메소드만 알고 있으면 됩니다.

자동차의 구성요소나 서브시스템이 추가되었다고 해도, 혹은 내용 자체가 변경되었다고 하더라도

인터페이스로 제공되고 있는 remoteStartup() 메소드의 시그너쳐만 달라지지 않는다면

클라이언트는 아무런 변경이 필요가 없습니다.





 

신고
Posted by 용식
이 패턴은 요청을 한 녀석과 요청을 처리하는 녀석을 완전히 분리시키는 패턴입니다.

즉, 명령어 그 자체를 하나의 클래스로 만들어서

요청을 처리하는 녀석과 요청을 하는 녀석의 중간 고리를 느슨하게 만드는 역할을 하는겁니다.

실생활에서 비슷하게 예를 들만한 것이

멀티리모컨이 있을 것 같습니다.

요즘 리모컨은 하나의 리모컨에 TV, DVD, 케이블, 비디오등 여러개의 장치들을 하나의

리모컨에서 작동 시킬 수 있도록 만들어져 나옵니다.

물론, DVD나 TV등 각각의 고유한 동작들도 있겠지만

전원 on, off와 같은 장치의 종류에 상관없이 공통적으로 동작하는 명령도 있습니다.

이런 것을 명령어 자체를 하나의 인터페이스로 규약을 정해 놓고

(실행해야 하는 메소드가 어떤 것인지만 알고 있으면..)
 
요청측과 처리측을 분리하여 프로그래밍하는 커맨드 패턴을 사용하여 구현 할 수 있습니다.

이 커멘드 클래스들은 자기가 어느 객체에게 요청을 해야하는지를 알고 있어야합니다.

즉, 이 커멘드 클래스들 자체가 독립적으로도 사용이 될 수 있다는 것이죠.

만들다만, 와우짝퉁으로 한번 이야기해보겠습니다.

전투와 방어버튼이 있습니다.

그리고 전사와 사제, 도적, 마법사는 전투버튼을 누를 때 각각 고유의 행동을 합니다.

전사 - 칼을 뽑는다.
사제 - 축복 주문을 시전한다.
도적 - 그림자 숨기를 한다.
마법사 - 파이어볼을 캐스팅한다.

방어버튼을 누르면

전사 - 방패를 앞으로 든다.
사제 - 방어력 업 버프를 시전한다.
도적 - 백스탭을 한다.
마법사 - 블링크를 시전한다.

전투와 방어버튼은 하나뿐이지만, 여기에 어떤 대상이 연결되어 있느냐에 따라서
그 행동이 달라집니다. 물론 이 버튼 자체는 자기가 어떤 캐릭터에게 명령을 내리는지
알지 못 합니다.

게임 유저는 알고 있겠죠, 지금 전사를 조종하고 있으니까
전투 버튼을 누르면 칼을 뽑을 것이고 방어 버튼을 누르면 방패를 들 것이다 라고..

하지만 중요한 것은 버튼 그 자체는 대상을 모른다는 것입니다.

만약, 버튼자체가 자기가 명령을 내려야 하는 대상을 알아야 한다면
전사가 나왔을때는 전사용 버튼이, 사제가 나왔을 때는 사제용 버튼이 ..
이런식으로 대상에 맞는 버튼이 각각 나와줘야 할 것입니다.

그럼 이게 어떤식으로 구현이 되는 것인지 알아보겠습니다.

일단, 명령어 자체를 캡슐화 시킨다고 했습니다. 그리고 캐릭터와 버튼은 이 명령어를
둘 사이의 인터페이스로 사용합니다.

public interface Command {
 public void execute();
}

일단, 인터페이스를 하나 작성합니다.

이것이 버튼으로부터 객체에게 요청을 할 때 사용 할 Command 인터페이스입니다.

전사 클래스는 아래와 같습니다.

public class Warrior {
 
 public void attack() {
  System.out.println("칼을 뽑습니다.");
 }
 
 public void guard() {
  System.out.println("방패를 앞으로 듭니다.");
 }
}
battle은 칼을 뽑고 defence는 방어를 합니다.



일단 전사에 대한, 두개의 Command를 만들어보겠습니다.

public class WarriorBattleCommand implements Command {
 
 Warrior warrior;
 
 public WarriorBattleCommand(Warrior warrior) {
  this.warrior = warrior;
 }
 
 public void execute() {
  warrior.attack();
 }
}
------------------------------------------------------------

public class WarriorDefenceCommand implements Command {
 
 Warrior warrior;
 
 public WarriorDefenceCommand(Warrior warrior) {
  this.warrior = warrior;
 }
 
 public void execute() {
  warrior.guard();
 }
}

대충 감이 오시는 분도 계실 것입니다.
이 커멘드 클래스는 자기가 어떤 대상에게 요청을 해야하는지 알고 있습니다.
위의 경우에는 Warrior가 그 대상이죠. 그리고, 클래스명을 봐도 알 수 있듯이
명령어 그 자체가 클래스로 구현되어져 있는 것입니다.

그러면 이것을 호출 할 버튼은 어떻게 구현이 될까요..?

public class ButtonControl {
 Command command;
 
 public void setCommand(Command command) {
  this.command = command;
 }
 
 public void buttonPress() {
  command.execute();
 }

}

버튼은 보시면 아시겠지만, 전투와 방어 버튼의 구분이 없습니다.

전투와 방어라는 것 자체는 Command 인터페이스를 구현한 클래스로 만들어지고

이 버튼은 단지 그 command의 execute()메서드를 실행하면 된다라는 것만 알고 있을 뿐입니다.

대상과 처리를 요청하는 부분이 분리가 되는 것입니다.

일단 테스트를 해보겠습니다.

public class Test {
 public static void main(String[] args) {
  ButtonControl bc = new ButtonControl();
  Warrior warrior = new Warrior();
  WarriorBattleCommand warriorBattleCommand = new WarriorBattleCommand(warrior);
 
  bc.setCommand(warriorBattleCommand);
  bc.buttonPress();
 
  WarriorDefenceCommand warriorDefenceCommand = new WarriorDefenceCommand(warrior);
  bc.setCommand(warriorDefenceCommand);
  bc.buttonPress();
 }
}

일단 버튼 객체를 생성하고, 명령을 수행 할 캐릭터(Warrior)를 생성합니다.
그리고, 그 캐릭터가 수행 할 명령을 생성 한 후 Warrior를 셋팅합니다.
그리고 buttonPress를 수행하면

칼을 뽑습니다.
방패를 앞으로 듭니다.
이렇게 실행이 됩니다.

잘 생각해보시면.. 일단 warriorBattleCommand 이 자체로도 자기가 이 명령을 요청 할 대상을
알고 있습니다. 이게 어떤 장점이 있는가하면..

만약에 버튼이 바뀌었다고 해보겠습니다. UI가 변경되어서 키보드가 아닌 마우스로 조종을
하게 된 것이죠. 그렇다고해도 크게 문제 될 부분이 없는 것이, command의 execute() 메소드만 실행하면 된다는 것만 알고 있으면, 행동을 요청하는 측이 무엇이 되던지간에 상관이 없기 때문입니다. 즉

public class MouseControl {
 Command command;
 
 public void setCommand(Command command) {
  this.command = command;
 }
 
 public void click() {
  command.execute();
 }

}

이런식으로 말이죠..

이 커멘드 패턴에서 요청을 받아 처리하는 측(Warrior)을 Receiver라고 얘기합니다.
그리고 ButtonControl과 같은 클래스를 Invoker라고 합니다. 이 인보커는 명령을 가지고 있고.
execute() 메소드를 호출하면 커멘드 객체에게 특정 작업을 수행하도록 요청 할 수 있다는 사실을
알고 있습니다.

그리고 Command는 모든 커멘드 객체에서 구현해야 할 인터페이스입니다.

이것을 구현해야만 Invoker가 (ButtonControl) execute()를 호출하면 명령을 처리 할 수
있다.라는 서로의 약속을 지킬 수 있게 됩니다.

그리고 이 Command 인터페이스를 구현한 클래스를
ConcreteCommand라고 합니다. 이녀석은 Receiver(Warrior)의 메소드를 호출하여
명령을 처리합니다.

그리고 Test 클래스에 해당하는 것이 Client입니다.

이제, 사제 클래스를 한번 보겠습니다.

사제 클래스는 앞에서 얘기했듯이

battle버튼을 누르면
사제 - 축복 주문을 시전하고
방어버튼을 누르면
사제 - 방어력 업 버프를 시전합니다.

그런데 이 주문 시전의 절차가 조금 복잡스러워서

1. 책을 꺼내고
2. 대상을 설정하고
3. 주문을 외운다.

이 3단계를 거쳐야 주문이 시전된다고 합니다.

이런 것은 어떻게 구현하면 될까요?

public class Priest {
 
 public void castBless() {
  System.out.println("축복 주문을 시전!!");
 }
 
 public void defenceUpBuff() {
  System.out.println("방어도 업!!");
 }
 
 public void takeBook() {
  System.out.println("책을 꺼냅니다.");
 }
 
 public void targeting() {
  System.out.println("대상을 설정한다.");
 }
 
 public void casting() {
  System.out.println("주문을 외우는 중입니다...");
 }
}
주문을 외우기 위해 사제클래스는 위와 같은 행동들을 가지고 있습니다.

그러면, battle버튼을 눌렀을때의 command는 어떻게 만들어지면 될까요?

public class PriestBattleCommand {
 Priest priest;
 
 public PriestBattleCommand(Priest priest) {
  this.priest = priest;
 }
 
 public void execute() {
  priest.takeBook();
  priest.targeting();
  priest.casting();
  priest.castBless();

 }
}
눌려지는 Button은 battle 버튼 하나지만 실제로 실행되는 것은 각각의 receiver(Priest와 Warrior)의 메소드들입니다.

public class Test {
 public static void main(String[] args) {
  ButtonControl bc = new ButtonControl();
  Warrior warrior = new Warrior();
  WarriorBattleCommand warriorBattleCommand = new WarriorBattleCommand(warrior);
 
  bc.setCommand(warriorBattleCommand);
  bc.buttonPress();
 
  WarriorDefenceCommand warriorDefenceCommand = new WarriorDefenceCommand(warrior);
  bc.setCommand(warriorDefenceCommand);
  bc.buttonPress();
 
  System.out.println("\n사제는?");
  Priest priest = new Priest();
  PriestBattleCommand priestBattleCommand = new PriestBattleCommand(priest);
  bc.setCommand(priestBattleCommand);
  bc.buttonPress();
 }
}

Button클래스는 한번만 생성되었지만
셋팅되는 Command에 따라서 각각의 Receiver가 알맞은 행동을 할 수 있습니다.

이것은 Command 클래스를 사용하는 법만 알고 있다면 그것이 버튼이던 마우스던
손가락이던 리모컨이던 어느 곳에서도 위와 똑같은 행동을 요청하고 처리 할 수 있다는 뜻입니다.

요청하는측과 실제 일을 하는 측이 완전히 분리되어버린거죠.

그리고 이것은 Command가 그 자체로도 의미가 있도록 캡슐화가 되어있기 때문에
각각의 Command 클래스들을 큐에 넣어놓고, 스케쥴링이나 작업큐등 다양한 용도로도 사용 할
수 있습니다.

만약에 쓰레드 A가 priestBattleCommand를 큐에 넣어놨는데
쓰레드 B가 이 priestBattleCommand를 처리 할 수도 있습니다.
단지 Command의 execute 메소드를 호출하면 되니까요
(실제로 무슨 명령어인지 누가 그 명령을 처리 할건지 몰라도 상관없습니다!)


신고
Posted by 용식

너무나 잘 알려진 패턴입니다.

단 하나의 인스턴스를 유지하는 것입니다.

아마 현장에서도 가장 많이 쓰이고 있는 패턴 중 하나가 아닐까 싶습니다.

생성자를 private으로 선언하여 다른 클래스에서 이 클래스의 객체를 직접 생성시키지 못 하게

하고, static 으로 선언 된 자기 자신의 객체를 넘겨주는 메소드를 만들어

이미 생성되어 있는 자기 자신의 객체를 넘겨주는 방식입니다.

public class Singleton {
 private static Singleton instance;
 
 private Singleton() { }
 
 public static Singleton getInstance() {
  if(instance == null) {
   instance = new Singleton();
  }
 
  return instance;
 }
}

일반적으로 사용되는 싱글턴 패턴의 형식입니다.

getInstance  메서드는 Singleton 인스턴스가 있으면 그것을 리턴하고

없으면, 새로 생성하여 리턴합니다.

하지만, 위와 같은 경우에는 문제가 생길 수 있습니다.

다수의 스레드가 getInstacne 메소드를 실행하는 경우

instance 객체가 2개 이상 생성 될 수 있기 때문입니다.

예를 들어 쓰레드 A와 B가 getInstance 메소드를 거의 동시에 실행 되었을 경우

jvm의 스케쥴링에 따라서 어떤 경우에는 아래와 같은 현상이 발생 할 수 있습니다.

쓰레드 A가 instance == null 임을 확인

쓰레드 A wait

쓰레드 B가 instance == null 임을 확인

쓰레드 B가 instance = new Singleton(); 를 실행

쓰레드 B가 return instance로 객체를 리턴.

쓰레드 A가 instance = new Singleton(); 를 실행

쓰레드 A가 return instance로 객체를 리턴.

위 처럼
서로 다른 객체가 리턴 되는 경우가 있을 수 있습니다.

이런 멀티스레딩을 해결 하는 간단한 방법 중 하나는 synchronized를 사용하는 것 입니다.


 public static synchronized Singleton getInstance() {
  if(instance == null) {
   instance = new Singleton();
  }
메소드 자체에 동기화가 걸려버리는거죠.. 하지만 위의 경우.. 멀티스레드로 인해 문제가 생기는 경우는 instance가 처음 생성 될 때 뿐입니다. 즉, 일단 instance 변수에 Singleton의 객체 레퍼런스를 대입하고 나면 이 메소드를 통채로 동기화를 시킬 필요는 없는 것입니다.

괜히 오버헤드만 증가하죠..

그래서 나온 다른 하나의 방법은 아예 처음부터 만들어버리는 것입니다.

public class Singleton {
 private static Singleton instance = new Singleton();
 
 private Singleton() { }
 
 public static Singleton getInstance() {
  return instance;
 }
}

그리고 다른 하나의 방법은 volatile 을 사용하는 것입니다.
책에서는 DCL이라고 하네요. Double-Checking Locking.

상세한 내용은

http://javaservice.net/~java/bbs/read.cgi?m=qna&b=qna2&c=r_p_p&n=1088474804

이곳에 서민구(4baf)님께서 작성하신 댓글을 읽어보시면 좋을 것 같습니다.

아무튼.. 이 volatile을 이용하면

public class Singleton {
 private volatile static Singleton instance;
 
 private Singleton() { }
 
 public static Singleton getInstance() {
  if(instance == null) {
   synchronized(Singleton.class) {
    if(instance == null) {
     instance = new Singleton();
    }
   }
  }
  return instance;
 }
}

volatile을 사용했음에도 synchronized(Singleton.class)를 사용한 이유는
안전장치라고 생각해도 될 것 같습니다.

일단 한번 인스턴스가 생성 된 이후에는 첫번째 if(instance == null) { } 이 블럭 안으로
쓰레드가 들어 갈 일이 없을테니까요.

메서드 전체에 syncronized를 걸어놓지 않았기 때문에 일단 처음 instance 변수에
Singleton 객체 레퍼런스를 대입 할 때를 제외하고는 성능의 저하도 없을 것입니다.

다만 이 방식은 자바5 부터 사용 하시길 권장합니다.

그리고 속도의 문제가 큰 이슈거리가 아니시라면 그냥 메소드 전체에
synchronized 를 사용하셔도 동기화 문제는 처리 하실 수 있습니다.
신고
Posted by 용식
Abstract Factory Pattern

추상 팩토리 패턴입니다.

이것은 팩토리를 추상화하여 사용하는 방법입니다.

여기서는 각 나라의 햄버거와 샌드위치를 모두 함께 판매하는 total-store를 만들기로

했습니다.

컨셉은 각나라의 햄버거/샌드위치 공장에서 햄버거와 샌드위치를 만들어서 가져와

파는 것입니다.

이것을  전체적인 패턴을 추상 팩토리 패턴으로 구현해보겠습니다.

일단 샌드위치와 햄버거를 생산 할 공장의 인터페이스를 정의하겠습니다.


public interface ProductFactory {
 public Hamburger createHamburger();
 public Sandwich createSandwich();
}


추상 팩토리 패턴에서는 추상화된 공장과 추상화된 상품이 존재합니다.

위의 코드에서는 두개의 메소드가 존재합니다. 햄버거를 만드는 것과 샌드위치를 만드는 것입니다.

각 나라별로 햄버거와 샌드위치가 다르기 때문에 이 인터페이스를 구현 한 구상 팩토리를 만들

어야 합니다.

그러면 실제로 TotalStore에서는 이 공장을 사용해서 원하는 제품을 가져 올 수 있는거죠...

일단 구상 팩토리를 보겠습니다.

public class ChinaProductFactory implements ProductFactory {

 @Override
 public Hamburger createHamburger() {
  return new ChinaBicmacBurger();
 }

 @Override
 public Sandwich createSandwich() {
  return new ChinaSandwich();
 }

}
---------------------------------------------------------------

public class KoreaProductFactory implements ProductFactory {

 @Override
 public Hamburger createHamburger() {
  return new KoreaBicmacBurger();
 }

 @Override
 public Sandwich createSandwich() {
  return new KoreaSandwich();
 }

}

그러면 이것을 사용하는 TotalStore의 코드입니다.

public class TotalStore {
 ProductFactory factory;
 
 public void setFactory(ProductFactory factory) {
  this.factory = factory;
 }

 
 public Hamburger orderHamburger() {
  return factory.createHamburger();
 }
 
 public Sandwich orderSandwich() {
  return factory.createSandwich();
 }
}

위에서 볼드처리된 부분이 핵심입니다.

TotalStore에서는 만들고 싶은 상품에 따라서 팩토리를 교체하여 만들어 낼 수 있습니다.

실제로 햄버거나 샌드위치를 만들때도 결국은 factory에게 그것을 위임시키고 있습니다.

한국빅맥햄버거이던 중국샌드위치이건 그것은 factory만 셋팅이 되면

factory가 알아서 만들어 가져 올 것이고 TotalStore에서는

단지 Hamburger나 Sandwich 형식의 물건이 오면 그것을 믿고

사용하겠다는 것입니다.



햄버거클래스와 샌드위치 클래스를 보겠습니다.

TotalStore에서 공장에 믿고 맡기기 위해서는 최소한 Hamburger형인지 Sandwich형인지는

서로 주고 받을 약속이 되어 있어야 합니다.

그것이 중국식샌드위치인지 한국빅맥햄버거인지까지는 알필요가 없다고 하더라도요..

그래서 , 이 패턴에서는 햄버거와 샌드위치의 인터페이스(추상 클래스를 포함합니다)를

뽑아냅니다.

public abstract class Hamburger {
 String name;
 String meat;
 String bread;
 String source;
 
 public void prepare() {
  System.out.println("재료를 준비합니다..." + name);
 }
 
 public void roastPetty() {
  System.out.println(meat + "를 구워냅니다.");
 }
 
 public void mixStuff() {
  System.out.println(bread + "와 "+meat+"패티와 " + source + "를 사용해 햄버거를 만듭니다.");
 }
 
 public void box() {
  System.out.println("포장을 합니다.");
 }
 
 public void create() {
  prepare();
  roastPetty();
  mixStuff();
  box();
 }
}
--------------------------------------------------------
public class KoreaBicmacBurger extends Hamburger {
 
 public KoreaBicmacBurger() {
  name = "Korea BicMac Burger";
  meat = "한우소고기";
  bread = "쌀빵";
  source = "머스터드";
 
  create();
 }
}



 

public abstract class Sandwich {
 String name;
 String meat;
 String bread;
 String vege;
 
 public void prepare() {
  System.out.println("재료를 준비합니다..." + name);
 }
 
 public void roastPetty() {
  System.out.println(meat + "를 구워냅니다.");
 }
 
 public void mixStuff() {
  System.out.println(bread + "와 "+meat+"패티와 " + vege + "를 사용해 샌드위치를 만듭니다.");
 }
 
 public void box() {
  System.out.println("포장을 합니다.");
 }
 
 public void create() {
  prepare();
  roastPetty();
  mixStuff();
  box();
 }
}
-----------------------------------------------------------
public class ChinaSandwich extends Sandwich {
 public ChinaSandwich() {
  name = "China Sandwich";
  meat = "계란과 햄 믹스";
  bread = "식빵";
  vege = "토마토";
 
  create();
 }
}


 햄버거와 샌드위치 구상 클래스의 생성자에 들어가있는 create() 메서드는 너무 신경쓰지 말아주세요.

그냥 이녀석이 만들어진다. 라는 것을 보여주기 위한 편법일 뿐입니다.

중요한 것은 햄버거와 샌드위치의 인터페이스로부터 구상 클래스가 만들어졌다는 것입니다.

테스트 코드는 아래와 같습니다.

public class Test {
 public static void main(String[] args) {
  TotalStore store = new TotalStore();
  System.out.println("난 한국 빅맥 버거를 먹을거에요");
  store.setFactory(new KoreaProductFactory());
  store.orderHamburger();
 
  System.out.println("난 중국 샌드위치를 먹을거에요");
  store.setFactory(new ChinaProductFactory());
  store.orderSandwich();
 }
}


 

난 한국 빅맥 버거를 먹을거에요
재료를 준비합니다...Korea BicMac Burger
한우소고기를 구워냅니다.
쌀빵와 한우소고기패티와 머스터드를 사용해 햄버거를 만듭니다.
포장을 합니다.

난 중국 샌드위치를 먹을거에요
재료를 준비합니다...China Sandwich
계란과 햄 믹스를 구워냅니다.
식빵와 계란과 햄 믹스패티와 토마토를 사용해 샌드위치를 만듭니다.
포장을 합니다.

재미있는 것은 팩토리부분을 보시면 어디서 많이 보던 형식이 나타납니다.

ProductFactory의
 public Hamburger createHamburger();
 public Sandwich createSandwich();

이 두개의 메서드를 실제 구현하는 부분은 ProductFactory를 구현한

구상 클래스들입니다. 이건 패토리 메소드 패턴이죠^^

추상 팩토리 패턴에는 팩토리 메소드 패턴도 같이 사용이 됩니다.


팩토리 메소드 패턴은 메소드를 추상화시켜 하위에서 구현하도록 한 것이고

추상 팩토리 패턴은 팩토리 자체를 추상화 시켜서 하위 클래스에서 구현을 하도록 한 것입니다.

그렇기 때문에 그 과정에서 자연스럽게 추상 팩토리에 있는 메서드를 구현하게 되고

이 과정에서 팩토리 메소드 패턴이 사용되는 것입니다.


앞의 3개의 팩토리 패턴은 결국 패턴을 이루고 있는 구성의 차이는 있지만

만들어지는 무언가를 추상화 시켜서 (혹은 만드는 무언가를 ) 만드는 부분과

사용하는 부분의 의존성을 최소화 시키려는데

그 목적이 있습니다.

사용하는 부분에서 만들어지는 녀석의 구현 클래스를 가지고 작업을 하고 있으면

구현 클래스의 변경이라든가, 새로운 구현 클래스의 추가라는 작업이 생길 때 마다

사용하는 녀석과 만드는 녀석 모두를 변경해줘야 하는 부담이 생기게 됩니다.


하지만, "나는 니가 한국빅맥버거를 줄지, 중국빅맥버거를 줄지 그것은 잘 모르지만

그게 햄버거라는 것은 알아. 그리고 햄버거라는 것은 나라에 상관 없이 공통의 행동을

가지고 있지. 그리고 난 그 행동이 필요한거야." 라고 추상화를 시켜버리면 나중에

미국식 햄버거가 새로 생겼다 하더라도 그것이 햄버거라는 형식을 가지고 있으면

(extends나 implements 하고 있으면) 사용하는 측의 수정은 전혀 필요없이

단순히 햄버거를 상속 혹은 구현하고 있는 미국빅맥버거만 만들어주면 되는 것입니다.

공장이 추상화 된 경우에는

"나는 니가 한국햄버거와 샌드위치를 만드는 공장인지 중국햄버거와 샌드위치를

만드는 공장인지 잘 모르지만, 아무튼 니가 createHamburger와 createSandwich

메소드를 가진 공장이라는 것은 알고있고, 그것들을 실행하면 내가 원하는 것을

얻을 수 있다는 건 알고있어." 정도가 되겠지요 ^^

미국식 햄버거와 샌드위치를 추가로 팔기로 했다면, 그것을 만드는 공장만 하나

생성해서 사용하면 ok가 되는 것입니다.



신고
Posted by 용식
Factory Method Pattern.

이번에 정리 할 내용은 팩토리 메소드 패턴이네요..

여기서 중요한 것도 역시 변화되는 부분을

따로 캡슐화 시킨다는 것입니다.

전 세계적으로 프랜차이즈를 하고 있는 햄버거집이 있습니다.

햄버거를 만드는데는 절차가 필요합니다.

재료를 준비하고 - 패티를 굽고 - 패티,빵,야채를 조합하고 - 포장을 합니다.

이 순서는 전세계 어디서나 똑같이 적용됩니다.

하지만 나라별로 그 나라 사람들의 기호에 따라서 햄버거의 재료에 조금씩 차이가 있습니다.

예를들어 중국은 돼지고기와 밀빵 케첩을 사용하고

한국은 한우와 쌀빵 머스터드소스를 사용합니다.

미국은 닭고기와 밀가루빵 마요네즈를 사용합니다.

(물론 실제로 저렇지는 않죠.. -_-)

하지만, 햄버거를 만드는 절차 자체는 모든 프랜차이즈점이 동일해야 합니다.

그러면, 햄버거 가게를 한번 만들어 보겠습니다.


public abstract class HamburgerStore {
 
 public HamBurger orderHamburger(String type) {
  HamBurger hamburger;
 
  hamburger = createHamburger(type);
 
  hamburger.prepare();
  hamburger.roastPetty();
  hamburger.mixStuff();
  hamburger.box();
 
  return hamburger;
 }
 
 abstract HamBurger createHamburger(String type);
}


햄버거를 만드는 절차는 동일하기 때문에, 추상클래스인 HamburgerStore에서 그 부분을

구현해 놓습니다. 일종의 프레임웍을 잡아 놓은 셈이죠. 하지만, 국가별로 햄버거의 스타일은

조금씩 다릅니다. 그 달라지는 부분을 캡슐화 시켜낸 것이 포인트입니다.

보시면 createHamburger(String type) 메소드가 추상 메소드로 선언되어 있습니다.

즉, 이 HamburgerStore를 상속해서 만들어지는 KoreaHamburgerStore나 ChinaHamburgerStore는

저 추상메서드 createHamburger(String type), 즉 국가별로 조금씩 스타일이

다른 햄버거를 리턴해주는 메소드를 구현해야 합니다.

바뀌는 부분은 너희들이 알아서 바꿔서, 나한테는 아무튼 Hamburger만 넘겨주면

잡혀있는 프레임웍에 맞춰서 햄버거를 만들게.
라는 것입니다.

그럼, 한국지점과 중국지점의 스토어를 만들어 보겠습니다.


public class KoreaHamburgerStore extends HamburgerStore {

 @Override
 public HamBurger createHamburger(String type) {
  if("bigmac".equals(type)) return new KoreaBicmacBurger();
  else if("double".equals(type)) return new KoreaDoubleBurger();
  else return null;
 }
}
-----------------------------------------------------------------
public class ChinaHamburgerStore extends HamburgerStore {
 @Override
 public HamBurger createHamburger(String type) {
  if("bigmac".equals(type)) return new ChinaBicmacBurger();
  else if("double".equals(type)) return new ChinaDoubleBurger();
  else return null;
 }
}


우리나라나 중국이나 빅맥이나 치킨버거 메뉴는 동일하게 존재하지만 위에서 언급했다시피

거기에 사용되는 재료는 조금씩 차이가 납니다.

그 부분을 감안해서 햄버거 클래스를 만들어 보겠습니다.

public abstract class Hamburger {
 String name;
 String meat;
 String bread;
 String source;
 
 public void prepare() {
  System.out.println("재료를 준비합니다..." + name);
 }
 
 public void roastPetty() {
  System.out.println(meat + "를 구워냅니다.");
 }
 
 public void mixStuff() {
  System.out.println(bread + "와 "+meat+"패티와 " + source + "를 사용해 햄버거를 만듭니다.");
 }
 
 public void box() {
  System.out.println("포장을 합니다.");
 }
}

재료에 대한 상태를 가질 수 있게 인스턴스 변수가 선언되어 있고
햄버거를 만드는 과정이 기본적으로는 구현되어 있습니다. 필요한 곳에서 이것을 오버라이드해서

사용해도 될겁니다. 상세 햄버거 클래스를 보겠습니다.


public class ChinaBicmacBurger extends Hamburger {

 public ChinaBicmacBurger() {
  name = "China BicMac Burger";
  meat = "돼지고기";
  bread = "밀빵";
  source = "캐첩";
 }
}
-----------------------------------------------------------
public class KoreaBicmacBurger extends Hamburger {
 
 public KoreaBicmacBurger() {
  name = "Korea BicMac Burger";
  meat = "한우소고기";
  bread = "쌀빵";
  source = "머스터드";
 }
}

국가별로 조금씩 다른 재료를 하위 클래스에서 직접 set 해주고 있습니다.


 public class Test {
 public static void main(String[] args) {
  HamburgerStore store = new KoreaHamburgerStore();
  HamburgerStore store2 = new ChinaHamburgerStore();
 
  Hamburger burger = store.orderHamburger("bigmac");
  Hamburger burger2 = store2.orderHamburger("bigmac");
 
 }
}

재료를 준비합니다...Korea BicMac Burger
한우소고기를 구워냅니다.
쌀빵와 한우소고기패티와 머스터드를 사용해 햄버거를 만듭니다.
포장을 합니다.
재료를 준비합니다...China BicMac Burger
돼지고기를 구워냅니다.
밀빵와 돼지고기패티와 캐첩를 사용해 햄버거를 만듭니다.
포장을 합니다.

햄버거를 만들기 위해서 store를 만들고 orderHamburger만 호출하면 됩니다.

그리고 HamburgerStore도 햄버거를 만드는데 있어서 실제로 그 햄버거의 구현 객체가 무엇인지

신경쓰지 않습니다. Hamburger 타입만 받으면 그것을 가지고 그냥 정해진 절차에 따라서

햄버거를 만들뿐입니다.

  Hamburger hamburger;
 
  hamburger = createHamburger(type);

"나는 니가 한국빅맥버거인지 중국빅맥버거인지 신경쓰지 않아. 하지만 Hamburger가 리턴될 거라는 것은 알고 있고, Hamburger라면 준비하고, 고기굽고, 재료를 믹스하고 포장을 할 수 있다는 것도 알지."

.

지금 이것이 팩토리메소드 패턴입니다.

여기서 HamburgerStore가 생산자 클래스가 되고 그것을 상속한

KoreaHamburgerStore가 바로 구현 생산자 클래스가 됩니다.

그리고 Hamburger와 그것을 상속한 KoreaBigMacBurger가 바로 제품 클래스가 됩니다.

팩토리에서 제품을 생산하듯이 Store에서 Hamburger를 만들어 내죠.

구현 생산자 클래스인 KoreaHamburgerStore와 ChinaHamburgerStore에는 각각 나라의

햄버거에 대한 모든 사항이 캡슐화 되어 있습니다.

이 패턴이 앞에 작성한 심플 팩토리와 다른 가장 큰 차이점은

심플팩토리는 일회용으로 객체를 리턴시키는 반면에

이 팩토리 메소드 패턴은 어떤 구현체를 사용할지 서브클래스에서 결정하는 일종의

프레임워크를 만들 수 있다는 부분입니다. (HamburgerStore의 orderHamburger(String type) 메소드)




신고
Posted by 용식
팩토리 패턴은 싱글턴 패턴과 함께 이미 상당히 널리 쓰이고 있는
디자인 패턴입니다.

이 팩토리 메소드 패턴에는 3가지 유형이 있습니다.

헤드퍼스트 디자인 패턴의 글을 인용해보면

심플팩토리와 추상 팩토리 패턴 그리고 팩토리 메소드 패턴입니다.

이 책에서는 엄격히 얘기하면 심플 팩토리는 패턴은 아니라고 합니다..

하지만 아마 가장 많이 쓰이는 방식이 이 심플 팩토리가 아닐까 싶습니다.

그래서 팩토리 패턴은 3번에 나눠서 내용을 정리하려고 합니다.

하나에 전부 정리하려니 은근 빡세서요 -_-;

일단 심플 팩토리 먼저 정리해보겠습니다.

이 패턴은 추상화를 이용합니다. 그래서 하나의 구현 클래스에 집중되어 있는 의존도를

끊어버리는 것이죠. 결국 "나는 상세 구현 클래스가 무엇인지는 관심없다."

입니다.

아래와 같은 클래스를 보겠습니다.


public abstract class Animal {
 String name;
 
 public abstract void sing();
}

추상클래스 Animal을 만들고 이것을 상속해서 개,고양이,오리 클래스를 만듭니다.

public class Cat extends Animal {

 @Override
 public void sing() {
  System.out.println("야옹!");  
 }
}

--------------------------------------
public class Dog extends Animal {

 @Override
 public void sing() {
  System.out.println("멍!");  
 }
}
---------------------------------------
public class Duck extends Animal {

 @Override
 public void sing() {
  System.out.println("꽥!");
 }
}

그리고 이 동물들이 소리를 내도록 하는 클래스가 있습니다.

public class AnimalSing {
 String animal;
 Animal a;
 
 public AnimalSing(String animal) {
  this.animal = animal;
 }
 
 public void listen() {
  if("dog".equals(animal)) {
   a = new Dog();
   a.sing();
  } else if("cat".equals(animal)) {
   a = new Cat();
   a.sing();
  } else if("duck".equals(animal)) {
   a = new Duck();
   a.sing();
  } else
   System.out.println("이 동물은 뭔가요?");
 }

}
그래도 상속도 이용했고 해서 구현이 되어있지만, 문제는 AnimalSong의 listen 메소드입니다.
저정도의 클래스 갯수라면 AnimalSing 클래스에서 저렇게 되어 있는 것도 나쁘지는 않을 것입니다. 무조건 패턴화 시킨다고 좋은 것은 아니니까요.

어쩔때는 불필요하게 복잡도를 증가시키는 경우도 있습니다.

하지만 동물클래스가 엄청 많다거나
혹은 이제 더 이상 고양이는 쓰지 않는다고 해서 위 메소드에서 고양이 부분을 지워야 한다는등의
작업 요건이 생기게 되면 (그리고 이런 변화는 항상 생기게 마련이죠..)
문제가 생깁니다.

물론 어디선가는 위의 코드처럼 각각의 동물 클래스를 생성해서 넘겨주는 부분은 필요합니다.

하지만 그 부분이 위의 AnimalSing 클래스는 아니라는 거죠.

AnimalSing 클래스의 listen메서드는 말그대로 Animal (상세가 뭔지는 몰라도) 을 상속한 동물 클래스들의 소리만 들려주면 됩니다. 그 동물들이 뭐냐는 것까지 AnimalSing 클래스가 판단해서 생성 할 필요까지는 없는거죠. 위의 경우 단순히 sing()메서드만을 호출하기 때문에 간단해 보이지만 이런 내용이 복잡해져서 길고 긴 코드가 listen메서드에 들어가 있다면

단순히 고양이 클래스를 생성하는 부분을 수정하려 했다가 다른 동물들의 소리까지 못 듣게 되버리는 상황이 발생 할 수도 있습니다.

원래 더욱 안 좋은 케이스의 코드는 Animal이라는 추상 클래스조차 사용하지 않은 것이지만
(어떤 케이스일까요..?)
거기부터 얘기를 하면 리팩토링에 대한 얘기가 되어버리기 때문에 ... 그냥 Animal 클래스를 사용해서 상속을 이용해 구현해놓은 코드를 예제로 만들어 보았습니다.

그래서 사용하는 것이 심플 팩토리입니다.

공장 내부에서 무엇을 하는지는 모릅니다. 그냥 Animal이 올 것이라는 것만 알죠.
그리고 그 Animal은 sing()이라는 메서드를 가지고 있고, 받는 입장에서는 (AnimalSong)
그냥 그 sing메서드를 호출하면 됩니다. 이렇게 함으로써 AnimalSong 클래스와
Dog,Cat,Duck 클래스들은 의존성이 약해지게 되는 것입니다.

Animal 이라는 추상클래스 덕분에요.

심플 팩토리는 아래와 같이 작성 될 것입니다.

public class AnimalFactory {
 
 public Animal getAnimal(String animal) {
  if("dog".equals(animal)) {
   return new Dog();
  } else if("cat".equals(animal)) {
   return new Cat();
  } else if("duck".equals(animal)) {
   return new Duck();
  } else
   return null;
 }
}

이름을 받아서 해당되는 클래스의 객체를 넘겨줍니다. Animal형이죠.

사용되는 부분은

public class AnimalSing {
 String animal;
 Animal a;
 
 public AnimalSing(String animal) {
  this.animal = animal;
 }
 
 public void listen() {
  AnimalFactory af = new AnimalFactory();
  Animal a = af.getAnimal(animal);
  a.sing();
 }
}
이런식으로 구현됩니다. 중요한 것은 이 클래스에서는 Cat이나 Dog나 Duck에 대해서는
아무것도 모른다는 것입니다. 단지 넘어올 타입이 Animal이라는 것만 알고 있으면 되는거죠.

만약에 sing()메서드의 시그너쳐가 변하게 되면 당연히 이 부분도 수정이 되어야 하지만
그렇게 인터페이스(추상클래스와 인터페이스를 모두 지칭합니다)가 수정되는 경우는 어쩔 수 없습니다. (그래서 인터페이스 설계가 중요합니다.)

public class Test {
 public static void main(String[] args) {
  AnimalSing as = new AnimalSing("dog");
  as.listen();
 }
}
멍!


간단한 테스트 코드는 위와 같습니다.

위의 경우 name이 미리 정의되어 있는 단어가 안들어가면(dog,duck.cat 이외의 단어) 사실 예외가 발생합니다. 이런 경우 enum을 사용하면 도움이 될 수 있습니다.
신고
Posted by 용식

데코레이터 패턴은 상속과 위임을 이용한 패턴입니다.

하나의 객체를 장식하듯이 감싸고, 자신이 장식하고 있는 객체에게 행동을 위임합니다.

우선은 다시 와우로 돌아가서,

임의로 캐릭터를 하나 생성한다고 가정해보겠습니다.

직업을 고르고, 그 캐릭터에게 입힐 기본적인 방어구셋을 정합니다.

그러면 그 방어구에 따라서 캐릭터의 방어도가 결정되지요..

물론 이 캐릭터도 기본적인 방어도는 가지고 있습니다.

이것을 단순히 상속만을 이용해서 구현하려고 해보겠습니다.

public abstract class Character {
 String desc;
 
 public String getDesc() {
  return desc;
 }
 
 public abstract int def();
}

------------------------------------------------
public class Priest extends Character {

 public Priest() {
  desc = "연습생 로브를 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //성직자의 기본 방어도는 10입니다.
  return 10;  
 }
}
---------------------------------------------------------
public class Warrior extends Character {

 public Warrior() {
  desc = "가죽갑옷을 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //전사의 기본 방어도는 100입니다.
  return 100;  
 }
}

전사와 성직자 캐릭터는 생성시 기본적인 옷을 입고 있는 상태로 생성이 되며
기본적인 방어도를 가지고 있습니다.

여기서 캐릭터를 선택 할 때 몇몇 갑옷을 선택 할 수 있다고 가정해보겠습니다.
그것을 투구,장화,장갑으로 가정해보겠습니다.
이상태에서 이 문제를 해결하기 위해서는 Character 클래스에 투구,장화, 장갑의 상태를 추가하고
이를 셋팅하기 위한 setter 메서드가 필요하고, cost() 메서드에서는 각각의 장비를 가지고 있는지
유무를 확인하여, 방어도를 계산해야 합니다.

이를 상속받은 Warrior/Priest 클래스는 자신의 방어도를 계산 할 때
super.cost()를 호출 한 후 자신의 기본 방어도를 더하면 되겠지요..?

아래와 같이 구현 될 것입니다.

public abstract class Character {
 String desc;
 
 boolean helm = false;
 boolean guntlet = false;
 boolean shoes = false;
 
 public String getDesc() {
  return desc;
 }
 
 //이제 여기서 구현을 합니다.
 public int def() {
  int def = 0;
  if(hasHelm()) {
   def+=10;
  }
 
  if(hasGuntlet()) {
   def+=10;
  }
 
  if(hasShoes()) {
   def+=10;
  }
 
  return def;
 }
 
 public void setHelm() {
  desc += " 헬멧을 씁니다.";
  helm = true;
 }
 
 public void setGuntlet() {
  desc += " 장갑을 착용합니다.";
  guntlet = true;
 }
 
 public void setShoes() {
  desc += " 신발을 신습니다.";
  shoes = true;
 }
 
 public boolean hasHelm() {
  return helm;
 }
 
 public boolean hasGuntlet() {
  return guntlet;
 }
 
 public boolean hasShoes() {
  return shoes;
 }
}

그리고 이를 상속받은 Warrior와 Priest 클래스는 아래와 같이 변경됩니다.

public class Priest extends Character {

 public Priest() {
  desc = "연습생 로브를 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //성직자의 기본 방어도는 10입니다.
  int basic = 10;
  basic+=super.def();
  return basic;  
 }
}


public class Warrior extends Character {

 public Warrior() {
  desc = "가죽갑옷을 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //전사의 기본 방어도는 100입니다.
  int basic = 10;
  basic+=super.def();
  return basic;  
 }
}

그럼 테스트를 해보겠습니다.

public class Test {
 public static void main(String[] args) {
  Character warrior = new Warrior();
  warrior.setGuntlet();
  warrior.setHelm();
 
  System.out.println("전사 : "+warrior.getDesc() + " 방어도 : ["+warrior.def()+"]");
 
  Character priest = new Priest();
  System.out.println("성직자 : "+priest.getDesc() + " 방어도 : ["+priest.def()+"]");
 }
}


전사 : 가죽갑옷을 입고 있습니다. 장갑을 착용합니다. 헬멧을 씁니다.방어도 : [30]
성직자 : 연습생 로브를 입고 있습니다.방어도 : [10]

전사와 성직자를 생성하고 전사에게는 장갑과 헬멧을 그리고
성직자는 기본적인 상태로 만든다음에 방어도와 상태를 보았습니다.

여기까지만 보면 꽤 훌륭한 것 같습니다.
코드의 중복도 없고, 상속도 잘 활용한 것 같습니다.

하지만 여기서 새로운 장비가 추가되면 어떨까요?

혹은 게임이 패치되어서 각 장비의 방어도가 달라진다면?

이렇게되면 기존의 클래스를 열어서 고치는 일을 피할 수 없습니다.

게다가 고쳐야 하는 부분도 만만치 않죠.. 실제로 게임에서 등장하는 장비들은 한두가지가 아닐 것 입니다.

그래서 이것을 데코레이터 패턴으로 변경해 보겠습니다.

public abstract class Character {
 String desc;
 
 public String getDesc() {
  return desc;
 }
 
 public abstract int def();
}

Character 클래스가 가지고 있던 장비에 대한 정보를 모두 없애버렸습니다.
그리고 이를 상속하는 Priest와 Warrior 클래스를 만듭니다.

public class Priest extends Character {

 public Priest() {
  desc = "연습생 로브를 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //성직자의 기본방어도는 10입니다.
  return 10;  
 }
}

---------------------------------------------

public class Warrior extends Character {

 public Warrior() {
  desc = "가죽갑옷을 입고 있습니다.";
 }
 
 @Override
 public int def() {
  //전사의 기본 방어도는 100입니다.
  return 100;  
 }
}

각 클래스는 자기 자신의 방어도만을 리턴합니다. 장비에 대한 정보는 없죠.
그러면 이제 이 장비에 대한 클래스를 만들어보겠습니다.

클래스를 장비로 "장식한다(덮는다)"라는 개념으로 생각해보세요.

이 장비에 대한 클래스들은 자기가 장식해야 할 대상을 알고 있어야 합니다.
그것이 바로 Character 형이죠.
그리고, 장비 자신도 다른 장비에 의해서 장식이 될 수 있기 때문에
그 자신도 Character형이어야 합니다.


(물론 개념적으로 보면 말이 조금 안되는 얘기이긴 합니다. 장비가 장비를 장식한다? 라는 거..
그래도 그냥 Decorator의 개념을 설명하기 위한 방법으로 이해해주세요..^^)

핵심은 장식의 대상이 되는 녀석도, 그리고 장식을 하는 녀석도 모두 같은 클래스형이어야 한다는 것입니다.

일단, 장비를 나타내는 클래스를 만들어보겠습니다.

public abstract class CharacterDecorator extends Character {
 public abstract String getDesc();
}

Character형을 상속합니다.

이제 장비 클래스들을 만들어보겠습니다.

public class Guntlet extends CharacterDecorator {
 Character character;
 
 public Guntlet(Character c) {
  this.character = c;
 }
 @Override
 public String getDesc() {
  return character.getDesc() + " 장갑을 착용합니다.";
 }

 @Override
 public int def() {
  return 10 + character.def();
 }
}
-------------------------------------------
public class Helm extends CharacterDecorator {
 Character character;
 
 public Helm(Character c) {
  this.character = c;
 }
 @Override
 public String getDesc() {
  return character.getDesc() + " 투구를 씁니다.";
 }

 @Override
 public int def() {
  return 10 + character.def();
 }
}
---------------------------------------
public class Shoes extends CharacterDecorator {
 Character character;
 
 public Shoes(Character c) {
  this.character = c;
 }
 @Override
 public String getDesc() {
  return character.getDesc() + " 장화를 신습니다.";
 }

 @Override
 public int def() {
  return 10 + character.def();
 }
}

보시면 아시겠지만 이 클래스들은 자신의 일만 합니다.
일단 누가 자기를 장식하고 있던, 자기가 누구를 장식하고 있던 그 형이
Character 형이라면 상관하지 않습니다.

단지 자기가 감싸고 있는 클래스의 getDesc()와 def() 메서드를 호출하여 거기다가
자신의 행동을 더할뿐입니다.

사용하는 방식은 아래와 같습니다.

public class Test {
 public static void main(String[] args) {
  Character warrior = new Warrior();
  warrior = new Helm(warrior);
  warrior = new Guntlet(warrior);

 
  System.out.println("전사 : "+warrior.getDesc() + " 방어도 : ["+warrior.def()+"]");
 
  Character priest = new Priest();
  System.out.println("성직자 : "+priest.getDesc() + " 방어도 : ["+priest.def()+"]");
 }
}

마치 장식하듯이 클래스를 감싸나갑니다.
이것을 조금 다르게 표현하면,

Character warrior = new Warrior();
  warrior = new Guntlet( new Helm(warrior) );

이런식으로 나타낼 수 있습니다. 어디서 많이 본 형식 아닌가요?
자바에서 제공되는 InputStream, OutputStream 즉, java.io 클래스들이 데코레이터 패턴으로
구성되어 있습니다.

그리고, 검색 오픈소스인 루씬의 Analyzer도 1.4 버젼까지는 위와 같은 형식으로
구성되어 있었습니다.

역시 이 패턴의 가장 큰 장점 중 하나는 동적으로 클래스의 행동을 추가 할 수 있다는 것이라고 생각됩니다.









 

신고
Posted by 용식


티스토리 툴바