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#]

20151215.zip

Attachments(1)