먼저 이 글은 영문 위키의 글을 번역한 글임을 알려드립니다.

영어 실력이 부족한 관계로 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Facade_pattern



구조적 패턴 - 퍼사드 패턴



퍼사드 패턴은 객체 지향 프로그래밍에서 종종 사용되는 소프트웨어 디자인 패턴이다. 그 이름은 건축학의 퍼사드(전면, facade)에서 유추되었다. 

퍼사드는 많은 양의 코드를 단순화된 인터페이스로 제공하는 객체이다. (예를들면 클래스 라이브러리가 있다.)

퍼사드는 다음과 같은 것들을 할 수 있다. 

  • 퍼사드는 공통적인 작업(tasks)에 대해 편리한 메서드를 가지기 때문에, 더 사용하기 쉽고, 이해하기 쉽고, 테스트하기 쉽게 소프트웨어 라이브러리를 만들 수 있다. 
  • 같은 이유로 라이브러리를 더 읽기 쉽게 만들 수 있다.
  • 대부분의 코드가 퍼사드를 사용하기 때문에, 바깥 코드와 라이브러리 안쪽 코드의 의존성(dependency)을 줄일 수 있고, 따라서 시스템을 개발하는데에 더 유연성을 제공한다.
  • 형편없이 설계된 API의 컬렉션을 하나의 잘 설계된 API로 감쌀 수 있다.


감싸는 클래스(wrapper)가 특정 인터페이스를 준수해야하고 다형성(polymorphic)을 지원해야만 한다면 어댑터(adapter)가 사용된다. 반면에, 퍼사드는 더 쉽거나 간단한 인터페이스를 원할 때 사용한다.

구조 Structure


Facde - 퍼사드 클래스는 Package 1, 2, 3을 나머지 애플리케이션으로부터 추상화시킨다.

Clients - Package들의 리소스에 접근하기 위해 퍼사드 패턴을 사용하는 객체

예 Example

이 추상적인 예는 복잡한 시스템에서(CPU와 하드 드라이브 같은 컴퓨터 내부) 클라이언트(당신)가 어떻게 퍼사드(컴퓨터)와 상호작용하는지 보여준다.

/* Complex parts */
 
class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}
 
class Memory {
    public void load(long position, byte[] data) { ... }
}
 
class HardDrive {
    public byte[] read(long lba, int size) { ... }
}
 
/* Facade */
 
class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;
 
    public Computer() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }
 
    public void startComputer() {
        cpu.freeze();
        memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
        cpu.jump(BOOT_ADDRESS);
        cpu.execute();
    }
}
 
/* Client */
 
class You {
    public static void main(String[] args) {
        Computer facade = new Computer();
        facade.startComputer();
    }
}


저작자 표시 비영리 변경 금지
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이글은 영문 위키의 글을 번역한 글임을 알려드립니다.
영어 실력이 부족한 관계로 오역이 있을 수도 있습니다.


구조적 패턴 - 데코레이터 패턴




객체 지향 프로그래밍에서 데코레이터 패턴은 이미 존재하는 객체에 동적으로 행위(동작, behaviour)를 추가할 수 있도록 하는 디자인 패턴이다.

도입 Introduction

데코레이터 패턴은 런타임에 특정 객체의 기능을 확장하는데 사용될 수 있는데, 이 때 같은 클래스의 다른 객체에는 무관하게 기능을 확장할 수 있고,  이는 기초작업이 설계할 때 이미 완료되었을 때 가능한 일이다. 이는 원래(original) 클래스를 감싸는 새로운 데코레이터 클래스를 설계함으로써 얻을 수 있는 성과이다. 이렇게 데코레이터로 감싸는 것은 아래의 순서에 따라 하게 된다.

  1. 원래의(original) "Decorator"클래스를 "Component"클래스로부터 서브 클래싱한다.(뒤에 나오는 UML 다이어그램을 보라.)
  2. Decorator 클래스에서 Component에 대한 포인터를 필드(멤버 변수)로 추가해라.
  3. Component를 Decorator의 생성자에 인자로 전달해서, Decorator클래스에서 Component 포인터를 초기화할 수 있도록 하라.
  4. Decorator클래스에서 모든 "Component" 메서드를 "Component" 포인터로 재전송하라(redirect. 이 말을 다시 설명하자면, Component 클래스에 있는 operation()이라는 메서드를 Decorator에서 오버라이드를 하게 되는데, 이 때 Decorator에서 포인터로 가지고 있는 Component객체의 operation()을 다시 호출하라는 의미입니다. )
  5. ConcreteDecorator 클래스에서 Component 클래스의 수정이 필요한 메서드를 오버라이드 하라.


이 패턴은  매번 오버라이드된 메서드에 새로운 기능이 추가될 때마다(새로운 데코레이터로 감쌀때마다), 여러개의 데코레이터들이 스택처럼 쌓일 수 있게 설계한다.

데코레이터 패턴은 서브 클래싱의 대안이다. 서브 클래싱은 컴파일할 때 행동을 추가하게 되고, 그때 클래스의 변화는 모든 원래의(original) 클래스의 객체에 대해 변화를 일으킨다. ; 꾸미는 것(decorating)은 런타임에 각각의 객체에 새로운 행위를 제공할 수 있다. 


이러한 차이는 기능을 확장하는 몇몇 독립적인 방법들이 있을 때 아주 중요하게 다가온다. 일부 객체 지향 프로그래밍 언어에서는 클래스가 런타임에 생성될 수 없고, 전형적으로(보통, typically) 이는 설계할 때에 어떤 확장의 조합이 필요한지 예측할 수 없게된다. 이는 새로운 클래스는 모든 가능한 조합에 대해 만들어져야 함을 의미할지도 모른다. 대조적으로 데코레이터는 런타임에 만들어지는 객체라서 사용할 때마다 조합될 수 있다. 자바와 .NET 프레임웍의 I/O Streams 구현은 데코레이터 패턴을 포함하고 있다.

동기 Motivation

한 예로써, 윈도우 시스템의 윈도우를 생각해보자. 윈도우의 컨텐츠를 스크롤할 수 있도록 하기 위해, 우리는 수평 스크롤바나 수직 스크롤바를 추가하고 싶을 수도 있다. 윈도우가 Window클래스의 객체에 의해 표현된다고 가정하고, 이 클래스는 스크롤바를 추가하는 기능이 없다고 가정하자. 우리는 스크롤 기능을 제공하는 ScrollingWindow를 Window로 부터 서브클래싱하여 만들거나, 이미 존재하는 Window객체에 스크롤 기능을 추가하는 ScrollingWindowDecorator를 만들 수 있다. 이 상황에서는 둘 중 어떤 해결책도 괜찮다.


그렇다면 이번에는 우리의 윈도우에 경계선(borders)을 추가하고 싶다고 가정해보자. 마찬가지로 원래의 Window클래스는 이러한 기능을 지원하지 않는다. ScrollingWindow 서브클래스는 효과적으로 새로운 윈도우를 만들었기 때문에 한가지 문제가 생겼다. 

만약 우리가 모든 윈도우에 경계선을 추가하고 싶다면 WindowWithBorder라는 서브클래스와 ScrollingWindowWithBorder라는 서브클래스를 만들면 된다. 확실히 이런 문제는 모든 새로운 기능이 추가될 때마다 더 문제가 된다. 

