Chap 7 Design Patterns (1)
Design Pattern 다양한 수준의 설계패턴 존재 – 프로그래밍 수준 추상화된 상위계층 수준 – 수 천 가지 이상의 패턴들이 발표되었으며, 문서와 국 제회의에서 토의되고 있음 – 일반 개발자는 새로운 설계패턴을 작성하기 보다는 이미 정리되어 있는 설계패턴을 활용하는 것이 바람 직함 –GoF (Gang of Four) 에서 23 개의 설계패턴을 3 가지 유형으로 분류
3 가지 유형의 패턴 Creational Pattern – 객체를 생성하는데 관련된 패턴들 – 객체가 생성되는 과정에 유연성을 높이고, 코드의 유지가 쉬워진 다. Structural Pattern – 프로그램의 구조에 관련된 패턴들 – 프로그램내의 자료구조나 인터페이스 구조 등 프로그램의 구조를 설계하는데 많이 활용될 수 있는 패턴들 Behavioral Pattern – 반복적으로 사용되는 객체들의 상호작용을 패턴화 해 놓은 것
Pattern Types CreationalStructuralBehavioral Abstract Factory Prototype Singleton Factory Method Builder Adapter Bridge Composite Decorator Flyweight Façade Proxy Chain of Responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor
Design Pattern Abstract Factory – 구체적인 클래스를 미리 정하지 않고, 상호 관련 있는 객체들의 패밀리 (family) 를 생성하는 인터페이스를 제공한다. Adapter – 기존 클래스의 인터페이스를 사용자가 원하는 다른 인터페이스로 변환함으로써, 서로 다른 인터페이스 때문에 상호연동을 못하는 클래스들을 연동될 수 있도록 해준다. Bridge – 시스템의 클래스들을 구현부분과 추상부분으로 분리하여 설계함으로써 두 부분이 상호 독 립적으로 바뀔 수 있도록 한다. Builder – 복잡한 객체를 생성하는 부분과 객체 표현부분을 분리함으로써, 서로 다른 객체 표현부분 들을 생성하더라도 동일한 객체 생성부분을 이용할 수 있게 한다. Chain of Responsibility – 서비스 제공자들을 체인형태로 달아둠으로써, 서비스 요청자와 서비스 제공자의 결합도 (coupling) 를 약화시키고, 복수개의 서비스 제공자를 들 수 있다. Command – 소프트웨어 내에서 발생할 수 있는 명령을 객체화시킴으로써, 명령을 기록하거나 명령을 수행하기 전 상태로 소프트웨어 상태를 복구할 때 이용할 수 있다.
Design Pattern Composite – 부분 - 전체 구조 (Part-Whole Hierarchy) 를 표현하기 위하여 객체들을 트리구조 로 구성한다. 이를 통하여 사용자가 개별적 객체나 복합적 객체를 동일하게 다 룰 수 있다. Decorator – 한 객체에 대해서 동적으로 책임사항들 (Responsibilities) 을 덧붙일 수 있다. 이 를 통하여 기능확장을 위한 서브클래싱 (Subclassing) 과 같은 효과를 거둘 수 있 다. Façade – 서브시스템 안의 여러 인터페이스들에 대하여 통합된 인터페이스를 제공한다. 제공되는 인터페이스를 통하여 서브시스템의 기능을 쉽게 사용할 수 있다. Factory Method – 생성되는 객체에 대한 결정을 서브클래스가 할 수 있도록 객체 생성인터페이스 를 제공한다. Flyweight – 수많은 작은 객체들에 대해서 효율적인 공유기능을 제공한다. Interpreter – 특정언어에 관한 문법
Design Pattern Iterator – 자료구조의 내부적 표현과 상관없이, 저장되어 있는 자료요소들을 순차적으로 접근할 수 있는 방법을 제공한다. Mediator – 객체들의 상호 작용을 캡슐화하는 객체를 정의한다. 이를 통하여 객체들 간의 커플링을 줄일 수 있으며, 각 상호 작용을 독립적으로 변경할 수 있다. Memento – 객체지향의 캡슐화 원칙을 어기지 않으면서, 객체의 내부 상태정보들을 찾아내 어 외부 객체화한다. 객체화된 상태정보는, 원 객체의 상태복구에 이용될수 있 다. Observer – 한 객체의 상태에 변화가 일어나면, 해당 객체의 상태에 관심 있는 모든 다른 객 체들에게 자동으로 변화가 발생한 사실을 알려준다. 즉 객체들간의 일 - 대 - 다 (one-to-many) 관계를 표현한다. Prototype – 원형 (prototypical) 객체를 복사하는 방식으로 객체를 생성한다. 이를 통하여 생 성하는 객체의 종류를 동적으로 지정할 수 있다.
Design Pattern Proxy – 특정 객체에 대한 접근을 관리하기 위하여 해당 객체의 대리자 (surrogate) 를 만든다. Singleton – 특정 클래스의 객체가 단 하나만 생성되도록 보장하며, 그 객체에 대한 전역 접근이 가능하 도록 해준다. State – 객체의 상태정보가 변함에 따라, 마치 객체의 클래스가 변하는 것처럼, 객체의 행동도 바뀌 도록 해준다. Strategy – 알고리즘을 객체화하여 여러 알고리즘을 동적으로 교체가능 하도록 만든다. 알고리즘을 이 용하는 클라이언트 코드와는 상관없이 알고리즘을 다양하게 바꿀 수 있다. Template Method – 연산에 있어서 전체 알고리즘의 윤곽만 기술한 다음, 알고리즘의 특정 부분의 구현을 서브 클래스로 맡긴다. 이를 통하여 전체 알고리즘의 구조를 변화시키지 않으면서 서브클래스가 알고리즘의 특정부분을 쉽게 변경시킬 수 있다. Visitor – 자료구조 내에 있는 객체 요소들에게 특정 연산을 수행하고자 원할 때 이용한다. Visitor 는 연산 수행의 대상이 되는 객체들의 클래스를 바꾸지 않고도 새로운 연산을 추가할 수 있도 록 도와준다.
Prototype Pattern 용도 – 취급하는 객체의 종류가 너무 많아 개별적으로 다른 클래스를 만들면 다량의 코딩을 해야 하는 경우 – 클래스로부터 개체 인스턴스를 생성하기 어려운 경우 예 ) 그래픽에디터에서 사용자의 마우스 조작에 의해 만들어진 인 스턴스를 프로그래밍하는 경우 – 인스턴스를 생성할 때의 framework 를 특정 클래스에 의존하지 않도록 만들고 싶은 경우 클래스 이름을 지정해서 인스턴스를 생성하는 것이 아니라 미 리 ‘ 모형 (prototype)’ 이 되는 인스턴스를 등록해 놓고, 등록된 인 스턴스를 복사해서 인스턴스를 생성
Prototype Pattern 클래스 다이아그램
Prototype Pattern – source code package framework; import java.util.*; public class Manager { private Hashtable showcase = new Hashtable(); public void register(String name, Product proto) { showcase.put(name, proto); } public Product create(String protoname) { Product p = (Product)showcase.get(protoname); return p.createClone(); }
Prototype Pattern – source code package framework; public interface Product extends Cloneable { public abstract void use(String s); public abstract Product createClone(); }
Prototype Pattern – source code import framework.*; public class MessageBox implements Product { private char decochar; public MessageBox(char decochar) { this.decochar = decochar; } public void use(String s) { int length = s.getBytes().length; for (int i = 0; i < length + 4; i++) { System.out.print(decochar); } System.out.println(""); System.out.println(decochar + " " + s + " " + decochar); for (int i = 0; i < length + 4; i++) { System.out.print(decochar); } System.out.println(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; }
Prototype Pattern – source code import framework.*; public class UnderlinePen implements Product { private char ulchar; public UnderlinePen(char ulchar) { this.ulchar = ulchar; } public void use(String s) { int length = s.getBytes().length; System.out.println("\"" + s + "\""); System.out.print(" "); for (int i = 0; i < length; i++) { System.out.print(ulchar); } System.out.println(""); } public Product createClone() { Product p = null; try { p = (Product)clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; }
Prototype Pattern – source code import framework.*; public class Main { public static void main(String[] args) { // 준비 Manager manager = new Manager(); UnderlinePen upen = new UnderlinePen('~'); MessageBox mbox = new MessageBox('*'); MessageBox sbox = new MessageBox('/'); manager.register("strong message", upen); manager.register("warning box", mbox); manager.register("slash box", sbox); // 생성 Product p1 = manager.create("strong message"); p1.use("Hello, world."); Product p2 = manager.create("warning box"); p2.use("Hello, world."); Product p3 = manager.create("slash box"); p3.use("Hello, world."); }
Singleton Pattern 용도 – 시스템 내부에 단 한 개의 인스턴스만을 생성하고자 할 때 – 예 ) 컴퓨터 자체를 표현한 클래스 현재 시스템 설정을 표현한 클래스
Singleton Pattern 클래스 다이애그램
Singleton Pattern – source code public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { System.out.println(" 인스턴스를 생성했습니다."); } public static Singleton getInstance() { return singleton; }
Singleton Pattern – source code public class Main { public static void main(String[] args) { System.out.println("Start."); Singleton obj1 = Singleton.getInstance(); Singleton obj2 = Singleton.getInstance(); if (obj1 == obj2) { System.out.println("obj1 과 obj2 는 같은 인스턴스입니다."); } else { System.out.println("obj1 과 obj2 는 같은 인스턴스입니다."); } System.out.println("End."); }
Composite Pattern 용도 – 틀 ( 그릇 ) 과 그 안의 내용물을 같은 종류로서 취급하 면 편리한 경우 – 예 ) 디렉토리 ( 폴더 ) = 디렉토리 ( 폴더 ) + 화일
Composite Pattern 클래스 다이아그램
Composite Pattern – source code public abstract class Entry { public abstract String getName(); // 이름을 얻는다. public abstract int getSize(); // 사이즈를 얻는다. public Entry add(Entry entry) throws FileTreatmentException { // 엔트리를 추가한다. throw new FileTreatmentException(); } public void printList() { // 일람을 표시한다. printList(""); } protected abstract void printList(String prefix); // prefix 를 앞에 붙여서 일람을 표시한다. public String toString() { // 문자열 표현 return getName() + " (" + getSize() + ")"; }
Composite Pattern – source code public class File extends Entry { private String name; private int size; public File(String name, int size) { this.name = name; this.size = size; } public String getName() { return name; } public int getSize() { return size; } protected void printList(String prefix) { System.out.println(prefix + "/" + this); }
Composite Pattern – source code import java.util.Iterator; import java.util.Vector; public class Directory extends Entry { private String name; // 디렉토리의 이름 private Vector directory = new Vector(); // 디렉토리 엔 트리의 집합 public Directory(String name) { // 생성자 this.name = name; } public String getName() { // 이름을 얻는다. return name; } public int getSize() { // 사이즈를 얻는다. int size = 0; Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry)it.next(); size += entry.getSize(); } return size; } public Entry add(Entry entry) { // 엔트리의 추가 directory.add(entry); return this; }
Composite Pattern – source code ( 계속 ) protected void printList(String prefix) { // 엔트리의 일람 System.out.println(prefix + "/" + this); Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry)it.next(); entry.printList(prefix + "/" + name); }
Composite Pattern – source code public class FileTreatmentException extends RuntimeException { public FileTreatmentException() { } public FileTreatmentException(String msg) { super(msg); }
Composite Pattern – source code public class Main { public static void main(String[] args) { try { System.out.println("Making root entries..."); Directory rootdir = new Directory("root"); Directory bindir = new Directory("bin"); Directory tmpdir = new Directory("tmp"); Directory usrdir = new Directory("usr"); rootdir.add(bindir); rootdir.add(tmpdir); rootdir.add(usrdir); bindir.add(new File("vi", 10000)); bindir.add(new File("latex", 20000)); rootdir.printList(); System.out.println(""); System.out.println("Making user entries..."); Directory Kim = new Directory("Kim"); Directory Lee = new Directory("Lee"); Directory Kang = new Directory("Kang");
Composite Pattern – source code ( 계속 ) usrdir.add(Kim); usrdir.add(Lee); usrdir.add(Kang); Kim.add(new File("diary.html", 100)); Kim.add(new File("Composite.java", 200)); Lee.add(new File("memo.tex", 300)); Kang.add(new File("game.doc", 400)); Kang.add(new File("junk.mail", 500)); rootdir.printList(); } catch (FileTreatmentException e) { e.printStackTrace(); }
Decorator Pattern 용도 – 지속적으로 장식 (decoration) 을 추가하는 경우 – 예 ) 문자열 주위에 여러 유형의 border 장식을 추가
Decorator Pattern 클래스 다이아그램
Decorator Pattern – source code public abstract class Display { public abstract int getColumns(); // 가로의 문자수를 얻는다. public abstract int getRows(); // 세로의 줄수를 얻는다. public abstract String getRowText(int row); // row 번째의 문자 열을 얻는다. public final void show() { // 전부 표시한다. for (int i = 0; i < getRows(); i++) { System.out.println(getRowText(i)); }
Decorator Pattern – source code public class StringDisplay extends Display { private String string; // 표시문자열 public StringDisplay(String string) {// 인수로 표시문자열을 지정 this.string = string; } public int getColumns() { // 문자수 return string.getBytes().length; } public int getRows() { // 줄수는 1 return 1; } public String getRowText(int row) { // row 가 0 일때만 반환 if (row == 0) { return string; } else { return null; }
Decorator Pattern – source code public abstract class Border extends Display { protected Display display; // 장식이 감싸고 있는 " 내용물 " 을 가리킴. protected Border(Display display) { // 인스턴스 생성시 " 내용물 “ // 을 인수로 지정 this.display = display; }
Decorator Pattern – source code public class SideBorder extends Border { private char borderChar; // 장식이 되는 문자 public SideBorder(Display display, char ch) {// 생성자에서 // Display 와 장식문자 super(display); this.borderChar = ch; } public int getColumns() {// 문자수는 내용물의 양쪽에 장식 문자분을 더 함 return 1 + display.getColumns() + 1; } public int getRows() { // 줄수는 내용물의 줄수와 같음 return display.getRows(); } public String getRowText(int row) { // 지정한 줄의 내용은 내용물의 // 지정 줄의 양쪽에 장식문자를 붙인 것 return borderChar + display.getRowText(row) + borderChar; }
Decorator Pattern – source code public class FullBorder extends Border { public FullBorder(Display display) { super(display); } public int getColumns() {// 문자수는 내용물의 양쪽에 좌우의 장식 문자분을 더한 것 return 1 + display.getColumns() + 1; } public int getRows() {// 줄수는 내용물의 줄수에 상하의 장식문자분을 더한 것 return 1 + display.getRows() + 1; } public String getRowText(int row) {// 지정한 줄의 내용 if (row == 0) { // 장식의 상단 return "+" + makeLine('-', display.getColumns()) + "+"; } else if (row == display.getRows() + 1) { // 장식의 하단 return "+" + makeLine('-', display.getColumns()) + "+"; } else { // 그 밖에 return "|" + display.getRowText(row - 1) + "|"; } private String makeLine(char ch, int count) {// 문자 ch 를 count 개 연속시킨 문자열 // 생성 StringBuffer buf = new StringBuffer(); for (int i = 0; i < count; i++) { buf.append(ch); } return buf.toString(); }
Decorator Pattern – source code public class Main { public static void main(String[] args) { Display b1 = new StringDisplay("Hello, world."); Display b2 = new SideBorder(b1, '#'); Display b3 = new FullBorder(b2); b1.show(); b2.show(); b3.show(); Display b4 = new SideBorder( new FullBorder( new SideBorder( new FullBorder( new StringDisplay(" 안녕하세요 ") ), '*' ) ), '/' ); b4.show(); }
Adapter Pattern 용도 – 이미 제공되어 있는 클래스를 그대로 사용할 수 없는 경우에, ‘ 이미 제공된 클래스 ’ 와 ‘ 필요한 클래스 ’ 사이 의 ‘ 간격 ’ 을 메우기 위함 사용 방법 – 상속 (inheritance) 을 이용 – 위임 (delegation) 을 이용 어떤 메소드의 실제 처리를 다른 인스턴스의 메소드에게 맡 김
Adapter Pattern ( 상속이용 ) 클래스 다이아그램
Adapter Pattern ( 상속이용 ) – source code public class Banner { private String string; public Banner(String string) { this.string = string; } public void showWithParen() { System.out.println("(" + string + ")"); } public void showWithAster() { System.out.println("*" + string + "*"); } public interface Print { public abstract void printWeak(); public abstract void printStrong(); }
Adapter Pattern ( 상속이용 ) – source code public class PrintBanner extends Banner implements Print { public PrintBanner(String string) { super(string); } public void printWeak() { showWithParen(); } public void printStrong() { showWithAster(); }
Adapter Pattern ( 상속이용 ) – source code public class Main { public static void main(String[] args) { Print p = new PrintBanner("Hello"); p.printWeak(); p.printStrong(); }
Adapter Pattern ( 위임이용 ) 클래스 다이아그램
Adapter Pattern ( 위임이용 ) – source code public abstract class Print { public abstract void printWeak(); public abstract void printStrong(); } public class PrintBanner extends Print { private Banner banner; public PrintBanner(String string) { this.banner = new Banner(string); } public void printWeak() { banner.showWithParen(); } public void printStrong() { banner.showWithAster(); }
Facade Pattern 용도 – 대규모 프로그램에는 서로 관련 있는 많은 클래스들 이 많은데, 복잡하게 얽혀 있는 것을 정리해서 간단한 또는 높은 레벨의 인터페이스 (API) 를 제공 – 많은 클래스들을 개별적으로 제어하지 않고 ‘ 창구 (façade)’ 에 요구 – 결과적으로 인터페이스가 적어짐 외부와의 결합도를 낮추는 효과
Facade Pattern 클래스 다이아그램
Facade Pattern – source code package pagemaker; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class Database { private Database() { // new 로 인스턴스를 생성시키지 않기 위해 private 선언 } public static Properties getProperties(String dbname) { // 데이터베이스명으 로 부터 Properties 를 얻는다. String filename = dbname + ".txt"; Properties prop = new Properties(); try { prop.load(new FileInputStream(filename)); } catch (IOException e) { System.out.println("Warning: " + filename + " is not found."); } return prop; }
Facade Pattern – source code package pagemaker; import java.io.Writer; import java.io.IOException; public class HtmlWriter { private Writer writer; public HtmlWriter(Writer writer) { // 생성자 this.writer = writer; } public void title(String title) throws IOException { // 타이틀의 출력 writer.write(" "); writer.write(" " + title + " "); writer.write(" "); writer.write(" \n"); writer.write(" " + title + " \n"); } public void paragraph(String msg) throws IOException { // 단락의 출력 writer.write(" " + msg + " \n"); } public void link(String href, String caption) throws IOException { // 링크의 출력 paragraph(" " + caption + " "); } public void mailto(String mailaddr, String username) throws IOException { // 메 일주소 출력 link("mailto:" + mailaddr, username); } public void close() throws IOException { // 닫는다. writer.write(" "); writer.write(" \n"); writer.close(); }
Facade Pattern – source code package pagemaker; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; public class PageMaker { private PageMaker() { // 인스턴스는 만들기 않기 때문에 private 선언한다. } public static void makeWelcomePage(String mailaddr, String filename) { try { Properties mailprop = Database.getProperties("maildata"); String username = mailprop.getProperty(mailaddr); HtmlWriter writer = new HtmlWriter(new FileWriter(filename)); writer.title("Welcome to " + username + "'s page!"); writer.paragraph(username + " 의 페이지에 오신걸 환영합니다."); writer.paragraph(" 메일이 기다리고 있습니다."); writer.mailto(mailaddr, username); writer.close(); System.out.println(filename + " is created for " + mailaddr + " (" + username + ")"); } catch (IOException e) { e.printStackTrace(); }
Facade Pattern – source code import pagemaker.PageMaker; public class Main { public static void main(String[] args) { "welcome.html"); } Maildata.txt 파일의 내용
Proxy Pattern 용도 –Proxy = 대리인 – 시간이 많이 걸리는 heavy job 을 수행하는 객체인스 턴스의 생성을 지연시키고자 할 때 – 예 ) 시스템 초기화 프로그램의 경우, 기동하는 시점에서 이용하 지 않는 기능까지 모두 초기화하고자 하면, 초기 기동에 많 은 시간이 소요됨 프린트 프로그램에서 실제 프린터를 기동하는 인스턴스는 시간이 많이 소요되어서, 대리인을 통해 필요 heavy job 을 수행하기 전의 일을 처리
Proxy Pattern 클래스 다이아그램
Proxy Pattern – source code public class Printer implements Printable { private String name; public Printer() { heavyJob("Printer 의 인스턴스를 생성중 "); } public Printer(String name) { // 생성자 this.name = name; heavyJob("Printer 의 인스턴스 (" + name + ") 를 생성중 "); } public void setPrinterName(String name) { // 이름의 설정 this.name = name; } public String getPrinterName() { // 이름의 취득 return name; } public void print(String string) { // 이름을 붙여서 표시 System.out.println("=== " + name + " ==="); System.out.println(string); } private void heavyJob(String msg) { // 무거운 작업 System.out.print(msg); for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.print("."); } System.out.println(" 완료 "); }
Proxy Pattern – source code public interface Printable { public abstract void setPrinterName(String name); // 이름의 설정 public abstract String getPrinterName(); // 이름의 취득 public abstract void print(String string); // 문자열 표시 ( 프린 트아웃 ) }
Proxy Pattern – source code public class PrinterProxy implements Printable { private String name; // 이름 private Printer real; // " 본인 " public PrinterProxy() { } public PrinterProxy(String name) { // 생성자 this.name = name; } public synchronized void setPrinterName(String name) { // 이름의 설정 if (real != null) { real.setPrinterName(name); // " 본인 " 에게도 설정한다. } this.name = name; } public String getPrinterName() { // 이름의 취득 return name; } public void print(String string) { // 표시 realize(); real.print(string); } private synchronized void realize() { // " 본인 " 을 생성 if (real == null) { real = new Printer(name); }
Proxy Pattern – source code public class Main { public static void main(String[] args) { Printable p = new PrinterProxy("Alice"); System.out.println(" 현재의 이름은 " + p.getPrinterName() + " 입니다."); p.setPrinterName("Bob"); System.out.println(" 현재의 이름은 " + p.getPrinterName() + " 입니다."); p.print("Hello, world."); }