00198 20151216 자바 GoF 디자인패턴 수업 8일차 - AngryQA/blog GitHub Wiki

자바 GoF 디자인패턴 수업 8일차

AngryQA | 2015-12-16 수요일 오전 9:46 | IT/개발전반 | 원본

1. 중재자 패턴

: 객체간의 관계가 복잡할 때 중간의 중재하는 객체를 만들어서 상호관계를 캡슐화

통보센터 ??

NotificationCenter : iOS, OSX 에서 이벤트를 처리하는 방법

[#M_핸들러 기반의 이벤트 처리방식|접기|

// 중재자 패턴 // : 객체간의 관계가 복잡할 때 중간의 중재하는 객체를 만들어서 // 상호 관계를 캡슐화(도메인 특화적인 관계) import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;

// NotificationCenter // : iOS, OSX 에서 이벤트를 처리하는 방법

@FunctionalInterface interface IObserver { void onNotify(); }

class NotificationCenter { private Map notifiMap = new HashMap();

public void addObserver(String name, IObserver observer) { if (!notifiMap.containsKey(name)) { notifiMap.put(name, new ArrayList()); }

 notifiMap.get(name).add(observer);

}

public void postNotificationWithName(String name) { List observers = notifiMap.get(name); if (observers != null) { for (IObserver e : observers) e.onNotify(); } }

private static final NotificationCenter INSTANCE = new NotificationCenter();

public static NotificationCenter defaultCenter() { return INSTANCE; } }

class Dialog { public void warning() { System.out.println("Dialog warning - Low Battery"); } }

// 핸들러 기반의 이벤트 처리 방식(C++, C#, ObjC) // -> 내부적으로 인터페이스 기반으로 동작하는 것이다. // http://d.pr/n/17LKI public class Example1 { public static void main(String[] args) { NotificationCenter nc = NotificationCenter.defaultCenter();

 nc.addObserver("LOWBATTERY",
     () -> System.out.println("Low Battery Warning"));

 Dialog dialog = new Dialog();
 // nc.addObserver("LOWBATTERY", () -> dialog.warning());
 nc.addObserver("LOWBATTERY", dialog::warning);

 // 배터리를 체크하는 모듈에서
 nc.postNotificationWithName("LOWBATTERY");

} }

_M#]

2. Command pattern : 명령을 객체로 캡슐화