데코레이터 방법으로는 런타임에 간단히 BorderWindowDecorator를 만들면 된다. 우리는 이미 존재하는 윈도우를 ScrollingWindowDecorator나 BorderedWindowDecorator로 꾸밀 수 있다.


데코레이터가 사용되면 좋은 또다른 상황은 어떤 규칙에 따라 객체의 속성이나 메서드에 대한 접근을 한정지어야 할 필요가 있을 때 이다. (예를들면 다른 사용자 자격인증서.) 이러한 경우에 원래의 객체에 접근 제어를 구현하는 것보다는 원래 객체는 변화시키지 않고 속성이나 메서드 사용에 대해 아무것도 모르도록하고, 접근 제어를 할 수 있는 데코레이터 객체로 감싸면 오직 승인된 인터페이스만 제공하도록 할 수 있다.

구조 Structure


예 Examples

자바

첫번째 예 (window / scrolling 시나리오)

아래의 자바 예는 window/scrolling 시나리오에서 데코레이터의 사용을 설명하고 있다.

// the Window interface
interface Window {
    public void draw(); // draws the Window
    public String getDescription(); // returns a description of the Window
}
 
// implementation of a simple Window without any scrollbars
class SimpleWindow implements Window {
    public void draw() {
        // draw window
    }
 
    public String getDescription() {
        return "simple window";
    }
}


아래의 클래스는 모든 Window클래스(데코레이터 자신들을 포함한)에 대한 데코레이터들이다.

// abstract decorator class - note that it implements Window
abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated
 
    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    public void draw() {
        decoratedWindow.draw();
    }
}
 
// the first concrete decorator which adds vertical scrollbar functionality
class VerticalScrollBarDecorator extends WindowDecorator {
    public VerticalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }
 
    public void draw() {
        decoratedWindow.draw();
        drawVerticalScrollBar();
    }
 
    private void drawVerticalScrollBar() {
        // draw the vertical scrollbar
    }
 
    public String getDescription() {
        return decoratedWindow.getDescription() + ", including vertical scrollbars";
    }
}
 
// the second concrete decorator which adds horizontal scrollbar functionality
class HorizontalScrollBarDecorator extends WindowDecorator {
    public HorizontalScrollBarDecorator (Window decoratedWindow) {
        super(decoratedWindow);
    }
 
    public void draw() {
        decoratedWindow.draw();
        drawHorizontalScrollBar();
    }
 
    private void drawHorizontalScrollBar() {
        // draw the horizontal scrollbar
    }
 
    public String getDescription() {
        return decoratedWindow.getDescription() + ", including horizontal scrollbars";
    }
}

여기 아래에는 완전히 꾸며진(fully decorated) Window객체를 생성하는 테스트 프로그램이고, 실행해보면 설명을 출력한다.

public class DecoratedWindowTest {
    public static void main(String[] args) {
        // create a decorated Window with horizontal and vertical scrollbars
        Window decoratedWindow = new HorizontalScrollBarDecorator (
                new VerticalScrollBarDecorator(new SimpleWindow()));
 
        // print the Window's description
        System.out.println(decoratedWindow.getDescription());
    }
}

이 프로그램의 결과는 "simple window, including vertical scrollbars, including horizontal scrollbars"라고 나올 것이다. 두 데코레이터의 getDescription 메서드에서 어떻게 처음에 꾸며진(decorated) Window의 설명(description)을 가져오고 그 뒤에 꾸미는 작업(decorates)을 하게되는지 유심히 살펴보아라.




두번째 예(커피 만드는 시나리오)

다음의 자바 예는 커피를 만드는 시나리오에서 데코레이터를 사용하는 방법을 설명하고 있다. 이 예에서 시나리오는 가격과 재료만 포함하고 있다.

// The Coffee Interface defines the functionality of Coffee implemented by decorator
public interface Coffee {
    public double getCost(); // returns the cost of the coffee
    public String getIngredients(); // returns the ingredients of the coffee
}
 
// implementation of a simple coffee without any extra ingredients
public class SimpleCoffee implements Coffee {
    public double getCost() {
        return 1;
    }
 
    public String getIngredients() {
        return "Coffee";
    }
}

다음의 클래스들에는 모든 커피 클래스가 포함되어 있다.(데코레이터 자신도 포함해서 말이다.)

// abstract decorator class - note that it implements Coffee interface
abstract public class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;
    protected String ingredientSeparator = ", ";
 
    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }
 
    public double getCost() { // implementing methods of the interface
        return decoratedCoffee.getCost();
    }
 
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}
 
