00197 20151215 자바 GoF 디자인패턴 수업 7일차 - AngryQA/blog GitHub Wiki
자바 GoF 디자인패턴 수업 7일차
AngryQA | 2015-12-15 화요일 오전 9:38 | IT/개발전반 | 원본
1. 상태패턴(State Pattern)
// Item의 상태에 따른 동작을 정의한 인터페이스
// Replace type code with State or Strategy
// 디자인 패턴에서 변하는 것을 클래스로 만들어서 인터페이스 기반으로 // 교체하는 패턴은 총 3가지 입니다. // "Strategy" : 알고리즘을 캡슐화해서 실행 시간에 교체 가능하게 하는 것 // "State" : 객체의 상태에 따른 동작을 정의한 클래스를 만들어서 // 교체하는 것. // "Builder" : 동일한 구축공정으로 객체를 만들지만 각 공정에 따른 // 표현이 달라지는 객체를 만들 때
[#M_// 방법 1. 아이템의 종류에 따른 조건 분기|접기|
// 상태 패턴(State Pattern) // : 객체의 상태가 전이됨에 따라 동작이 변경되는 것을 // 표현하는 방법
// 방법 1. 아이템의 종류에 따른 조건 분기 class Hero { private int state = 1;
// OCP를 만족할 수 없다. // : 아이템의 종류가 추가됨에 따라, 기존 코드는 // 수정되어야만 한다. public void run() { if (state == 1) System.out.println("Run"); else if (state == 2) System.out.println("Fast Run"); }
public void attack() { if (state == 1) System.out.println("Attack"); else if (state == 2) System.out.println("Fast Attack"); }
public void setState(int state) { this.state = state; } }
public class Example1 { public static void main(String[] args) { Hero hero = new Hero();
hero.setState(2);
hero.run();
hero.attack();
}
}
_M#]
상태패턴을 템플릿 메소드로(많이씀)
하지만 문제점이 상태를 못가지고 가네에
[#M_// 방법 2. 변하는 것을 자식 클래스가 메소드를 재정의함으로 변경하는 // 설계 기법 - Template Method Pattern|접기|
// 방법 2. 변하는 것을 자식 클래스가 메소드를 재정의함으로 변경하는 // 설계 기법 - Template Method Pattern class Hero { private int gold;
public void addGold(int gold) { this.gold += gold; }
public void run() { System.out.println("Run"); }
public void attack() { System.out.println("Attack"); } }
class FastHero extends Hero { @Override public void run() { System.out.println("Fast Run"); }
@Override public void attack() { System.out.println("Fast Attack"); } }
// 문제점 : 상속을 통한 기능의 확장은 객체의 상태의 변경이 아닌 // 객체의 변경이다.(정적인 기능의 추가) // 동작 뿐 아니라 상태도 변경된다. 영웅의 다른 상태도 공유 되어야 // 한다. public class Example2 { public static void main(String[] args) { Hero normalHero = new Hero(); Hero fastHero = new FastHero();
Hero hero = normalHero; // 초기화
hero.attack();
hero.run();
hero.addGold(100); // 100Gold 획득
// 아이템 획득
hero = fastHero;
hero.attack();
hero.run();
}
}
M#][#M// "Strategy" : 알고리즘을 캡슐화해서 실행 시간에 교체 가능하게 하는 것|접기|
// Item의 상태에 따른 동작을 정의한 인터페이스
// Replace type code with State or Strategy
// 디자인 패턴에서 변하는 것을 클래스로 만들어서 인터페이스 기반으로 // 교체하는 패턴은 총 3가지 입니다. // "Strategy" : 알고리즘을 캡슐화해서 실행 시간에 교체 가능하게 하는 것 // "State" : 객체의 상태에 따른 동작을 정의한 클래스를 만들어서 // 교체하는 것. // "Builder" : 동일한 구축공정으로 객체를 만들지만 각 공정에 따른 // 표현이 달라지는 객체를 만들 때
interface IState { void run();
void attack(); }
public class Example3 { public static void main(String[] args) { IState normalState = new NormalState(); IState fastState = new FastState();
Hero hero = new Hero();
hero.setState(normalState); // 초기화
hero.run();
hero.attack();
hero.addGold(100); // 100gold 획득
hero.setState(fastState); // 아이템 획득
hero.run();
hero.attack();
} }
class Hero { private IState state; private int gold;
public void addGold(int gold) { this.gold += gold; }
public void setState(IState state) { this.state = state; }
public void run() { state.run(); }
public void attack() { state.attack(); } }
// 다양한 Item의 따른 동작을 정의한다. // Item의 상테의 수 만큼 인터페이스를 구현해야 한다. class NormalState implements IState { @Override public void run() { System.out.println("Run"); }
@Override public void attack() { System.out.println("Attack"); } }
class FastState implements IState { @Override public void run() { System.out.println("Fast Run"); }
@Override public void attack() { System.out.println("Fast Attack"); } }
_M#]
enum으로 구현할수도 있다.. 자바만 이렇게 구현 가능
[#M_// Enum Class 를 이용한 상태 패턴 구현|접기|
// Enum Class 를 이용한 상태 패턴 구현 // enum State { NORMAL { @Override public void run() { System.out.println("Run"); }
@Override
public void attack() {
System.out.println("Attack");
}
}, FAST { @Override public void run() { System.out.println("Fast Run"); }
@Override
public void attack() {
System.out.println("Fast Attack");
}
};
public abstract void run(); public abstract void attack(); }
class Hero { private State state; public void setState(State state) { this.state = state; }
public void run() { state.run(); }
public void attack() { state.attack(); } }
public class Example4 { public static void main(String[] args) { Hero hero = new Hero(); hero.setState(State.NORMAL); hero.run();
hero.setState(State.FAST);
hero.run();
}
}
_M#]
Buider Pattern
동일한 구축 공정을 가지지만, 각 공정에 따른 다른 표현을 가지는 객체를 만들때 사용하는 패턴
[#M_더보기|접기|
// Builder Pattern // : 동일한 구축 공정을 가지지만, 각 공정에 따른 // 다른 표현을 가지는 객체를 만들때 사용하는 패턴
interface IBuilder { void makeHat(); void makeUniform(); void makeShoes();
String getResult(); }
// 축구 게임의 캐릭터를 만드는 공정은 변하지 않습니다. class Director { private IBuilder builder; public void setBuilder(IBuilder builder) { this.builder = builder; }
// 아래 메소드가 캐릭터를 만들어서 반환합니다. String construct() { // builder.makeHat(); builder.makeUniform(); builder.makeShoes();
return builder.getResult();
} }
// 다양한 나라의 빌더를 만들어서 제공합니다. class KoreanBuilder implements IBuilder { private String character = ""; @Override public void makeHat() { character += "삿갓\n"; }
@Override public void makeUniform() { character += "한복\n"; }
@Override public void makeShoes() { character += "고무신\n"; }
@Override public String getResult() { return character; } }
class AmericanBuilder implements IBuilder { private String character = "";
@Override public void makeHat() { character += "야구모자\n"; }
@Override public void makeUniform() { character += "양복\n"; }
@Override public void makeShoes() { character += "구두\n"; }
@Override public String getResult() { return character; } }
public class Example5 { public static void main(String[] args) { KoreanBuilder koreanBuilder = new KoreanBuilder(); AmericanBuilder americanBuilder = new AmericanBuilder();
Director director = new Director();
// 캐릭터 선택 화살표 키를 누르면
director.setBuilder(koreanBuilder);
System.out.println(director.construct());
director.setBuilder(americanBuilder);
System.out.println(director.construct());
} }
_M#]
2. 객체의 상태가 많을때 초기화하는 방법
[#M_// 방법 1. 점층적 생성자 패턴(Telescoping Constructor Pattern)|접기|
public class Example6 {
public static void main(String[] args) { // 설정하려는 인자의 개수에 맞쳐 생성자를 골라서 생성하면 된다. UserInfo userInfo = new UserInfo("Chansigi", "Chansik.yun", 10, 500, 1000);
// 문제점
// 1\. 인자 수가 늘어나면, 클라이언트 코드 작성이 힘들다.
// 2\. 인자 순서가 변경되어도 컴파일 타임에 그 문제를 찾기가 어렵다.
// 3\. 가독성이 나쁘다.
} }
// 객체의 상태가 많을 때 초기화하는 방법. // 방법 1. 점층적 생성자 패턴(Telescoping Constructor Pattern) class UserInfo { private final String id; // 필수 인자 private final String name; private int level; private int gold; private int cash;
public UserInfo(String id, String name, int level, int gold, int cash) { this.id = id; this.name = name; this.level = level; this.gold = gold; this.cash = cash; }
public UserInfo(String id, String name, int level, int gold) { this(id, name, level, gold, 0); }
public UserInfo(String id, String name, int level) { this(id, name, level, 0, 0); }
public UserInfo(String id, String name) { this(id, name, 0, 0, 0); } }
M#][#M/ 방법 2. Beans 패턴 문제많음..|접기|
public class Example7 {
public static void main(String[] args) { // 문제점 // 1. 객체 생성 과정이 한번에 끝나지 않는다. // => 객체 일관성이 없다. // 2. 스레드 안전성에 추가적인 고려가 필요하다. // 3. 변경 불가능한 객체도 만들 수 없다. // 4. 필수 인자와 선택 인자에 대한 구분도 불가능하다. UserInfo userInfo = new UserInfo(); userInfo.setId("Chansigi"); userInfo.setName("Chansik.Yun"); userInfo.setLevel(1); userInfo.setGold(1000); userInfo.setCash(500); } }
// 방법 2. Beans 패턴 // 기본 생성자를 통해서 객체를 생성한후, setter를 이용하여 // 객체의 필수 필드 뿐 아니라 선택적 필드를 초기화 하는 방법. class UserInfo { private String id; // 필수 인자 private String name; private int level; private int gold; private int cash;
public UserInfo() { }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setLevel(int level) { this.level = level; }
public void setGold(int gold) { this.gold = gold; }
public void setCash(int cash) { this.cash = cash; } }
M#][#M// 방법 3. 빌더 패턴 // = 점층적 생성자 패턴 안전성 + 빈즈 패턴 가독성|접기|
// 장점
// 1. 가독성이 높고, 객체 일관성이 깨지지 않는다. // 2. 필수 인자에 대한 부분을 쉽게 지원 가능하다. // 3. 불변 객체를 만들기도 편하다. // 4. 매개 변수가 추가되어도 유연하게 추가 가능하다.
// 단점 // 1. 추가적인 메모리가 필요하다. // 2. 객체를 생성하기 위해서는 먼저 빌더를 생성해야 한다. // => 4개 이상 인자에서 고려되어야 한다. public class Example8 { public static void main(String[] args) { UserInfo ui = new UserInfo.Builder("Chansigi", "Chansik.yun") .level(1) .gold(1000) .cash(500).build();
String str = new StringBuilder()
.append("Hello")
.append(true)
.append("world").toString();
} }
// 방법 3. 빌더 패턴 // = 점층적 생성자 패턴 안전성 + 빈즈 패턴 가독성 class UserInfo { private final String id; // 필수 인자 private final String name; private final int level; private final int gold; private final int cash;
public UserInfo(Builder builder) { this.id = builder.id; this.name = builder.name; this.level = builder.level; this.gold = builder.gold; this.cash = builder.cash; }
public static class Builder { private final String id; // 필수 인자 private final String name; private int level; private int gold; private int cash;
public Builder(String id, String name) {
this.id = id;
this.name = name;
}
public Builder level(int level) {
this.level = level;
return this;
}
public Builder gold(int gold) {
this.gold = gold;
return this;
}
public Builder cash(int cash) {
this.cash = cash;
return this;
}
public UserInfo build() {
return new UserInfo(this);
}
} }
_M#]
3. Composite (컴포지 패턴)
메뉴바등 제작..
/ Composite 패턴 핵심 2가지 // 1. 복합 객체(PopupMenu)는 개별 객체(MenuItem)와 복합 객체를 모두 // 보관한다. => 공통의 부모(BaseMenu) // 2. 복합 객체와 개별 객체는 동일시 된다. // -> 사용법이 같다(모두 command()를 사용한다)
[#M_더보기|접기|
import java.util.ArrayList; import java.util.List; import java.util.Scanner;
public class Example9 { public static void main(String[] args) { PopupMenu menuBar = new PopupMenu("메뉴바");
PopupMenu p1 = new PopupMenu("화면 설정");
PopupMenu p2 = new PopupMenu("소리 설정");
menuBar.addMenu(p1);
// menuBar.addMenu(p2);
p1.addMenu(p2);
p1.addMenu(new MenuItem("해상도 변경"));
p1.addMenu(new MenuItem("색상 변경"));
p1.addMenu(new MenuItem("명암 변경"));
p2.addMenu(new MenuItem("볼륨 조절"));
p2.addMenu(new MenuItem("음향 조절"));
// 시작하려면?
menuBar.command();
} }
// Composite 패턴 핵심 2가지 // 1. 복합 객체(PopupMenu)는 개별 객체(MenuItem)와 복합 객체를 모두 // 보관한다. => 공통의 부모(BaseMenu) // 2. 복합 객체와 개별 객체는 동일시 된다. // -> 사용법이 같다(모두 command()를 사용한다)
// 메뉴 // MenuItem과 PopupMenu는 공통의 부모가 필요하다. abstract class BaseMenu { private String title;
public BaseMenu(String title) { this.title = title; }
public String getTitle() { return title; }
// 모든 메뉴는 선택되면 command()가 호출된다. // 부모 입장에서는 구현해 줄 필요가 없고, // 자식이 반드시 제공해야 한다면 추상 메소드로 설계해야 한다. public abstract void command(); }
class MenuItem extends BaseMenu { public MenuItem(String title) { super(title); }
@Override public void command() { System.out.println(getTitle() + " 선택되었음."); } }
// 재귀적 합성을 사용한 설계 방법입니다. class PopupMenu extends BaseMenu { private List menus = new ArrayList();
public PopupMenu(String title) { super(title); }
public void addMenu(BaseMenu menu) { menus.add(menu); }
@Override public void command() { Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println();
int sz = menus.size();
for (int i = 0; i < sz; ++i) {
System.out.printf("%2d. %s\n",
i + 1,
menus.get(i).getTitle());
}
System.out.printf("%2d. 상위 메뉴로\n", sz + 1);
System.out.print("메뉴를 선택하세요 >> ");
int cmd = scanner.nextInt();
if (cmd < 1 || cmd > sz + 1)
continue;
if (cmd == sz + 1)
break;
menus.get(cmd - 1).command();
}
} }
_M#][#M_추가예제|접기|
import java.util.ArrayList;
import java.util.List;
// http://d.pr/n/1i250 // 복합 객체 : Folder // 단일 객체 : File // 부모 클래스 : Item // 동일시 인터페이스 : getSize() public class Example10 { public static void main(String[] args) { Folder fo1 = new Folder("Root"); Folder fo2 = new Folder("A"); Folder fo3 = new Folder("B");
File f1 = new File("a.txt", 10);
File f2 = new File("b.txt", 20);
File f3 = new File("c.txt", 30);
fo1.addItem(fo2);
fo2.addItem(fo3);
fo1.addItem(f1);
fo2.addItem(f2);
fo3.addItem(f3);
System.out.println(f1.getSize()); // 10
System.out.println(fo2.getSize()); // 50
System.out.println(fo1.getSize()); // 60
} }
abstract class Item { private String name;
public Item(String name) { this.name = name; }
public abstract int getSize(); }
class File extends Item { private int size;
public File(String name, int size) { super(name); this.size = size; }
@Override public int getSize() { return size; } }
// 재귀적 합성을 통한 복합 객체의 구성 - Composite Pattern class Folder extends Item { private List items = new ArrayList();
public Folder(String name) { super(name); }
public void addItem(Item item) { items.add(item); }
@Override public int getSize() { int total = 0; for (Item e : items) { total += e.getSize(); } return total; } }
_M#]
4. 자바에서 이벤트를 처리하는 방법
-> 인터페이스 기반의 리스너방식
핸들러방식은 객체마다 생성해야므로.... 위반됨..
-> 전략패턴
[#M_전략패턴|접기|
class Dialog implements IMenuListener { public void close() { System.out.println("close"); } public void open() { System.out.println("open"); }
// 문제점 // 모든 객체가 결국 인터페이스 메소드를 호출하므로, // 어떤 객체에서 이벤트가 발생했는지 구분하는 코드를 작성해야 한다. // "OCP를 만족할 수 없다." // => 핸들러 방식의 이벤트 처리 방식 @Override public void onCommand(int id) { if (id == 11) close(); else if (id == 12) open(); } }
public class Example11 { public static void main(String[] args) { MenuItem closeMenu = new MenuItem("닫기", 11); MenuItem openMenu = new MenuItem("열기", 12);
Dialog dialog = new Dialog();
openMenu.setListener(dialog);
closeMenu.setListener(dialog);
openMenu.command();
closeMenu.command();
} }
// 자바에서 이벤트를 처리하는 방법. // => 인터페이스 기반 리스너 방식
interface IMenuListener { void onCommand(int id); }
class MenuItem { private String title; private int id;
private IMenuListener listener;
public void setListener(IMenuListener listener) { this.listener = listener; }
public MenuItem(String title, int id) { this.title = title; this.id = id; }
public void command() { // 메뉴가 선택된 사실을 다시 외부로 알려야 합니다. // "객체가 외부로 이벤트를 발생시킨다." 라고 표현합니다. listener.onCommand(id); }
}
_M#]
-> 관찰자 패턴
이벤트가 걸린 이벤트가 생겼다고 모든객체에게 전달
[#M_더보기|접기|
import java.util.ArrayList; import java.util.List;
class Dialog implements IMenuListener { public void close() { System.out.println("close"); } public void open() { System.out.println("open"); }
// 문제점 // 모든 객체가 결국 인터페이스 메소드를 호출하므로, // 어떤 객체에서 이벤트가 발생했는지 구분하는 코드를 작성해야 한다. // "OCP를 만족할 수 없다." // => 핸들러 방식의 이벤트 처리 방식 // http://d.pr/n/17tva @Override public void onCommand(int id) { if (id == 11) close(); else if (id == 12) open(); } }
public class Example11 { public static void main(String[] args) { MenuItem closeMenu = new MenuItem("닫기", 11); MenuItem openMenu = new MenuItem("열기", 12);
Dialog dialog = new Dialog();
openMenu.addListener(dialog);
closeMenu.addListener(dialog);
openMenu.command();
closeMenu.command();
} }
// 자바에서 이벤트를 처리하는 방법. // => 인터페이스 기반 리스너 방식
interface IMenuListener { void onCommand(int id); }
class MenuItem { private String title; private int id;
// 일종의 전략 패턴
private List listeners = new ArrayList(); public void addListener(IMenuListener listener) { listeners.add(listener); }
/* private IMenuListener listener;
public void setListener(IMenuListener listener) { this.listener = listener; } */
public MenuItem(String title, int id) { this.title = title; this.id = id; }
public void command() { // 메뉴가 선택된 사실을 다시 외부로 알려야 합니다. // "객체가 외부로 이벤트를 발생시킨다." 라고 표현합니다. // listener.onCommand(id);
// 관찰자 패턴 : Observer Pattern
// 의도 : 등록된 모든 객체에게 이벤트를 발생
for (IMenuListener listener : listeners)
listener.onCommand(id);
}
}
_M#][#M_최신자바의 이벤트처리방법(JAVA8)|접기|
// 최신 자바의 이벤트 처리 방법 class Dialog { private void open() { System.out.println("open"); }
private void close() { System.out.println("close"); }
public Dialog() { // 메뉴 구성 MenuItem openMenu = new MenuItem("Open", 11); openMenu.setListener(new IMenuListener() { @Override public void onCommand(int id) { open(); } });
MenuItem closeMenu = new MenuItem("Close", 12);
closeMenu.setListener(id -> close());
} }
public class Example12 { }
@FunctionalInterface interface IMenuListener { void onCommand(int id); }
class MenuItem { private String title; private int id;
private IMenuListener listener;
public void setListener(IMenuListener listener) { this.listener = listener; }
public MenuItem(String title, int id) { this.title = title; this.id = id; }
public void command() { listener.onCommand(id); }
}
_M#]
5. 관찰자 패턴에 관해
MVC 모델은 옵저버와 어댑터 패턴으로 되어있다.
[#M_더보기|접기|
import java.util.ArrayList; import java.util.List; import java.util.Scanner;
public class Example13 { public static void main(String[] args) { Table table = new Table(); table.addObserver(new BarGraph()); table.addObserver(new PieGraph());
table.edit();
} }
interface IObserver { void update(Object data); }
// 관찰의 대상(Subject, Model, Document) class Table { private int[] data = { 0, 0, 0, 0, 0 };
private List observers = new ArrayList();
public void addObserver(IObserver observer) { observers.add(observer); }
public void notify(Object data) { for (IObserver observer : observers) { observer.update(data); } }
public void edit() { Scanner scanner = new Scanner(System.in); while (true) { System.out.print("index >> "); int index = scanner.nextInt();
if (index < 0 || index >= data.length)
break;
System.out.print("data >> ");
data[index] = scanner.nextInt();
// 데이터 변경된 사실을 등록된 관찰자에게 알려야 한다.
notify(data);
}
} }
// 이제 다양한 그래프를 제공하면 됩니다. class PieGraph implements IObserver { @Override public void update(Object data) { System.out.println("***** Pie Graph *****");
int[] value = (int[])data;
for (int i = 0 ; i < 5 ; i++) {
System.out.printf("%2d : %2d\n", i, value[i]);
}
} }
class BarGraph implements IObserver { @Override public void update(Object data) { System.out.println("***** Bar Graph *****");
int[] value = (int[])data;
for (int i = 0 ; i < 5 ; i++) {
System.out.printf("%2d : %2d\n", i, value[i]);
}
} }
_M#][#M_더보기|접기|
import java.util.ArrayList;
import java.util.List; import java.util.Scanner;
// http://d.pr/n/15Mv2 public class Example13 { public static void main(String[] args) { Table table = new Table(); table.addObserver(new BarGraph()); table.addObserver(new PieGraph());
table.edit();
} }
interface IObserver { void update(E data); }
// 관찰의 대상(Subject, Model, Document) // 모든 테이블의 공통의 특징(관찰자 패턴의 기본 로직)은 항상 동일하다. // 달라지는 것은 메소드 재정의로 변경한다면, // 공통적인 것은 부모 클래스로 만들어서 제공 class Subject { private List observers = new ArrayList();
public void addObserver(IObserver observer) { observers.add(observer); }
public void notify(int[] data) { for (IObserver observer : observers) { observer.update(data); } } }
class Table extends Subject { private int[] data = { 0, 0, 0, 0, 0 };
public void edit() { Scanner scanner = new Scanner(System.in); while (true) { System.out.print("index >> "); int index = scanner.nextInt();
if (index < 0 || index >= data.length)
break;
System.out.print("data >> ");
data[index] = scanner.nextInt();
// 데이터 변경된 사실을 등록된 관찰자에게 알려야 한다.
notify(data);
}
} }
// 이제 다양한 그래프를 제공하면 됩니다. class PieGraph implements IObserver { @Override public void update(int[] data) { System.out.println("***** Pie Graph *****");
// int[] value = (int[])data;
for (int i = 0 ; i < 5 ; i++) {
System.out.printf("%2d : %2d\n", i, data[i]);
}
} }
class BarGraph implements IObserver { @Override public void update(Object data) { System.out.println("***** Bar Graph *****");
int[] value = (int[])data;
for (int i = 0 ; i < 5 ; i++) {
System.out.printf("%2d : %2d\n", i, value[i]);
}
} }
_M#]