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_더보기|접기|
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#]
정리하는말, 패턴의 구현이 아닌 패턴의 목적으로 패턴을 구분하다.