// Decorator Milk that mixes milk with coffee
// note it extends CoffeeDecorator
public class Milk extends CoffeeDecorator {
    public Milk(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() { // overriding methods defined in the abstract superclass
        return super.getCost() + 0.5;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Milk";
    }
}
 
// Decorator Whip that mixes whip with coffee
// note it extends CoffeeDecorator
public class Whip extends CoffeeDecorator {
    public Whip(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() {
        return super.getCost() + 0.7;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Whip";
    }
}
 
// Decorator Sprinkles that mixes sprinkles with coffee
// note it extends CoffeeDecorator
public class Sprinkles extends CoffeeDecorator {
    public Sprinkles(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
 
    public double getCost() {
        return super.getCost() + 0.2;
    }
 
    public String getIngredients() {
        return super.getIngredients() + ingredientSeparator + "Sprinkles";
    }
}

아래에는 완전히 꾸며진(fully decorated) Coffee의 객체를 생성하는 테스트 프로그램이고, 여기에서는 커피의 가격을 계산하고 재료를 출력한다.

public class Main
{
    public static void main(String[] args)
    {
        Coffee c = new SimpleCoffee();
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
        c = new Milk(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
        c = new Whip(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
 
        // Note that you can also stack more than one decorator of the same type
        c = new Sprinkles(c);
        System.out.println("Cost: " + c.getCost() + "; Ingredients: " + c.getIngredients());
    }
}

이 프로그램의 출력 결과는 다음과 같다.

Cost: 1.0; Ingredients: Coffee
Cost: 1.5; Ingredients: Coffee, Milk
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
Cost: 2.4; Ingredients: Coffee, Milk, Sprinkles, Whip
Cost: 2.6; Ingredients: Coffee, Milk, Sprinkles, Whip, Sprinkles



동적인 언어들(Dynamic languages)

데코레이터 패턴은 동적인 언어들에서도 사용될 수 있는데, 이때에는 인터페이스와 전통적인 OOP 상속을 사용하지 않고 쓴다.

자바스크립트(커피 만드는 시나리오)

// Class to be decorated
function Coffee() {
 
}
 
Coffee.prototype.cost = function() {
        return 1;
};
 
// Decorator A
function Milk(coffee) {
        var currentCost = coffee.cost();
        coffee.cost = function() {
                return currentCost + 0.5;
        };
 
        return coffee;
}
 
// Decorator B
function Whip(coffee) {
        var currentCost = coffee.cost();
        coffee.cost = function() {
                return currentCost + 0.7;
        };
 
        return coffee;
}
 
// Decorator C
function Sprinkles(coffee) {
        var currentCost = coffee.cost();
        coffee.cost = function() {
                return currentCost + 0.2;
        };
 
        return coffee;
}
 
// Here's one way of using it
var coffee = new Milk(new Whip(new Sprinkles(new Coffee())));
alert( coffee.cost() );
 
// Here's another
var coffee = new Coffee();
coffee = new Sprinkles(coffee);
coffee = new Whip(coffee);
coffee = new Milk(coffee);
alert(coffee.cost());


저작자 표시 비영리 변경 금지
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이 글은 영문 위키의 글을 번역한 것임을 알려드립니다.

영어 실력이 부족한 관계로 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Bridge_pattern



구조적 패턴 - 브릿지 패턴




브릿지 패턴은 소프트웨어 공학에서 사용되는 디자인 패턴인데, 이는 "구현(implementation)으로부터 추상(abstraction) 레이어를 분리하여 이 둘이 서로 독립적으로 변화할 수 있도록 한다."

브릿지는 캡슐화(encapsulation), 집합(aggregation)을 사용하고 또한 다른 클래스들로 책임을 분리시키기 위해 상속(inheritance)를 사용할 수 있다. 


어떤 클래스가 자주 바뀐다면(varies), 객체 지향 프로그래밍의 특징들은 아주 유용해질 수 있다. 왜냐하면 프로그램 코드를 수정하는데 프로그램에 대해 최소한만 알고도 쉽게 수정할 수 있기 때문이다. 브릿지 패턴은 어떤 클래스와 그 클래스가 하는 일이 자주 변화할 때 유용하게 사용될 수 있다. 여기에서 어떤 클래스는 구현(implementation)이라 생각할 수 있고, 그 클래스가 할 수 있는 일은 추상(abstraction)으로 생각할 수 있다. 브릿지 패턴은 추상화(abstraction)의 두 계층(layer)로 생각할 수도 있다.


브릿지 패턴은 종종 어댑터 패턴과 혼동될 수 있다. 사실, 브릿지 패턴은 종종 클래스 어댑터 패턴을 사용해서 구현되기도 한다. 아래에 나오는 자바 코드가 그 예가 될 것이다.


변종(Variant) : 추상(abstraction)이 사용되는 시점까지 구현의 존재를 미룸으로써 구현을 더욱 갈라놓을 수 있다.(정확히 이해는 안되지만 구현 객체를 최대한 늦게 생성함으로써 구현과 추상의 관계를 더욱 약하게 할 수 있다는 말인것 같네요;

원문 : The implementation can be decoupled even more by deferring the presence of the implementation to the point where the abstraction is utilized.)

구조 Structure

Abstraction - 추상 인터페이스를 정의한다. Implementor에 대한 레퍼런스를 유지한다.

RefinedAbstraction - Abstraction에 의해 정의된 인터페이스를 확장한다.(extends)

Implementor - 구현 클래스를 위한 인터페이스를 정의한다.

ConcreteImplementor - Implementor 인터페이스를 구현한다. 

예 Example

아래의 자바 프로그램은 'shape'를 이용한 예이다. 


/** "Implementor" */
interface DrawingAPI {
    public void drawCircle(double x, double y, double radius);
}
 
/** "ConcreteImplementor"  1/2 */
class DrawingAPI1 implements DrawingAPI {
   public void drawCircle(double x, double y, double radius) {
        System.out.printf("API1.circle at %f:%f radius %f\n", x, y, radius);
   }
}
 
/** "ConcreteImplementor" 2/2 */
class DrawingAPI2 implements DrawingAPI {
   public void drawCircle(double x, double y, double radius) {
        System.out.printf("API2.circle at %f:%f radius %f\n", x, y, radius);
   }
}
 
/** "Abstraction" */
abstract class Shape {
   protected DrawingAPI drawingAPI;
 
   protected Shape(DrawingAPI drawingAPI){
      this.drawingAPI = drawingAPI;
   }
 
   public abstract void draw();                             // low-level
   public abstract void resizeByPercentage(double pct);     // high-level
}
 
/** "Refined Abstraction" */
class CircleShape extends Shape {
   private double x, y, radius;
   public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
      super(drawingAPI);
      this.x = x;  this.y = y;  this.radius = radius;
   }
 
   // low-level i.e. Implementation specific
   public void draw() {
        drawingAPI.drawCircle(x, y, radius);
   }
   // high-level i.e. Abstraction specific
   public void resizeByPercentage(double pct) {
        radius *= pct;
   }
}
 
/** "Client" */
class BridgePattern {
   public static void main(String[] args) {
       Shape[] shapes = new Shape[] {
           new CircleShape(1, 2, 3, new DrawingAPI1()),
           new CircleShape(5, 7, 11, new DrawingAPI2()),
       };
 
       for (Shape shape : shapes) {
           shape.resizeByPercentage(2.5);
           shape.draw();
       }
   }
}



저작자 표시 비영리 변경 금지
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이 글은 영문 위키의 글을 번역한 글임을 알려드립니다.

영어 실력이 부족한 관계로 오역이 있을 수 있습니다.^^;

원문 주소 : http://en.wikipedia.org/wiki/Adapter_pattern



구조 패턴 - 어댑터(Apapter)



컴퓨터 프로그래밍에서 어댑터 패턴은(종종 wrapper pattern이라고 언급되기도 하고, 더 간단히 wrapper라고 불리기도 한다.) 한 클래스의 인터페이스를 호환 가능한 다른 인터페이스로 변환시키는 디자인 패턴이다. 

호환되지 않는 인터페이스때문에 일반적인 방법으로는 같이 사용할 수 없는 클래스들을 어댑터 패턴을 이용하면 같이 사용할 수 있다. 이는 클라이언트 코드에게 원래 클래스의 인터페이스를 노출시키는 대신에 어댑터의 인터페이스를 노출시킴으로써 가능해진다. 

어댑터는 어댑터 인터페이스에 대한 호출을 원래 클래스의 인터페이스 호출로 변환하고, 이를 위해 필요한 코드 량은 일반적으로 작다. 어댑터는 또한 데이터를 적절한 형태로 변환하는 역할도 한다. 

예를 들어, 여러개의 불(boolean) 값이 하나의 정수에 들어있는데(예를들면 flag같은 경우), 당신의 클라이언트 코드에서는 하나의 'true'나 'false' 값만 필요하다면 어댑터는 이 정수에서 적절한 값을 추출해내는 역할을 할 수 있다. 

다른 예는 날짜의 형태를 변경하는 것이다.(예를들면 YYYYMMDD를  MM/DD/YYYY 형태로 만들거나 DD/MM/YYYY 같은 형태로 만들 수 있다.)




구조 Structure

어댑터 패턴에는 두가지 종류가 있다. 

객체 어댑터 패턴 Object Adapter Pattern

이 타입의 어댑터 패턴에서 어댑터는 감싸야할 클래스의 객체를 포함하고 있다. 이러한 상황에서 어댑터는 감싸진 객체를 호출하는 일을 한다.






클래스 어댑터 패턴 Class Adapter Pattern

이 타입의 어댑터는 목표를 달성하기 위해 여러개의 다형성 인터페이스(polymorphic interfaces)를 사용한다. 어댑터는 구현될 예정인 인터페이스나 이미 구현된 인터페이스를 상속받아서 만들어진다. 구현될 예정인 인터페이스는 순수(pure) 인터페이스 클래스로 만들어지는 것이 일반적인데, 특히 자바같이 다중 상속(multiple inheritance)을 지원하지 않는 언어들에서 그러하다.




어댑터 패턴은 이미 존재하는 클래스가 당신이 필요로하는 일부 또는 전체 서비스를 제공하지만 당신에게 필요한 인터페이스는 제공하지 않는 상황에 유용할 수 있다. 어댑터의 좋은 실제 예는 XML 문서의 DOM(Document Object Model)의 인터페이스를 볼 수 있는  트리 구조로 바꾸는 것이다. 어댑터 디자인 패턴을 사용하는 튜토리얼 링크는 아래에 나열해 놓았다. 



더 발전된 형태의 실시간 어댑터 패턴(?) A further form of runtime Adapter Pattern

아래에 나오는 것과 같이 더 발전된 형태의 실시간으로 사용될 수 있는 어댑터 패턴이 있다 : 

classA가 약간의 데이터를 가지고(String데이터라 가정하고) classB를 지원하도록 요구되어진다. 컴파일 시의 해결책은 다음과 같다.



classB.setStringData(classA.getStringData());


string데이터의 포맷(형식)이 다양해져야 한다고 가정해보자. 컴파일 시의 해결책은 상속을 사용하는 것이다.:
Format1ClassA extends ClassA {
   public String getStringData() {
      return format(toString());
   }
}


그리고 팩토리 패턴의 도움으로 런타임시에 정확하게 표현할 형식(formatting) 객체를 만들 수 있다.



어댑터를 사용하는 방법의 진행 절차는 다음과 같다.:



(i) 중개하는 Provider 인터페이스를 정의하고, 데이터의 소스를 감싸는 Provider 인터페이스의 구현을 작성해라. 이 예에서는 ClassA가 데이터 소스가 되고, 적절한 형태를 갖춘 데이터를 만들어낸다.


public interface StringProvider {
    public String getStringData();
}
 
public class ClassAFormat1 implements StringProvider {
    ClassA classA;
 