[#M_더보기|접기|

// undo, redo // Command Pattern : 명령을 객체로 캡슐화 import java.util.ArrayList; import java.util.List; import java.util.Scanner;

interface ICommand { void execute();

default void undo() {} default boolean canUndo() { return false; } }

class AddRectCommand implements ICommand { private List shapes; public AddRectCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { shapes.add(new Rect()); } }

class AddCircleCommand implements ICommand { private List shapes; public AddCircleCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { shapes.add(new Circle()); } }

class DrawCommand implements ICommand { private List shapes; public DrawCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { for (Shape e : shapes) { e.draw(); } } }

public class Example2 { public static void main(String[] args) { List shapes = new ArrayList();

 Scanner scanner = new Scanner(System.in);
 while (true) {
   ICommand command = null;

   int cmd = scanner.nextInt();
   if      (cmd == 1) command = new AddRectCommand(shapes);
   else if (cmd == 2) command = new AddCircleCommand(shapes);
   else if (cmd == 9) command = new DrawCommand(shapes);

   if (command != null)
     command.execute();
 }

} }

abstract class Shape { public abstract void draw(); }

class Rect extends Shape { public void draw() { System.out.println("Rect draw"); } }

class Circle extends Shape { public void draw() { System.out.println("Circle draw"); } 

}

_M#]

undo, redo 구현

redo는 구현해봅시다...

[#M_더보기|접기|

// undo, redo // Command Pattern : 명령을 객체로 캡슐화 // http://d.pr/n/RH1L import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.Stack;

interface ICommand { void execute();

default void undo() {} default boolean canUndo() { return false; } }

class AddRectCommand implements ICommand { private List shapes; public AddRectCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { shapes.add(new Rect()); }

@Override public void undo() { shapes.remove(shapes.size() - 1); }

@Override public boolean canUndo() { return true; } }

class AddCircleCommand implements ICommand { private List shapes; public AddCircleCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { shapes.add(new Circle()); }

@Override public void undo() { shapes.remove(shapes.size() - 1); }

@Override public boolean canUndo() { return true; } }

class DrawCommand implements ICommand { private List shapes; public DrawCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { for (Shape e : shapes) { e.draw(); } } }

public class Example2 { public static void main(String[] args) { List shapes = new ArrayList(); Stack undo = new Stack();

 Scanner scanner = new Scanner(System.in);
 while (true) {
   ICommand command = null;

   int cmd = scanner.nextInt();
   if      (cmd == 1) command = new AddRectCommand(shapes);
   else if (cmd == 2) command = new AddCircleCommand(shapes);
   else if (cmd == 9) command = new DrawCommand(shapes);
   else if (cmd == 0) {
     command = undo.pop();
     command.undo();
     continue;
   }

   if (command != null) {
     command.execute();
     if (command.canUndo())
       undo.push(command);
   }
 }

} }

abstract class Shape { public abstract void draw(); }

class Rect extends Shape { public void draw() { System.out.println("Rect draw"); } }

class Circle extends Shape { public void draw() { System.out.println("Circle draw"); } }

_M#]

2. 종합적으로 적용해본 도형그르기

// - 알기 쉬운 디자인 패턴(C++) // 공통성과 가변성의 분리의 원칙을 사용하는 GoF의 디자인 패턴 5가지 // 1) 변하는 것을 메소드로 분리 // - Template Method : 알고리즘의 변경 // - Factory Method : 객체 생성

// 2) 변하는 것을 클래스로 분리(교체 가능해야 하므로 인터페이스 기반) // - Strategy : 정책 // - State : 상테에 따른 동작 // - Builder : 공정에 따른 표현

// 간접층의 원리 // : 소프트웨어 난제는 간접층을 도입함으로써 그 문제를 해결할 수 있다. // => 객체로 캡슐화

책: JAVA 언어로 배우는 리팩토링, 패턴을 활용한 리팩토링, 마틴 파울러 리팩토링

[#M_더보기|접기|

// - 알기 쉬운 디자인 패턴(C++) // 공통성과 가변성의 분리의 원칙을 사용하는 GoF의 디자인 패턴 5가지 // 1) 변하는 것을 메소드로 분리 // - Template Method : 알고리즘의 변경 // - Factory Method : 객체 생성

// 2) 변하는 것을 클래스로 분리(교체 가능해야 하므로 인터페이스 기반) // - Strategy : 정책 // - State : 상테에 따른 동작 // - Builder : 공정에 따른 표현

// 간접층의 원리 // : 소프트웨어 난제는 간접층을 도입함으로써 그 문제를 해결할 수 있다. // => 객체로 캡슐화

// Command Pattern : 명령을 객체로 캡슐화 // http://d.pr/n/9AcS import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.Stack;

interface ICommand { void execute();

default void undo() {} default boolean canUndo() { return false; } }

// 공통된 로직은 부모 클래스로 묶어 주는 것이 좋다. abstract class AddCommand implements ICommand { private List shapes;

public AddCommand(List shapes) { this.shapes = shapes; }

@Override public void undo() { shapes.remove(shapes.size() - 1); }

@Override public boolean canUndo() { return true; }

@Override public void execute() { shapes.add(createShape()); }

// Factory Method Pattern // : Template Method Pattern의 모양인데, // 자식이 재정의하는 메소드가 하는 일이 정책의 변경이 아닌 // 객체 생성이라면 protected abstract Shape createShape(); }

class AddRectCommand extends AddCommand { public AddRectCommand(List shapes) { super(shapes); }

@Override protected Shape createShape() { return new Rect(); } }

class AddCircleCommand extends AddCommand { public AddCircleCommand(List shapes) { super(shapes); }

@Override protected Shape createShape() { return new Circle(); } }

class DrawCommand implements ICommand { private List shapes; public DrawCommand(List shapes) { this.shapes = shapes; }

@Override public void execute() { for (Shape e : shapes) { e.draw(); } } }

// 재귀적 합성을 통한 복합 객체의 구성 - Composite class MacroCommand implements ICommand { private List commands = new ArrayList();

public void addCommand(ICommand command) { commands.add(command); }

@Override public void execute() { for (ICommand command : commands) command.execute(); } }

public class Example2 { public static void main(String[] args) { List shapes = new ArrayList(); Stack undo = new Stack(); Stack redo = new Stack();

 MacroCommand macroCommand = new MacroCommand();
 macroCommand.addCommand(new AddRectCommand(shapes));
 macroCommand.addCommand(new AddCircleCommand(shapes));
 macroCommand.addCommand(new DrawCommand(shapes));
 macroCommand.execute();

 Scanner scanner = new Scanner(System.in);
 while (true) {
   ICommand command = null;

   int cmd = scanner.nextInt();
   if      (cmd == 1) command = new AddRectCommand(shapes);
   else if (cmd == 2) command = new AddCircleCommand(shapes);
   else if (cmd == 8) command = redo.pop();
   else if (cmd == 9) command = new DrawCommand(shapes);
   else if (cmd == 0) {
     command = undo.pop();
     command.undo();

     redo.push(command);
     continue;
   }

   if (command != null) {
     command.execute();
     if (command.canUndo())
       undo.push(command);
   }
 }

} }

abstract class Shape { public abstract void draw(); }

class Rect extends Shape { public void draw() { System.out.println("Rect draw"); } }

class Circle extends Shape { public void draw() { System.out.println("Circle draw"); } 

}

_M#]

3. 컬렉션에 관해

ㅁ 컬렉션 단점에 대해(오브젝트타입)

[#M_더보기|접기|

class Point {} class Dialog {}

// 단점 // 1. 모든 객체 타입을 하나의 컬렉션에 저장할 수 있다. // 2. 타입 안전성이 떨어진다. // 실수로 다른 타입을 넣어도 컴파일 시간에 에러가 발생하지 않는다. // 3. 값을 꺼낼 때 해당 타입으로 항상 캐스팅 해야 한다. public class Example4 { public static void main(String[] args) { SList s = new SList(); s.addFront(new Point()); // s.addFront(new Dialog()); // 잘못 들어갔다. s.addFront(new Point());

 Point dialog = (Point) s.front();

 System.out.println(s.front());

} }

// Collection을 설계하는 방법 // 방법 1. Object 기반 컨테이너

class SList { private Node head; public SList() { head = null; }

public void addFront(Object data) { head = new Node(data, head); }

public Object front() { return head.data; }

static class Node { Object data; Node next;

 Node(Object data, Node next) {
   this.data = data;
   this.next = next;
 }

}

_M#]

4. 제네릭기반의 컨테이너

타입안전성이 뛰어나 잘못된 타입이 들어가면 컴파일 에러가발생하고 꺼낼때 캐스팅도 필요 없다.

[#M_더보기|접기|

class Dialog {}

public class Example5 { public static void main(String[] args) { SList s = new SList(); s.addFront(new Dialog());

 Dialog dialog = s.front();

 System.out.println(s.front());

} }

// 방법 2. Generic 기반 컬렉션 // 장점 : 타입 안전성이 뛰어나기 때문에, 잘못된 타입이 들어가면 // 컴파일 에러가 발생하고, 꺼낼 때 캐스팅도 필요 없다.

// 자바 Generic : 코드를 생성하는 기술이 아니므로, 코드 메모리 // 부하도 없고, 성능 향상도 없다. (Obj-C) class SList { private Node head; public SList() { head = null; }

public void addFront(E data) { head = new Node(data, head); }

public E front() { return head.data; }

static class Node { E data; Node next;

 Node(E data, Node next) {
   this.data = data;
   this.next = next;
 }

}

_M#]

5. 컬렉션 관한 패턴

반복자 패턴

[#M_더보기|접기|

// http://d.pr/n/1dzEe

import java.util.Iterator;

// 반복자(Iterator) 패턴 // 의도 : 컬렉션 또는 복합 객체의 내부 구조에 상관없이 // 요소를 열거하는 패턴 public class Example6 { public static void main(String[] args) { SList s = new SList(); s.addFront(10); s.addFront(20); s.addFront(30);

 System.out.println(s.front());

 Iterator iterator = s.iterator();
 while (iterator.hasNext()) {
   Integer value = iterator.next();
   System.out.println(value);
 }

 // 아래의 코드는 우리가 설계하는 컬렉션과 반복자가
 // 자바가 제공하는 인터페이스 규격에 부합되면 사용할 수 있습니다.
 for (Integer e : s)
   System.out.println(e);

} }

// 모든 반복자의 인터페이스를 먼저 설계해야 합니다. // C++ : Generic Interface - 연산자 오버로딩 // Java : Iterator, Iterable // C# : IEnumerator, IEnumerable

/* interface Iterator { boolean hasNext(); E next(); }

interface Iterable { Iterator iterator(); } */

class SList implements Iterable { // 모든 컬렉션은 자신의 반복자를 리턴해야 한다. @Override public Iterator iterator() { return new SListIterator(head); }

static class SListIterator implements Iterator { private Node current;

 public SListIterator(Node current) {
   this.current = current;
 }

 @Override
 public boolean hasNext() {
   return current != null;
 }

 @Override
 public E next() {
   E value = current.data;
   current = current.next;
   return value;
 }

}

private Node head; public SList() { head = null; }

public void addFront(E data) { head = new Node(data, head); }

public E front() { return head.data; }

static class Node { E data; Node next;

 Node(E data, Node next) {
   this.data = data;
   this.next = next;
 }

}

_M#]

방문자 패턴

[#M_더보기|접기|

// 방문자(Visitor) 패턴 // : 컬렉션 또는 복합 객체의 내부 구조와 상관없이 // 요소에 연산을 수행하는 패턴

public class Example7 { public static void main(String[] args) { SList s = new SList(); s.addFront(10); s.addFront(20); s.addFront(30);

 // s 안에 있는 모든 요소를 2배로 출력하고 싶다.
 // 1\. 루프를 수행하면서 모든 요소를 2배로 한 후 출력하면 된다.
 // 2\. 특정 작업을 자주하게 된다면 메소드로 추가해주는 것이 좋다.
 // 3\. SList 뿐 아니라 모든 컬렉션에서 해당 작업을 자주하게 된다
 //    모든 컬렉션에 해당 메소드가 추가되어야 한다.
 //  => 방문자 패턴을 사용하면 좋다.
 System.out.println(s.front());

 s.accept(new TwiceVisitor());

} }

// 모든 방문자 객체의 인터페이스 interface Visitor { void visit(E value); }

// 이제 다양한 방문자를 제공하면 됩니다. class TwiceVisitor implements Visitor { @Override public void visit(Integer value) { System.out.println(value * 2); } }

// 방문의 대상 객체의 인터페이스 interface Accpetor { void accept(Visitor visitor); }

class SList implements Accpetor {

// 아래 메소드만 정확하게 이해하면 됩니다. @Override public void accept(Visitor visitor) { Node current = head; while (current != null) { visitor.visit(current.data); current = current.next; } }

private Node head;

public SList() { head = null; }

public void addFront(E data) { head = new Node(data, head); }

public E front() { return head.data; }

static class Node { E data; Node next;

 Node(E data, Node next) {
   this.data = data;
   this.next = next;
 }

}

_M#]

방문자패턴을구현해보자

하지만 캡슐화전략을 위배할수있다.

[#M_더보기|접기|

import java.util.ArrayList; import java.util.List; import java.util.Scanner;

interface MenuVisitor { void visit(PopupMenu menu); void visit(MenuItem menu); }

interface MenuAcceptor { void accept(MenuVisitor visitor); }

abstract class BaseMenu implements MenuAcceptor { private String title;

public BaseMenu(String s) { title = s; }

public String getTitle() { return title; }

// 방문자 패턴의 문제점 : 초기에 세운 [캡슐화 전략을 위배] 할 수 있다. public void setTitle(String title) { this.title = title; }

public abstract void command(); }

class PopupMenu extends BaseMenu { List items = new ArrayList();

public PopupMenu(String s) { super(s); }

public void addMenu(BaseMenu menu) { items.add(menu); }

@Override public void command() { Scanner sc = new Scanner(System.in);

 while (true) {
   System.out.println();

   int sz = items.size();
   for (int i = 0; i < sz; i++) {
     System.out.println((i + 1) + ".  "  + items.get(i).getTitle());
   }

   System.out.println((sz + 1) + ". 상위 메뉴로...");
   System.out.print("메뉴를 선택하세요  >>  ");

   int cmd = sc.nextInt();

   if (cmd < 1 || cmd  > sz + 1)
     continue;

   if (cmd == sz + 1)
     break;

   items.get(cmd - 1).command();
 }

}

@Override public void accept(MenuVisitor visitor) { // 1. 자신을 전달한다. visitor.visit(this);

 // 2\. 복합 객체가 포함하고 있는 모든 메뉴들에 대해 방문자를 전달한다.
 for (BaseMenu menu : items) {
   menu.accept(visitor);
 }

} }

class MenuItem extends BaseMenu { @Override public void accept(MenuVisitor visitor) { visitor.visit(this); }

public MenuItem(String s) { super(s); }

@Override public void command() { System.out.println(getTitle() + " command excution!!"); } }

// 이제는 메뉴 시스템에 다양한 기능을 추가하는 방문자를 제공하면 됩니다. // -> 메뉴 관련 클래스에 메소드를 추가하지 않아도 기능을 추가할 수 있습니다. class TitleDecoratorVisitor implements MenuVisitor { @Override public void visit(PopupMenu menu) { String s = menu.getTitle(); s += " >>> "; menu.setTitle(s); }

@Override public void visit(MenuItem menu) { } }

public class Example8 { 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(new MenuItem("해상도 변경"));
 p1.addMenu(new MenuItem("색상 변경"));
 p1.addMenu(new MenuItem("명암 변경"));

 p2.addMenu(new MenuItem("볼륨 조절"));
 p2.addMenu(new MenuItem("음향 조절"));

 menubar.accept(new TitleDecoratorVisitor());
 menubar.command();

} }

_M#]

6. 메멘토 패턴

클래스의 [캡슐화 전략을 위배하지않고], 객체의 상태를 저장하거나 복원하는 기술을 제공하는 방법.

[#M_더보기|접기|

// 메멘토(Memento) 패턴 // : 클래스의 [캡슐화 전략을 위배하지 않고], 객체의 상태를 저장하거나 // 복원하는 기술을 제공하는 방법.

import java.util.ArrayList; import java.util.List;

public class Example10{ public static void main(String[] args) { Machine machine = new Machine(); // 0, 0

 int token = machine.createMemento();
 machine.foo();                    // 1, 1
 // 이전 상태로 복구하고 싶다.

 machine.restore(token);

} }

class Machine { private int stateA; private int stateB;

private class Memento { int stateA; int stateB; } private List backup = new ArrayList();

public int createMemento() { Memento m = new Memento(); m.stateA = stateA; m.stateB = stateB; backup.add(m);

 return backup.size() - 1;

}

public void restore(int token) { Memento m = backup.get(token); this.stateA = m.stateA; this.stateB = m.stateB; }

public void foo() { stateA += 1; stateB += 1; } 

}

_M#]


정리하는말, 패턴의 구현이 아닌 패턴의 목적으로 패턴을 구분하다.

20151216.zip

자바디자인패턴카탈로그.pdf

Attachments(2)