    public ClassAFormat1(ClassA classA) {
        this.classA = classA;
    }
 
    public String getStringData() {
        return format(classA.toString());
    }
}



(ii) Provider의 특정 구현을 반환하는 Adapter클래스를 작성한다.
public class ClassAFormat1Adapter extends Adapter {
   public Object adapt(Object o) {
      return new ClassAFormat1((ClassA) o);
   }
}


(iii) Adatper를 전역 레지스트리에 등록해서 Adapter가 런타임 시에 사용될 수(can be loocked up) 있도록 한다.
AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");



(iv) 당신의 코드에서 ClassA의 데이터를 ClassB로 옮기고 싶다면 다음과 같이 작성한다.


Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
StringProvider provider = (StringProvider) adapter.adapt(classA);
String string = provider.getStringData();
classB.setStringData(string);


또는 더 간결하게 하자면,

classB.setStringData(((StringProvider) AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format1").adapt(classA)).getStringData());


(v) 만약 데이터를 두번째 형식으로 전송하고 싶다면 다른 Adapter와 Provider를 찾아서(look up) 쓸 수 있다는 것이 장점이라 볼 수 있다.


Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format2");



(vi) 그리고 만약 ClassA로부터 ClassC의 이미지 데이터로 출력하고 싶다면 :
Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, ImageProvider.class, "format1");
ImageProvider provider = (ImageProvider) adapter.adapt(classA);
classC.setImage(provider.getImage());



(vii) 이런 방법으로 Adapter와 Provider를 사용하는 것은 여러개의 뷰(views)를 제공할 수 있다. 일반적으로, 기존 객체 구조에 새로 장착할 수 있는 객체들 사이를 중개 데이터가 들어갈 수 있는 메카니즘을 허용한다.(그러니까 기존 객체 구조를 유지하고 어댑터를 이용한 메카니즘을 사용한다는 의미인 것 같은데,,, 해석하려니 말이 어렵네요-_-)




어댑터 패턴의 구현 Implementation of Adapter pattern

어댑터 패턴을 구현할 때, 명료성을 위해서 클래스 이름을 "[AdapteeClassName]To[Interface]Adapter"와 같은 식으로 만든다. 예를들면 DAOToProviderAdapter(여기에서 DAO가 AdapteeClassName에 해당하고, Provider가 Interface에 해당하겠죠?). 

이를 위해서는 adaptee클래스를 인자로 받는 생성자가 필요하다. 이 인자는 "[AdapteeClassName]To[Interface]Adapter"의 객체 멤버로 전달될 것이다.


Class SampleAdapter implements ClientClass
{
    private AdapteeClass mInstance;
    public SampleAdapter(AdapteeClass instance)
    {
         mInstance=instance;
    }
    @Override
    public void ClientClassMethod()
    {
       // call AdapteeClass's method to implement ClientClassMethod
    }
 
}


저작자 표시
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이 글은 영문 위키의 글을 번역한 글임을 알려드립니다.

영어 실력이 부족하여 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Prototype_pattern



생성 패턴 - 프로토타입



The prototype pattern(이하 프로토타입 패턴)은 프로토타입(원형, 기본틀 등으로 해석할 수 있겠네요.) 객체에 의해 생성될 객체의 타입이 결정되는 생성 디자인 패턴이다. 이 패턴은 새로운 객체를 생성하기 위해 clone(복제)을 이용한다. 

이 패턴은 

  • 클라이언트 어플리케이션에서 객체 생성자의 서브 클래싱을 피한다.(반대로 추상 팩토리 패턴에서는 객체 생성자를 서브 클래싱해야만 한다.)
  • 주어진 애플리케이션에서 일반적인 방법(new 키워드를 이용한 방법)으로 객체를 생성할 때에 필요한 비용이 엄청난 경우에 이 비용을 없앨 수 있다.


이 패턴을 구현하기 위해, pure virtual(순수 추상 메서드라고 하면 되나요?... 전 원래 그냥 퓨어 버츄얼이라고 읽어서..) clone()메서드를 가지는 추상 클래스를 선언한다. polymorphic constructor(다형적인 생성자)가 필용한 어떤 클래스든지 추상 클래스를 상속받아서 clone()메서드를 구현해야 한다.

클라이언트는 new 연산자와 클래스 이름을 같이 쓰기보다는 프로토타입 객체의 clone()메서드를 호출하거나, 팩토리에 원하는 클래스에 해당하는 인자를 넣어서 호출하거나, 다른 디자인 패턴에 의해 제공되는 어떤 메커니즘을 통해 clone()메서드를 호출할 수도 있다.


구조 Structure



예 Example

프로토타입 패턴은 프로토타입 객체를 이용해서 어떤 종류의 객체를 생성할 지 정할 수 있다. 새로운 객체의 프로토타입은 종종 완전히 만들어지기 전에 생성이된다. 하지만 이 예에서는 프로토타입이 수동적이고(passive) 스스로는 복제하지는 않는다. 세포의 유사분열(두개의 독립적인 세포가 만들어진다.)은 프로토타입의 한 예이다. 유사분열은 스스로는 복제하는 일을 하기 때문에 프로토타입 패턴의 실례라 할 수 있다. 세포가 분열될 때, 두개의 독립적인 유전자 형이 결과물로 나온다. 다른 말로 세포가 스스로를 복제한다는 말이다. 


Java



/**
 * Prototype class
 */
abstract class Prototype implements Cloneable {
        @Override
        public Prototype clone() throws CloneNotSupportedException {
                return (Prototype)super.clone();
        }
 
        public abstract void setX(int x);
 
        public abstract void printX();
 
        public abstract int getX();
}
 
/**
 * Implementation of prototype class
 */
class PrototypeImpl extends Prototype {
        int x;
 
        public PrototypeImpl(int x) {
                this.x = x;
        }
 
        public void setX(int x) {
                this.x = x;
        }
 
        public void printX() {
                System.out.println("Value :" + x);
        }
 
        public int getX() {
                return x;
        }
}
 
/**
 * Client code
 */
public class PrototypeTest {
        public static void main(String args[]) throws CloneNotSupportedException {
                Prototype prototype = new PrototypeImpl(1000);
 
                for (int i = 1; i < 10; i++) {
                        Prototype tempotype =  prototype.clone();
 
                        // Usage of values in prototype to derive a new value.
                        tempotype.setX( tempotype.getX() * i);
                        tempotype.printX();
                }
        }
}
 
/*
**Code output**
 
Value :1000
Value :2000
Value :3000
Value :4000
Value :5000
Value :6000
Value :7000
Value :8000
Value :9000
*/


경험에 의한 규칙 Rules of Thumb

때때로 생성 패턴은 겹친다. - 프로토타입이나 추상팩토리 둘 중 하나가 적절한 경우가 있다. 

또 다른 경우에 생성 패턴들은 상호 보완적이기도 하다. : 추상 팩토리는 제품 객체를 복제해서 리턴해주기 위해 프로토타입의 집합을 저장하고 있을 수 있다. 추상 팩토리, 빌더, 프로토타입은 각자의 구현에 싱글톤을 사용할 수도 있다. 

추상 팩토리는 종종 팩토리 메서드로 구현되기도하지만(상속 inheritance을 통해 제품 객체를 생성하는 경우), 프로토타입을 이용해서 구현할 수도 있다.(위임 delegate를 이용해 생성하는 경우)


종종 설계자가 어디에서 더 유연한 설계가 필요한지를 발견하기 때문에,  설계는 팩토리 메서드를 사용해서 시작하고 추상 팩토리, 프로토타입, 똔느 빌더 등으로 발전할 수 있다.


프로토타입은 서브클래싱이 필요없지만, 초기화"initialize" 메서드를 필요로한다. 팩토리 메서드는 서브클래싱이 필요하지만, 초기화는 불필요하다.


콤포짓(Composite)과 데코레이터(Decorator)패턴을 많이 사용하는 설계에서는 종종 프로토타입을 사용하는 것이 좋을 수 있다. 


경험에 의하면 런타임시에 당신이 복제하기 위하는 객체를 트루 카피(true copy)해서 생성하고 싶을 때 clone()메서드가 필요할 수 있다. True copy라하면 새로 생성되는 객체의 모든 속성(attributes)이 그를 복제해내는 객체와 같아야 한다는 것이다. 만약 당신이 new를 사용해서 그 클래스를 초기화할 수 있었다면, 초기값들을 속성으로 가진 객체를 얻어냈을 것이다. 예를 들어, 당신이 은행 거래를 수행하는 시스템을 설계했다고 하면, 당신은 자신의 계좌 정보를 가지고 있는 객체를 복사하고, 복제된 객체를 이용해 거래를 하고나서 수정된 객체로 기존 객체를 덮고 싶을 것이다. 이러한 경우에 당신은 new 대신 clone()을 사용하고 싶을 것이다. 

저작자 표시
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이 글은 영문 위키의 글을 번역한 것을 알려드립니다.

영어 실력이 부족해 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Builder_pattern



생성 패턴 - 빌더 패턴



The Builder Pattern(이하 빌더 패턴)은 객체 생성 소프트웨어 디자인 패턴이다. 이 패턴의 의도는 객체의 생성의 단계(steps)들을 추상화해서, 이런 생성 단계의 구현이 달라지면 객체의 다른 표현이 나오도록 하는 것이다. 종종 빌더 패턴은 composite 패턴에 따라 제품을 생성하기 위해 사용된다. 


정의 Definition

빌더 디자인 패턴의 의도는 복잡한 객체의 생성을 표현으로부터 분리시키는 것이다. 이렇게 함으로써 같은 생성 과정(process)에서 다른 표현을 만들어 낼 수 있다.


구조 Structure


Builder - 객체(제품)를 생성하는 추상 인터페이스

Concrete Builder - Builder의 구현 클래스. 다른 객체를 생성할 수 있도록 하는 구체적인 클래스. 객체를 만들기 위해 부분(부품)을 생성하고 조립한다.

Director - Director클래스는 객체 생성의 정확한 순서(sequenct)를 다루는 부분에 책임이 있다. 이 클래스는 ConcreteBuilder를 인자로 받아서 필요한 동작을 수행한다.

Product - Builder를 이용해서 Director가 만들어낸 최종 객체


유용한 팁 Useful tips

  • 빌더는 복잡한 객체를 한단계 한단계씩 생성하는데에 초점을 맞춘다. 추상 팩토리는 제품 객체들의 집단(군)을 강조한다.(생성되는 객체가 단일 객체이던지, 복잡한 객체이던지는 상관없다.) 빌더는 제품을 마지막 단계에 반환하지만, 추상 팩토리가 하는 것처럼 제품을 곧바로 얻어낸다.
  • 빌더는 주로 복잡한 객체(Composite)를 생성한다.
  • 종종, 설계는 팩토리 메서드를 사용해서 시작하지만 설계자가 어느 부분에 있어서 더 많은 유연성이 필요하냐를 발견함에 따라 추상 팩토리, 프로토타입 또는 빌더 등으로 발전하게 된다.
  • 가끔 생성 패턴은 상호 보완적이다. : 빌더는 어떤 컴포넌트가 만들어지는지 정하기 위해 다른 패턴들 중 하나를 사용할 수 있다. 추상 팩토리, 빌더, 프로토타입은 싱글톤을 구현에 사용할 수 있다.
  • 빌더는 fluent interface의 좋은 지원자이다.

예 Examples

Java

/** "Product" */
 class Pizza {
    private String dough = "";
    private String sauce = "";
    private String topping = "";
 
    public void setDough(String dough)     { this.dough = dough; }
    public void setSauce(String sauce)     { this.sauce = sauce; }
    public void setTopping(String topping) { this.topping = topping; }
 }
 
 
 /** "Abstract Builder" */
 abstract class PizzaBuilder {
    protected Pizza pizza;
 
    public Pizza getPizza() { return pizza; }
    public void createNewPizzaProduct() { pizza = new Pizza(); }
 
    public abstract void buildDough();
    public abstract void buildSauce();
    public abstract void buildTopping();
 }
 
 /** "ConcreteBuilder" */
 class HawaiianPizzaBuilder extends PizzaBuilder {
    public void buildDough()   { pizza.setDough("cross"); }
    public void buildSauce()   { pizza.setSauce("mild"); }
    public void buildTopping() { pizza.setTopping("ham+pineapple"); }
 }
 
 /** "ConcreteBuilder" */
 class SpicyPizzaBuilder extends PizzaBuilder {
    public void buildDough()   { pizza.setDough("pan baked"); }
    public void buildSauce()   { pizza.setSauce("hot"); }
    public void buildTopping() { pizza.setTopping("pepperoni+salami"); }
 }
 
 
 /** "Director" */
 class Waiter {
    private PizzaBuilder pizzaBuilder;
 
    public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; }
    public Pizza getPizza() { return pizzaBuilder.getPizza(); }
 
    public void constructPizza() {
       pizzaBuilder.createNewPizzaProduct();
       pizzaBuilder.buildDough();
       pizzaBuilder.buildSauce();
       pizzaBuilder.buildTopping();
    }
 }
 
 
 /** A customer ordering a pizza. */
 class BuilderExample {
    public static void main(String[] args) {
        Waiter waiter = new Waiter();
        PizzaBuilder hawaiian_pizzabuilder = new HawaiianPizzaBuilder();
        PizzaBuilder spicy_pizzabuilder = new SpicyPizzaBuilder();
 
        waiter.setPizzaBuilder( hawaiian_pizzabuilder );
        waiter.constructPizza();
 
        Pizza pizza = waiter.getPizza();
    }
 }



저작자 표시
신고
Posted by Code-Moon

댓글을 달아 주세요

먼저 이 글은 영문 위키의 글을 번역한 것임을 알려드립니다.

영어 실력이 부족해 오역이 있을 수도 있으니 이해바랍니다.

원문 : http://en.wikipedia.org/wiki/Abstract_factory_pattern



생성 패턴 - Abstract Factory Pattern



The abstract factory pattern(이하 추상 팩토리 패턴)은 공통의 테마를 가진 팩토리의 그룹을 캡슐화하는 방법을 제공하는 소프트웨어 디자인 패턴이다. 일반적으로 클라이언트 소프트웨어는 추상 팩토리의 구체적인 구현체를 생성하고 그 구현체의 인터페이스를 사용한다. 클라이언트는 생성된 객체의 인터페이스만 사용하기 때문에 각각의 내부 팩토리로부터 얻는 구체적인 객체에 대해 알지 못한다. 이 패턴은 어던 객체들의 셋(또는 그룹)에 대한 구체적인 구현을 일반적인 사용법(인터페이스)로부터 분리시킨다. 


이에 대한 한 예를 들자. 

많은 종류의 제품(product)를 생성하는 인터페이스를 제공하는 DocumentCreator라는 추상 팩토리 클래스를 생각해볼 수 있다.(이 클래스는 createLetter()와 createResume()메서드를 이용해서 각각 편지와 이력서를 생성하도록 할 것이다.)

시스템은 DocumentCreator클래스를 상속한 몇몇 클래스를 가질 수 있다. 에를 들면 FancyDocumentCreator나 ModernDocumentCreator같은 클래스를 생각해 볼 수 있는데, 이 각각의 서브클래스는 createLetter()와 createResume()메서드를 각 클래스의 테마에 맞게 각각 다른 객체를 생성하도록할 수 있다. 

이렇게 생성되는 제품(여기에서 제품은 createLetter() 메서드로 생성되는 객체와 createResume() 메서드로 생성되는 객체를 의미한다.)은 클라이언트가 알고 있는 추상 클래스인 Letter와 Resume에서 유도된(derived, 서브클래스) 클래스이다.

클라이언트 코드에서는 DocumentCreator의 적절한 인스턴스(객체, 서브클래스)를 얻을 것이고 팩토리 메서드를 호출할 것이다. 각각의 결과 객체(각각의 결과 객체라 함은 Letter와 Resume라는 생성된 객체를 의미한다.)는 같은 DocumentCreator의 구현체로부터 만들어질 것이고, 같은 테마를 공유할 것이다. (추가 설명 : 클라이언트 코드에서는 FancyDocumentCreator나 ModernDocumentCreator 둘 중 하나의 추상 팩토리를 사용할 것이고, 둘 중 결정된 추상 팩토리에 따라 테마가 결정될 것이다. 즉, fancy테마인지 modern테마인지가 추상 팩토리를 결정하는 단계에서 결정이 되고, 클라이언트 코드에서 createLetter(), createResume()를 이용해서 생성한 객체는 같은 테마일 수 밖에 없을 것이다. 다시 말하면 FancyResume 객체와 ModernLetter를 같이 사용하는 일을 없다는 의미이다.) 클라이언트는 추상클래스인 Letter나 Resume에 대해서만 알면되고, 각각의 구체적인 클래스가 무엇인지에 대해서는 알 필요가 없다. 왜냐하면 그 구체적인 부분은 서브클래스 팩토리에서 결정하면 되는 부분이기 때문이다. 


팩토리는 어떤 객체들을 생성할 지 결정하는 위치(구체적인 클래스)이다. 이 패턴을 사용하는 목적은 객체의 생성을 사용하는 곳으로부터 분리시키는 것이다. 이는 베이스 클래스를 사용하는 코드(클라이언트 코드)에서 변경없이 새로운 타입의 서브클래스를 사용하도록 한다.


이 패턴을 사용하면 클라이언트 코드의 변경없이(심지어는 실행중에도) 구체 클래스를 교환하는 것이 가능하다는 것이다. 하지만 이패턴을 사용하면 비슷한 다른 패턴과 마찬가지로 불필요한 복잡도를 증가시킬 수 있고, 코드의 초기 작성에서 추가적인 작업이 들어갈 수 있다. 


정의 Definition

추상 팩토리 매서드 패턴의 정수(essence)는 "관련된 객체들의 집단(군, family)를 생성하는 인터페이스를 제공하되, 생성되는 객체의 구체적인 클래스를 알 필요없다"는 것이다.


사용법 Usage

팩토리는 생성될 객체의 구체적인 타입을 결정하고, 또한 구체적인 클래스가 생성되는 곳이다.

하지만, 팩토리는 생성되는 구체적인 객체의 추상 포인터만 반환한다.

이는 클라이언트가 팩토리 객체에게 생성할 객체의 추상 포인터를 리턴하도록 요청하기 때문에, 클라이언트 코드와 객체 생성을 분리시킨다. 

팩토리는 오직 추상 포인터만 반환을 하기 때문에, 클라이언트 코드는 실제 생성되는 객체의 구체적인 타입을 알 필요가 없다. 하지만, 구체적인 객체의 타입은 추상 팩토리가 알고 있다. (예를들면 팩토리가 설정 파일에서 이에 대한 정보를 읽을 수도 있을 것이다.) 설정 파일에서 이미 타입을 구체적으로 표현했기 때문에, 클라이언트는 타입을 구체화할 필요가 없다. 

이는 아래 내용을 의미한다.

  • 클라이언트 코드는 생성하려는 객체의 구체적인 타입이 무엇이든간에 알 필요가 없다. (구체적인 타입과 관련된 헤더 파일이나 클래스 정의를 포함할 필요가 없다.) 클라이언트 코드는 오직 추상적인 타입만을 다룰 뿐이다. 구체적인 타입의 객체는 팩토리에 의해서 생성되고, 클라이언트 코드에서는 추상 인터페이스를 통해서만 그러한 객체에 접근을 하는 것이다. 
  • 새로운 구체적인 타입을 추가하는 것은 클라이언트에서 다른 팩토리를 사용하도록 수정함으로써 해결된다. 이는 단지 1 줄의 코드 변경으로 될 것이다.(그렇게 하면 다른 팩토리는 다른 구체적인 타입의 객체를 생성하지만, 여전히 같은 추상 클래스 의 포인터를 반환한다.) 이는 새로운 타입의 객체를 생성하기 위해 클라이언트 코드를 수정하는 것보다(추상 팩토리를 사용하지 않으면 새로운 객체를 생성하는 클라이언트 코드의 모든 곳을 변경해야만 한다.) 훨씬 쉬운 방법이다. 뿐만 아니라 추상 팩토리를 사용하지 않으면 새로운 객체를 생성하는 모든 코드에서 구체적인 클래스에 대한 헤더 파일을 포함시킴으로써 구체적인 클래스 타입을 알아야만 한다. 만약 모든 팩토리 객체가 하나의 전역 싱글톤(singleton)객체에 저장되어 있고, 모든 클라이언트 코드가 객체 생성을 위한 적절한 팩토리에 접근하기 위해 싱글톤을 이용한다면, 팩토리를 변경시키는 것은 싱글톤 객체를 변화시키는 것만큼 쉬운 일이다. 



구조 Structure


GuiFactory인터페이스의 createButton()메서드는 Button타입의 객체를 반환한다. 어떤 Button의 구체적인 타입이 반환되는지는 GuiFactory의 어떤 서브 클래스를 사용하는지에 따라 달려있다.

위의 예는 간결성을 위해서 한 객체의 생성에 관해서만 보여주고 있다. 

(실제 예에서는 createButton()뿐만 아니라, createLabel(), createImageView(), createPane() 등의 메서드가 있을 수 있다.)


예 Example

어떤 타입의 팩토리를 사용하냐에 따라 출력물은 "I'm a WinButton"이나 "I'm an OSXButton"이 될 것이다. 이 애플리케이션은 어떤 종류의 GUIFactory가 사용되는지, 또 어떤 타입의 Button이 사용되는지 알지 못한다는 점에 주목하자.


Java


/* GUIFactory example -- */
 
interface GUIFactory {
    public Button createButton();
}
 
class WinFactory implements GUIFactory {
    public Button createButton() {
        return new WinButton();
    }
}
class OSXFactory implements GUIFactory {
    public Button createButton() {
        return new OSXButton();
    }
}
 
interface Button {
    public void paint();
}
 
class WinButton implements Button {
    public void paint() {
        System.out.println("I'm a WinButton");
    }
}
class OSXButton implements Button {
    public void paint() {
        System.out.println("I'm an OSXButton");
    }
}
 
class Application {
    public Application(GUIFactory factory) {
        Button button = factory.createButton();
        button.paint();
    }
}
 
public class ApplicationRunner {
    public static void main(String[] args) {
        new Application(createOsSpecificFactory());
    }
 
    public static GUIFactory createOsSpecificFactory() {
        int sys = readFromConfigFile("OS_TYPE");
        if (sys == 0) {
            return new WinFactory();
        } else {
            return new OSXFactory();
        }
    }
}



저작자 표시
신고
Posted by Code-Moon

댓글을 달아 주세요

이 글은 영문 위키피디아 페이지를 번역한 글 임을 먼저 알려드립니다.

영어 실력이 부족해서 다소 오역이 있을 수도 있습니다.

원문 주소 : http://en.wikipedia.org/wiki/Factory_method_pattern



생성 패턴 - Factory Method Pattern



팩토리 메서드 패턴은 공장의 개념을 구현하기 위한 객체지향 디자인 패턴이다. 다른 생성 패턴(creational patterns)들과 마찬가지로 이 패턴은 생성할 객체에 대한 정확한 클래스를 구분할 필요없이(알 필요없이) 객체를 생성하는 것에 대한 문제를 다룬다. 주로 객체를 생성할 때 보면, 그 객체를 결합하는(사용하는) 객체에서는 굳이 필요하지 않은 복잡한 과정이 필요하다. 객체의 생성은 심각한 코드의 중복을 야기할 수 있고, 결합하는(사용하는) 객체에서 접근할 수 없는 정보들을 요구할 수도 있으며, 충분한 추상화 단계를 제공하기 힘들 수도 있고, 결합하는(사용하는) 객체의 관심사에는 포함되지 않을 수도 있다. 팩토리 매서드 패턴은 객체를 생성할 때 별도의 메서드를 정의하고, 서브클래스에서 생성될 객체의 종류를 정할 수 있게 함으로써 이런 문제들을 해결한다.


객체의 생성에 필요한 과정들을 보면, 객체의 생명주기를 다루는 문제가 포함되고, 객체의 생성(build-up)과 파괴(tear-down)에 관련된 구체적인 문제를 다루는 것도 포함된다. 디자인 패턴의 범위 밖에서 봐도, 팩토리 매서드라는 용어 또한 팩토리(여기에서 팩토리의 주 목적은 객체 생성이다.)의 메서드를 지칭할 수도 있다. 





정의 Definition

팩토리 매서드 패턴의 진수는 "객체의 생성에 관련된 인터페이스를 정의하고, 인터페이스를 구현하는 클래스에서 어떤 클래스를 인스턴스화(생성)할 것인지 정하게 두는 것이다. 즉 팩토리 메서드는 서브클래스에서 인스턴스화를 하도록 미루는 것이다."


일반적인 사용 Common usage

팩토리 메서드는 툴킷과 프레임웍에서 일반적으로 사용된다. 라이브러리 코드에서 객체를 생성할 필요가 있는데, 그 객체는 프레임웍을 사용하는 애플리케이션에 의해 서브클래싱될 수도 있을 것이다. 

평행한 클래스 계층에서는 주로 다른 계층으로부터 적절한 객체를 생성하는 것이 가능하도록  한 계층으로부터 객체를 요구한다. 

팩토리 메서드는 TDD(test-driven development)에서 클래스를 테스트하기 위해 사용됩니다. 만약 Foo라는 클래스가 Dangerous라는 객체를 생성하는데 이 Dangerous 객체가 유닛테스트에 사용될 수 없을 수 있습니다.(Dangerous 객체가 데이터 베이스와 통신을 하는데, 이 데이터베이스가 사용 불가능할 수도 있는 경우) 이럴 때 Dangerous 객체의 생성을 Foo클래스의 가상의 팩토리 매서드인 createDangerous에 둘 수 있습니다. 테스트를 위해서 TestFoo(Foo클래스의 서브클래스)를 만들고 가상의 팩토리 메서드인 createDangerous를 오버라이드해서 FakeDangerous(말 그대로 가짜 객체입니다.)라는 객체를 반환할 수 있겠죠. 그러면 유닛 테스트에서 TestFoo를 사용해서 Foo클래스의 기능을 테스트할 수 있습니다. Foo클래스를 그대로 사용했을 때 Dangerous객체의 부작용(데이터베이스가 사용 불가능한 상황)이 생길 수 있는데 이러한 부작용없이 테스트가 가능하겠죠.


적용 가능성 Applicability

팩토리 메서드는 다음과 같은 상황에서 사용될 수 있다.

  • 한 객체의 생성이 코드의 중복없이 재사용하는 것을 못하도록 하는 상황일 때
  • 한 객체의 생성이 결합하는(사용하는) 클래스에서 필요하지 않은 정보나 리소스에 접근해야 할 때
  • 애플리케이션에서 일관성 있는 동작을 하기 위해 생성된 객체의 살아있는 시간(lifetime)의 관리가 중앙에서 관리할 필요가 있을 때

다른 이점과 변종 Other benefits and variants

팩토리 메서드 패턴의 뒤에 있는 동기는 서브클래스에게 어떤 타입의 객체를 생성할지 선택하는 것을 허락하는 것이지만, 팩토리 메서드를 사용하는데에는 다른 이점도 있다. 이런 이익의 대부분은 서브 클래싱의 의존하지 않는다. 이런 다른 이점을 위해 객체를 생성하는데에 다형성(polymorphic)을 사용하지 않고 팩토리 메서드를 정의하는 것이 일반적이고, 그러한 메서드는 종종 static 메서드이다. 


서술적인 이름 Descriptive names

팩토리 매서드는 특별한 이름을 가지고 있다. 많은 객체 지향 언어에서 생성자는 클래스 이름과 같아야 하는데, 이는 객체를 생서하는 방법이 한가지 이상일 경우에 모호성을 야기할 수 있다. 팩토리 메서드에서는 그러한 제약이 없고 서술적인 이름을 가질 수 있다. 예를 들자면, 복수(complex numbers)가 두개의 실수로 만들어 진다면, 이 두개의 실수는 카테시안 좌표계나 극 좌표계로 해석되어질 수 있다. 하지만 팩토리 매서드를 사용했을 때에는 그 의미가 확실해진다.

(아래는 자바에서의 예이다.)



class Complex {
     public static Complex fromCartesian(double real, double imaginary) {
         return new Complex(real, imaginary);
     }
 
     public static Complex fromPolar(double modulus, double angle) {
         return new Complex(modulus * cos(angle), modulus * sin(angle));
     }
 
     private Complex(double a, double b) {
         //...
     }
}
 
 Complex c = Complex.fromPolar(1, pi);

이와 같이 팩토리 메서드가 명확화를 위해 사용될때에는,  클라이언트에서 팩토리 메서드를 통해서 객체를 생성할 수 밖에 없게끔 생성자는 주로 private으로 만들어진다.


캡슐화 Encapsulation

팩토리 메서드는 객체의 생성을 캡슐화한다. 이는 생성 절차가 매우 복잡한 경우에 유용할 수 있다. 예를들면, 설정 파일이나 사용자의 입력에 따라 객체 생성 과정이 달라질때 말이다. 

이미지 파일을 읽어서 섬네일을 만드는 프로그램을 생각해보자. 그 프로그램은 각 포멧에 대해 리더 클래스(reader class)로 표현되는 다른 이미지 포맷을 지원한다.




public interface ImageReader {
    public DecodedImage getDecodedImage();
}
 
public class GifReader implements ImageReader {
    public DecodedImage getDecodedImage() {
        // ...
        return decodedImage;
    }
}
 
public class JpegReader implements ImageReader {
    public DecodedImage getDecodedImage() {
        // ...
        return decodedImage;
    }
}

이 프로그램은 매번 이미지를 읽을 때마다 파일에 있는 정보에 기반해 적절한 타입의 리더를 생생하는 과정이 필요하다. 이러한 로직은 팩토리 메서드로 캡슐화할 수 있다.



public class ImageReaderFactory {
    public static ImageReader getImageReader(InputStream is) {
        int imageType = determineImageType(is);
 
        switch (imageType) {
            case ImageReaderFactory.GIF:
                return new GifReader(is);
            case ImageReaderFactory.JPEG:
                return new JpegReader(is);
            // etc.
        }
    }
}

앞에 나온 예제에서 코드 조각(fragment)은 이미지 타입과 특정 팩토리 객체를 연결하기 위해서 스위치 문(switch)을 사용한다.  이방법을 대신해서 이러한 관계는 맵핑(mapping)으로도 구현될 수 있다. 이는 스위치 문을 associatvie array로 대체할 수도 있음을 말한다. 


구현 예 Example Implementations

Java

두가지 모드로 즐길 수 있는 미로 게임. 첫번째 모드는 보통의 방들이 인접한 방에만 연결이 되어 있는 모드이고, 두번째 모드에서는 마술방이 있는데 이 방은 플레이어를 랜덤한 위치로 이동시켜준다. 보통 게임 모드(첫번째 모드)는 템플릿 메서드를 사용해서 구현할 수 있다. 



public class MazeGame {
  public MazeGame() {
     Room room1 = makeRoom();
     Room room2 = makeRoom();
     room1.connect(room2);
     this.addRoom(room1);
     this.addRoom(room2);
  }
 
  protected Room makeRoom() {
     return new OrdinaryRoom();
  }
}


위의 코드 토막에서 MazeGame 생성자는 공통 로직을 만드는 템플릿 메서드이다. 이 생성자는 makeRoom() 팩토리 메서드를 호출하는데, 이 팩토리 메서드는 서브클래스에서 사용될 수 있는 다른 방들의 생성을 캡슐화하고 있다. 마술 방을 가지고 있는 다른 게임 모드(두번째 모드)를 구현하기 위해 makeRoom()메서드를 오버라이드 해야한다. 



public class MagicMazeGame extends MazeGame {
  @Override
  protected Room makeRoom() {
      return new MagicRoom();
  }
}


한계 Limitations

팩토리 메서드를 사용하는데에는 3가지 한계점이 있다. 첫번째는 기존 코드를 리팩토링하는 것과 관련이 있고, 나머지 두가지는 클래스를 확장하는 것과(클래스 상속) 관련이 있다. 

  • 첫 번째 한계는 팩토리를 사용하기 위해 기존 클래스를 리팩토링하는 것이 기존 클라이언트 코드를 깬다(break)는 점이다. 예를 들어, Complex클래스가 표준(standard) 클래스였다고하면, 이 클래스는 다음 코드와 같이 많은 클라이언트를 가지고 있을지도 모른다.(여기에서 클라이언트는 Complex클래스를 사용하는 클래스를 의미하는 것 같네요.)
  • Complex c = new Complex(-1, 0);
    
    일단 우리가 다른 두 개의 팩토리가 필요하다는 사실을 알게된다면, 우리는 그 클래스를 바꿀 것이다.  하지만 이제 생성자가 private이 되었기때문에 기존 클라이언트 코드는 더 이상 컴파일이 안된다.(사용할 수 없다.)

  • 두 번째 한계는 private 생성자를 사용하기 때문에 해당 클래스를 확장할 수 없다(상속받을 수 없다)는 점이다. 서브 클래스는 상속받은 생성자를 호출해야만하지만, 생성자가 private이라면 이는 불가능한 일이다.
  • 세 번째 한계는 만약 우리가 그 클래스를 확장한다면(예를들어 생성자를 protected로 만듦으로써 가능하다 - 이는 위험하지만 실현 가능하다.), 서브클래스는 부모가 가지고 있는 모든 팩토리 메서드를 완전히 같은 시그너쳐(signature, 같은 인자를 가지는 메서드를 의미하는 듯?)를 가지고 재구현해야만 한다.  예를 들어, StrangeComplex가 Complex클래스를 확장하는데 StrangeComplex 가 부모의 모든 팩토리 메서드를 재구현하지 않는다면 StrangeComplex.fromPolar(1, pi)같은 팩토리 메서드를 호출할 때 서브 클래스인 StrangeComplex의 객체가 생성되는 게 아니라, 수퍼클래스인 Complex객체가 생성될 것이다. 

이 세가지 문제들은 모두 팩토리를 최상위 클래스 멤버로 만듦으로써 완화시킬 수 있는 문제들이다. (잘 이해는 안되지만 아마 팩토리에서 생성하는 객체를 Abstract Class 또는 Virtual Class 또는 Interface로 만들면 해결된다는 것 같기도 하다.)





저작자 표시
신고
Posted by Code-Moon

댓글을 달아 주세요

티스토리 툴바