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

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

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



Behavioral pattern - Command Pattern



객체 지향 프로그래밍(object-oriented programming)에서 command pattern(이하 커맨드 패턴)은 나중에 호출할 메서드를 위한 모든 정보를 대표(represent)하고 캡슐화(encapsulate)하는 디자인 패턴(design pattern)이다.

이 정보에는 메서드 이름, 메서드를 소유하고 있는 객체 그리고 메서드 인자에 필요한 값들을 포함하고 있다.


커맨드 패턴과 항상 연관되는 세가지 조건은 클라이언트(client), 호출자(invoker), 수신자(receiver)이다. 클라이언트(client)는 커맨드 객체를 인스턴스화하고, 나중에 메서드를 호출하는데 필요한 정보들을 제공한다. 호출자(invoker)는 메서드가 언제 호출되어야하는지 결정한다. 수신자(receiver)는 메서드 코드를 포함하고 있는 클래스의 객체이다.


커맨드 객체를 사용하는 것은 한 메서드를 어떤 클래스가 가지고 있든지 메서드의 인자가 무엇인지 알 필요없이 그 메서드를 원하는 시점에 위임하거나 호출할 필요가 있는 일반적인 컴포넌트를 만들기 쉽게 해준다.

사용 Uses

커맨드 객체는 다음과 같이 구현하기에 용이하다.


다단계 취소 Multi-level undo

만약 어떤 프로그램의 모든 사용자 액션이 커맨드 객체로 구현되어 있다면, 그 프로그램은 가장 최근에 실행한 명령들에 대한 스택(stack)을 유지할 수 있다. 사용자가 어떤 명령을 취소하고 싶다면, 그 프로그램은 그냥 가장 최근의 명령을 스택에서 뽑아내고(pop) undo()메서드를 호출하면 된다.


Transactional behavior

undo와 비슷하게, 데이터베이스 엔진이나 소프트웨어 설치 프로그램은 이미 수행한 오퍼레이션의 리스트나 앞으로 수행할 오퍼레이션의 리스트를 유지할 필요가 있을지도 모른다. 만약 그 오퍼레이션 중 하나라도 실패하게 된다면 나머지 모든 오퍼레이션도 취소될 수 있다.(일반적으로 rollback이라 부른다.) 예를들면, 두개의 데이터베이스 테이블이 있고 이 둘 중 하나가 수정되면 나머지 하나도 업데이트해야하는 관계일 때, 두번째 테이블의 업데이트가 실패하면 트랜젝션이 롤백(rolled back)되고, 첫번째 테이블은 무효한 레퍼런스를 가지고 있지 않는다.


Progress bars

어떤 프로그램이 일련의 명령들을 가지고 있고, 이 명령이 차례대로 수행된다고 가정해보자. 만약 각 명령 객체가 getEstimatedDuration()메서드를 가지고 있다면, 그 프로그램은 전체 수행 시간을 쉽게 추정할 수 있을 것이다. 이를 이용해서 프로그램이 모든 태스크 중 얼마나 완료했는지를 보여줄 수 있는 프로그래스 바에 적용할 수 있다.


Wizards

종종 wizard는 사용자가 마지막 페이지에 있는 "완료"버튼을 누르면 하나의 액션이 수행되는 여러 페이지의 설정을 나타낸다. 이러한 경우에 애플리케이션의 코드로부터 사용자 인터페이스 코드를 분리하는 자연스러운 방법은 커맨드 객체를 이용해서 wizard를 구현하는 것이다. 커맨드 객체는 wizard가 처음 화면에 나타낼 때 만들어진다. 각 wizard 페이지는 GUI의 변화를 커맨드 객체에 저장해서, 이 깨체가 사용자 진행상태로 이주된다.(populate, 원래는 거주하다라는 뜻인데,, 여기에서는 이 커맨드 객체가 사용자의 진행상태를 가지고 있게 된다 정도로 이해하면 될 것 같습니다.) "완료"는 단순히 execute()를 호출하도록 한다. 이 방법으로 커맨드 클래스는 일체의 사용자 인터페이스 코드를 가지지 않게 된다.


GUI 버튼과 메뉴 항목 GUI buttons and menu items

스윙(Swing)Borland Delphi 프로그래밍에서 Action은 커맨드 객체이다. 요구되어지는 명령을 수행하는 것 외에도 Action은 관련된 아이콘, 키보드 단축키, 툴팁 글자 등을 가질 수도 있다. 툴바 버튼이나 메뉴 항목 컴포넌트는 Action 객체를 이용해서 완벽하게 초기화될 수도 있다.


쓰레드 풀 Thread Pools

전형적으로, 일반적인 목적의 쓰레드 풀 클래스는 public 메서드인 addTask()를 가지고 있을지도 모른다. 이 메서드는 내부 큐(queue)에 작업 아이템을 추가하는 메서드이다. 이는 큐에 있는 명령을 수행하는 쓰레드의 풀을 유지한다. 큐에 있는 아이템은 커맨드 객체이다. 전형적으로 이런 객체들은 java.lang.Runnable같은 공통 인터페이스를 구현하는데, 이는 쓰레드 풀이 어떤 태스크를 수행할지에 대해 전혀 알지 못해도 된다.


Macro recording

만약 모든 사용자 액션이 커맨드 객체에 의해 표현된다면, 프로그램은 커맨드 객체가 수행되는대로 그 리스트를 유지함으로써 간단하게 일련의 액션들을 저장할 수 있다. 그리고나서 같은 커맨드 객체들을 차례대로 수행함으로써 같은 액션을 "다시 재생(play back)"할 수 있다. 만약 프로그램이 스크립팅 엔진을 사용하고 있다면, 각 커맨드 객체는 toScript()메서드를 구현할 수 있고, 사용자 액션은 스크립트로써 간단히 저장될 수 있다.


네트워킹 Networking

전체 커맨드 객체를 네트워크를 통해서 다른 머신에서 수행되도록 하는 것이 가능하다. 예를들면 컴퓨터 게임에서 플레이어의 액션을 볼 수 있다.


병렬 처리 Parallel Processing

명령이 공유 자원에 태스크로 쓰여지고, 많은 쓰레드에 의해 병렬적으로 수행되는 경우( 원격 머신의 경우 - 이 변형은 종종 Master/Worker 패턴으로 언급된다.)


Mobile Code

URLClassLoader와 Codebase를 통해서 한 곳에서 다른 한 곳으로 코드를 스트리밍할 수 있는 Java같은 언어를 사용함으로써, 명령이 원격지에 전송되도록 할 수 있다.(EJB Command, Master Worker)

구조 Structure


  • 위의 Receiver에 대한 설명은 "커맨드에 의해 수행되는 실질적인 작업"이라 할 수 있다.

전문 용어 Terminology

커맨드 패턴 구현을 설명하기 위한 용어는 일관적이지 않아서 혼란스러울 수도 있다. 이는 모호함(ambituity), 유의어(synonyms)의 사용, 원래 패턴에 대한 불분명할 수 있는 구현이라는 결과로 나타난다.

  1. 모호함(Ambiguity)
    1. command라는 용어는 모호하다. 예를들어 move up, move up은 2번 수행되는 하나의 (move up) 명령일 수도 있고, 같은 일(move up)을 하는  두 개의 (다른)커맨드를 의미할 수도 있다. 만약 첫번째 경우라고 할때 명령이 undo 스택에 두번 들어가면, 스택에 있는 두 아이템은 같은 커맨드 객체를 가리킨다. 이는 어떤 명령이 항상 같은 방법으로(여기에서는 예를들면 move down) 취소된다고하면 적절할 것이다. Gang of Four와 아래의 자바 예에서는 커맨드라는 용어를 이와 같은 방법으로 이해하고 있다. 반면에 후자의 경우에 undo 스택에 커맨드를 추가하면 두개의 다른 객체가 들어가게 될 것이다. 이는 스택에 있는 각각의 객체들이 명령이 취소되는데 필요한 정보를 가지고 있는 경우에 적절할 것이다. 예를 들어, "선택 영역 삭제"라는 명령을 취소하기 위해서 그 객체는 삭제된 텍스트의 사본을 가지고 있을 수도 있다. 그래서 "선택 영역 삭제"가 취소되어야 할 때 그 텍스트가 다시 추가될 수 있는 것이다. 어떤 명령의 각 호출에 대해 다른 객체를 사용하는 것은 chain of responsibility pattern의 한 예임을 알아두어라.
    2. execute라는 용어 또한 모호하다. 이는 command 객체의 excute 메서드에 의해 특정 코드를 수행하는 것을 의미할 수도 있다. 하지만, 마이크로소프트의 WPF(Windows Presentation Foundation) 에서 커맨드는 excute메서드가 호출됐을 때 커맨드가 수행됐음을 의미하지만, 이것이 애플리케이션 코드가 수행되었음을 필연적으로 의미하는 것은 아니다. 그는 약간의 이벤트 처리 후에만 발생한다.
  2. 유의어와 동음 이의어(Synonyms and homonyms)
    1. Client, Source, Invoker : 사용자에 의한 버튼, 툴바 버튼, 메뉴 항목 클릭, 단축키가 눌러짐.
    2. Command Object, Routed Command Object, Action Object : command와 관련있는 단축키, 버튼 이미지, 커맨드 텍스트 등에 대해 알고있는 싱글톤 객체. source/invoker 객체는 Command/Action 객체의 excute/performAction 메서드를 호출한다. Command/Action 객체는 command/action의 유용성(availability)이 바뀌었을 때 적절한 source/invoker 객체에게 알려준다. 이는 command/action이 excute/perform할 수 없을 때 버튼과 메뉴 항목을 비활성화(회색으로)시킬 수 있도록 한다.
    3. Receiver, Target Object : 복사, 붙여넣기, 또는 움질일 객체. receiver 객체는 command의 excute 메서드에 의해 호출되는 메서드를 가지고 있다. receiver는 전형적으로 target 객체이기도 하다. 예를들어, receiver 객체가 cursor이고, 이 객체의 메서드가 moveUp이라면, cursor는 moveUp 액션의 target이라고 생각될 수 있을 것이다. 반면에, 만약 코드가 command 객체 자신에 의해 정의되어진다면 target 객체는 완전히 다른 객체일 것이다. 
    4. Command 객체, routed event 인자들, event 객체 : 소스(source)로부터 Command/Action 객체로, Target 객체로, 그것이 동작하는 코드로 전달되는 객체. 각 버튼 클릭이나 단축키는 새로운 command / event객체에 새로운 결과를 야기한다. 일부 구현에서는 CopyCommand같은 객체로부터 문서 영역(document section)같은 객체로 전달되기 때문에 command/event 객체에 더 많은 정보를 추가한다. 다른 구현에서는 네이밍 충돌(naming conflicts)를 피하기 위해 데이터를 한줄로 이동시키기때문에 command / event 객체를 다른 event 객체에 넣어둔다.(큰 박스 안에 작은 박스가 있는 것처럼. chain of responsibility pattern을 보라.)
    5. Handler, ExecutedRoutedEventHandler, method, function : 복사, 붙여넣기, 옮기기 등을 실제로하는 코드. 일부 구현에서 handler코드는 command/action 객체의 일부이다. 다른 구현에서는 Receiver/Target객체의 일부이고, 또 다른 구현에서 handler코드는 다른 객체로부터 분리되어진다. 
    6. Command Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker : command / event객체를 undo 스택이나 redo스택에 넣는 객체. 또는 다른 객체가 command/event객체에 대해서 어떤 액션을 취할 준비가 될때까지 command / event 객체에 유지하고 있는 객체. 또는 command / event객체를 적절한 receiver/target 객체나 handler 코드에 보내는 객체.
  3. 원래 command 패턴을 뛰어넘는 구현
    1. routed 커맨드를 도입하고 있는 마이크로소프트의 Windows Presentation Foundation (WPF)는 command 패턴과 event 처리를 합쳐놓았다. 그 결과 command 객체는 더 이상 target객체에 대한 레퍼런스 뿐만 아니라 애플리케이션 코드에 대한 레퍼런스도 가지고 있지 않다. 대신에 command 객체의 execute 메서드를 호출하는 것은 소위 말하는 Executed Routed Event가 호출되는 결과를 야기한다. 이 때 Executed Routed Event 는 event의 터널링(tunneling)또는 버블링(bubbling)을 하는 동안 binding과 직면할 수 있는데, binding은 target과 애플리케이션 코드를 확실히 해준다.



예 Example

"간단한" 스위치를 생각해보자. 이 예에서 Switch는 2개의 command를 가지고 있다. (불을 켜는 것과 불을 끄는 것)

이런 command패턴의 구현의 이점은 스위치가 어떤 기기에 상관없이 사용될 수 있다는 것이다. (불(light)뿐만 아니라 다른 것과 관련해서도 사용될 수 있다.) - 이 예에서는 불을 켜고 끄는 스위치이지만, Switch의 생성자에서는 Command의 어떤 서브클래스든지 받을 수 있다. 예를들어, 엔진에 시동을 거는 스위치를 만들 수도 있다. 


아래의 샘플 애플리케이션은 실제 전기 회로의 모델화한 것은 아니라는 점을 확실히하자. 전기적인 스위치는 벙어리다(dumb, 바보 정도로 해석해도 될 것 같네요). 실제 이진 스위치는 붙었는지 떨어졌는지 여부만 알 수 있다. 스위치는 회로에 연결되어 있을 수 있는 어떤 직접적인 부하(loads)와의 관계에 대해 알지도 못하고 가지지도 못한다. 스위치는 단순히 현재 상태에 대한 이벤트만 알릴 뿐이다. (ON 또는  OFF). 그러면 회로는 상태 엔진(State Engine)을 가지고 있어야 하는데, 이 상태엔진은 다양한 시점에서 적절한 많은 부하와 스위치가 있는 복잡한 회로를 수용하기 위해 회로의 상태를 관리한다. 그리고 각 부하는 특정 회로이 상태에 대한 조건이다. 결론을 내리자면 스위치는 어떤 램프의 자세한 내용에 대해서도 알지 못한다. 


Java


Python

class Switch(object):
    """The INVOKER class"""
    def __init__(self, flip_up_cmd, flip_down_cmd):
        self.flip_up = flip_up_cmd
        self.flip_down = flip_down_cmd
 
class Light(object):
    """The RECEIVER class"""
    def turn_on(self):
        print "The light is on"
    def turn_off(self):
        print "The light is off"
 
class LightSwitch(object):
    """The CLIENT class"""
    def __init__(self):
        lamp = Light()
        self._switch = Switch(lamp.turn_on, lamp.turn_off)
 
    def switch(self, cmd):
        cmd = cmd.strip().upper()
        if cmd == "ON":
            self._switch.flip_up()
        elif cmd == "OFF":
            self._switch.flip_down()
        else:
            print 'Argument "ON" or "OFF" is required.'
 
# Execute if this file is run as a script and not imported as a module
if __name__ == "__main__":
    light_switch = LightSwitch()
    print "Switch ON test."
    light_switch.switch("ON")
    print "Switch OFF test."
    light_switch.switch("OFF")
    print "Invalid Command test."
    light_switch.switch("****")


C#


'Design Patterns > 영문 위키(Wikipedia)' 카테고리의 다른 글

Iterator pattern  (0) 2012.06.18
Interpreter pattern  (0) 2012.06.15
Command Pattern  (0) 2012.06.15
Chain-of-responsibility pattern  (0) 2012.06.08
Proxy pattern (프록시 패턴)  (0) 2012.06.06
Flyweight pattern (플라이웨이트 패턴)  (0) 2012.06.05
Posted by Code-Moon

댓글을 달아 주세요

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

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

원문 주소 : http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern



행위 패턴 - Chain-of-responsibility




객체지향 프로그래밍(Object Oriented Design)에서 chain-of-responsibility 패턴command objects의 소스와 일련의 processing objects로 구성되는 디자인 패턴이다. 각각의 processing object는 그것이 다룰 수 있는 command objects의 타입을 정의하는 로직을 포함한다.; 나머지 것들은 체인(chain)의 다음processing object에게 전달된다. 메카니즘은 체인의 마지막에 새로운 processing objects를 추가하는 것에도 존재한다. 


기본 chain-of-responsibility 모델의 변경에서는, 일부 핸들러(handlers)가 dispatchers의 역할을 할 수도 있다. 여기에서 dispatcher는 책임의 나무(tree of responsibility)의 형태를 보이게끔 다양한 방향으로 명령들을(commands) 보낼 수 있다. 몇몇 상황에서는 이런 일이 재귀적으로 발생할 수 있는데, processing objects가 더 높은 레벨의(higher-up) processing objects를 호출하고 이 때의 commands는 문제의 더 작은 일부분을 풀기위한 시도이다.; 이런 상황에서 반복(recursion)은 command가 처리될때까지 계속되거나, 전체 트리(tree)를 다 돌았을때(explored, 탐험했을때) 끝난다. XML 인터프리터는 아마도 이러한 방법으로 동작할 것이다.


이 패턴은 programming best practice의 하나로 간주되는 느슨한 결합(lose coupling)의 아이디어에서 나온 것이다.

예 Example

아래의 코드는 로그를 기록하는 클래스를 예로 들어 이 패턴에 대해 설명한다. 각각의 로그 핸들러는 현재 로그 레벨에서 어떤 행동(action)을 취할지 결정하고 다음 로그 핸들러에게 메세지를 전달한다. 이 예는 어떻게 로그 클래스를 작성하는지에 대한 추천하는 것은 아니지 주의해라.


또한 chain of responsiblity 패턴의 순수한('pure') 구현에서 로거(logger)는 메세지를 처리하고 난 뒤에 다음 체인으로 책임을 전달하지 않을지도 모른다는 것을 알아둬라. 이 예에서는 처리가 되었든 안되었든 상관안하고 메세지는 체인으로 전달될 것이다.

Java

package chainofresp;
 
abstract class Logger {
    public static int ERR = 3;
    public static int NOTICE = 5;
    public static int DEBUG = 7;
    protected int mask;
 
    // The next element in the chain of responsibility
    protected Logger next;
 
    public Logger setNext(Logger log) {
        next = log;
        return log;
    }
 
    public void message(String msg, int priority) {
        if (priority <= mask) {
            writeMessage(msg);
        }
        if (next != null) {
            next.message(msg, priority);
        }
    }
 
    abstract protected void writeMessage(String msg);
}
 
class StdoutLogger extends Logger {
    public StdoutLogger(int mask) { 
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.out.println("Writing to stdout: " + msg);
    }
}
 
 
class EmailLogger extends Logger {
    public EmailLogger(int mask) {
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.out.println("Sending via email: " + msg);
    }
}
 
class StderrLogger extends Logger {
    public StderrLogger(int mask) {
        this.mask = mask;
    }
 
    protected void writeMessage(String msg) {
        System.err.println("Sending to stderr: " + msg);
    }
}
 
public class ChainOfResponsibilityExample {
    public static void main(String[] args) {
        // Build the chain of responsibility
        Logger logger, logger1,logger2;
        logger = new StdoutLogger(Logger.DEBUG);
        logger1 = logger.setNext(new EmailLogger(Logger.NOTICE));
        logger2 = logger1.setNext(new StderrLogger(Logger.ERR));
 
        // Handled by StdoutLogger
        logger.message("Entering function y.", Logger.DEBUG);
 
        // Handled by StdoutLogger and EmailLogger
        logger.message("Step1 completed.", Logger.NOTICE);
 
        // Handled by all three loggers
        logger.message("An error has occurred.", Logger.ERR);
    }
}
/*
The output is:
   Writing to stdout:   Entering function y.
   Writing to stdout:   Step1 completed.
   Sending via e-mail:  Step1 completed.
   Writing to stdout:   An error has occurred.
   Sending via e-mail:  An error has occurred.
   Writing to stderr:   An error has occurred.
*/


C#

using System;
using System.IO;
 
namespace ChainOfResponsibility
{
    public enum LogLevel
    {
        Info=1,
        Debug=2,
        Warning=4,
        Error=8,
        FunctionalMessage=16,
        FunctionalError=32,
        All = 63
    }
 
    /// 
    /// Abstract Handler in chain of responsibility Pattern
    /// 
    public abstract class Logger
    {
        protected LogLevel logMask;
 
        // The next Handler in the chain
        protected Logger next;
 
        public Logger(LogLevel mask)
        {
            this.logMask = mask;
        }
 
        /// 
        /// Sets the Next logger to make a list/chain of Handlers
        /// 
        public Logger SetNext(Logger nextlogger)
        {
            next = nextlogger;
            return nextlogger;
        }
 
        public void Message(string msg, LogLevel severity)
        {
            if ((severity & logMask) != 0)
            {
                WriteMessage(msg);
            }
            if (next != null)
            {
                next.Message(msg, severity);
            }
        }
 
        abstract protected void WriteMessage(string msg);
    }
 
    public class ConsoleLogger : Logger
    {
        public ConsoleLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            Console.WriteLine("Writing to console: " + msg);
        }
    }
 
    public class EmailLogger : Logger
    {
        public EmailLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            //Placeholder for mail send logic, usually the email configurations are saved in config file.
            Console.WriteLine("Sending via email: " + msg);
        }
    }
 
    class FileLogger : Logger
    {
        public FileLogger(LogLevel mask)
            : base(mask)
        { }
 
        protected override void WriteMessage(string msg)
        {
            //Placeholder for File writing logic
            Console.WriteLine("Writing to Log File: " + msg);
        }
    }
 
    public class Program
    {
        public static void Main(string[] args)
        {
            // Build the chain of responsibility
            Logger logger, logger1, logger2;
            logger1 = logger = new ConsoleLogger(LogLevel.All);
            logger1 = logger1.SetNext(new EmailLogger(LogLevel.FunctionalMessage | LogLevel.FunctionalError));
            logger2 = logger1.SetNext(new FileLogger(LogLevel.Warning | LogLevel.Error));
 
            // Handled by ConsoleLogger
            logger.Message("Entering function ProcessOrder().", LogLevel.Debug);
            logger.Message("Order record retrieved.", LogLevel.Info);
 
            // Handled by ConsoleLogger and FileLogger
            logger.Message("Customer Address details missing in Branch DataBase.", LogLevel.Warning);
            logger.Message("Customer Address details missing in Organization DataBase.", LogLevel.Error);
 
            // Handled by ConsoleLogger and EmailLogger
            logger.Message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FunctionalError);
 
            // Handled by ConsoleLogger and EmailLogger
            logger.Message("Order Dispatched.", LogLevel.FunctionalMessage);
        }
    }
}
 
/* Output
Writing to console: Entering function ProcessOrder().
Writing to console: Order record retrieved.
Writing to console: Customer Address details missing in Branch DataBase.
Writing to Log File: Customer Address details missing in Branch DataBase.
Writing to console: Customer Address details missing in Organization DataBase.
Writing to Log File: Customer Address details missing in Organization DataBase.
Writing to console: Unable to Process Order ORD1 Dated D1 For Customer C1.
Sending via email: Unable to Process Order ORD1 Dated D1 For Customer C1.
Writing to console: Order Dispatched.
Sending via email: Order Dispatched.
*/

또 다른 예 Another Example

아래는 자바로 작성한 또 다른 예이다. 이 예에서 우리는 다른 역할들을 가지고 있다. 각각의 역할들은 정해진 구매 한계치(purchasing limit)와 하나의 후계자(successor)를 가진다.(여기에서 후계자는 앞에서 나왔던 chain이라고 생각하시면 됩니다. 책임을 전달한 객체를 의미하죠.) 역할을 담당해야하는 유저는 구매 요청을 받는데 이 구매 요청이 그의 한계를 초과하는 경우에, 요청은 그의 후계자로 전달된다.


processRequest 추상 메서드를 가지고 있는 PurchasePower 추상 클래스

abstract class PurchasePower {
    protected final double base = 500;
    protected PurchasePower successor;
 
    public void setSuccessor(PurchasePower successor) {
        this.successor = successor;
    }
 
    abstract public void processRequest(PurchaseRequest request);
}


위의 추상 클래스를 상속받는 4개의 구현들 : Manager, Director, Vice President, President

class ManagerPPower extends PurchasePower {
    private final double ALLOWABLE = 10 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Manager will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class DirectorPPower extends PurchasePower {
    private final double ALLOWABLE = 20 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Director will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class VicePresidentPPower extends PurchasePower {
    private final double ALLOWABLE = 40 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("Vice President will approve $" + request.getAmount());
        } else if (successor != null) {
            successor.processRequest(request);
        }
    }
}
 
class PresidentPPower extends PurchasePower {
    private final double ALLOWABLE = 60 * base;
 
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < ALLOWABLE) {
            System.out.println("President will approve $" + request.getAmount());
        } else {
            System.out.println( "Your request for $" + request.getAmount() + " needs a board meeting!");
        }
    }
}


아래의 코드는 이 예에서 요청 데이터를 유지하는 PurchaseRequest 클래스를 정의한다.

class PurchaseRequest {
    private int number;
    private double amount;
    private String purpose;
 
    public PurchaseRequest(int number, double amount, String purpose) {
        this.number = number;
        this.amount = amount;
        this.purpose = purpose;
    }
 
    public double getAmount() {
        return amount;
    }
    public void setAmount(double amt)  {
        amount = amt;
    }
 
    public String getPurpose() {
        return purpose;
    }
    public void setPurpose(String reason) {
        purpose = reason;
    }
 
    public int getNumber(){
        return number;
    }
    public void setNumber(int num) {
        number = num;
    }   
}


아래의 사용 예에서 후계자들(successors)은 뒤에 나오는 순서대로이다. : Manager->Director->Vice President->President

class CheckAuthority {
    public static void main(String[] args) {
        ManagerPPower manager = new ManagerPPower();
        DirectorPPower director = new DirectorPPower();
        VicePresidentPPower vp = new VicePresidentPPower();
        PresidentPPower president = new PresidentPPower();
        manager.setSuccessor(director);
        director.setSuccessor(vp);
        vp.setSuccessor(president);
 
        // Press Ctrl+C to end.
        try {
            while (true) {
                System.out.println("Enter the amount to check who should approve your expenditure.");
                System.out.print(">");
                double d = Double.parseDouble(new BufferedReader(new InputStreamReader(System.in)).readLine());
                manager.processRequest(new PurchaseRequest(0, d, "General"));
           }
        } catch(Exception e) {
            System.exit(1);
        }  
    }
}


'Design Patterns > 영문 위키(Wikipedia)' 카테고리의 다른 글

Interpreter pattern  (0) 2012.06.15
Command Pattern  (0) 2012.06.15
Chain-of-responsibility pattern  (0) 2012.06.08
Proxy pattern (프록시 패턴)  (0) 2012.06.06
Flyweight pattern (플라이웨이트 패턴)  (0) 2012.06.05
Facade pattern (퍼사드 패턴)  (0) 2012.06.04
Posted by Code-Moon

댓글을 달아 주세요

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

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

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



구조적 패턴 - 프록시 패턴



컴퓨터 프로그래밍(computer programming)에서 프록시 패턴은 소프트웨어 디자인 패턴(software design pattern) 중 하나이다. 


가장 일반적인 형태의 프록시는 뭔가 다른 것의 인터페이스로 동작하는 클래스의 형태이다. 프록시는 어떤 인터페이스든지 가능하다. : 네트웍 연결, 메모리의 큰 객체, 파일, 또는 비용이 많이 들거나 복제하기 거의 불가능한 자원.


프록시 패턴의 잘 알려진 예는 참조 카운팅 포인터(reference counting pointer)객체이다. 


복잡한 객체를 여러개 복사해야하는 상황이 있다면, 프록시 패턴은 애플리케이션의 메모리 공간을 줄이기 위해 flyweight pattern에 맞춰질 수도 있다. 일반적으로 복잡한 객체 하나와 여러개의 프록시 객체들이 만들어지면, 이들 모두는 하나의 원본 복합 객체에 대한 참조를 포함한다 .프록시에서 수행되는 어떠한 동작(operations)들도 원본 객체로 연결되어진다.(forwared.. 포워드한다는 말인데, 원본 객체에게 위임한다는 표현이 더 어울릴 수도 있겠네요.) 일단 프록시의 모든 객체들이 영역 밖이라면, 복합 객체의 메모리는 해제될지도 모른다.




예 Example

아래의 자바 예는 가상 프록시(virtual proxy) 패턴을 보여주고 있다. ProxyImage 클래스는 원격 메서드에 접근하기 위해서 사용된다.

interface Image {
    void displayImage();
}
 
// on System A 
class RealImage implements Image {
    private String filename;
 
    public RealImage(String filename) { 
        this.filename = filename;
        loadImageFromDisk();
    }
 
    private void loadImageFromDisk() {
        System.out.println("Loading   " + filename);
    }
 
    public void displayImage() { 
        System.out.println("Displaying " + filename); 
    }
 
}
 
//on System B 
class ProxyImage implements Image {
    private String filename;
    private RealImage image;
 
    public ProxyImage(String filename) { 
        this.filename = filename; 
    }
 
    public void displayImage() {
        if (image == null) {
           image = new RealImage(filename);
        } 
        image.displayImage();
    }
}
 
class ProxyExample  {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");     
 
        image1.displayImage(); // loading necessary
        image1.displayImage(); // loading unnecessary
        image2.displayImage(); // loading necessary
        image2.displayImage(); // loading unnecessary
        image1.displayImage(); // loading unnecessary
    }
}

프로그램의 결과는 아래와 같다.

Loading   HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Displaying HiRes_10MB_Photo1
Loading   HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo2
Displaying HiRes_10MB_Photo1


Posted by Code-Moon

댓글을 달아 주세요

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

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

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



구조적 패턴 - 플라이웨이트 패턴



컴퓨터 프로그래밍에서 플라이웨이트는 소프트웨어 디자인 패턴 중 하나이다. 플라이웨이트는 가능한 많은 데이터는 다른 유사한 객체들과 공유함으로써 메모리 사용을 최소화하는 객체이다. ; 이는 어떤 단순 반복되는 표현(representation)이 받아들일 수 없을 정도의 메모리를 사용할 때 객체를 수없이 많이 사용하는 방법이다. 종종 객체 상태의 일부분은 공유될 수 있고, 이를 외부 자료구조(data structures)에 두고, 사용될 때에 플라이웨이트 객체에 전달하는 방법이 일반적이다.




플라이웨이트 패턴의 고전적인 사용예는 워드 프로세서(word processor)에서 문자들의 그래픽적 표현에 대한 자료구조이다. 한 문서, 폰트의 외곽선을 포함하는 글자모양(glyph) 객체(그냥 글자의 모양을 표현하는 외곽선이라 생각하면 쉬울 듯 합니다. ), 폰트 매트릭스, 그리고 다른 포맷 데이터 각각에 각 문자들을 가지고 있는 것이 바람직할지도 모르지만, 이렇게 하면 한 문자당 수백 또는 수천 바이트가 필요할지도 모른다. 대신에, 모든 문자들이 그 문서 안의 같은 문자 객체에 의해 공유되는 플라이웨이트 글자모양 객체에 대한 레퍼런스(reference)가 될 수 있다.; 오직 각 문자의 위치정보만이 내부적으로 저장될 필요가 있을 것이다.


다른 문맥에서 동일한 자료 구조를 공유하는 아이디어는 hash consing이라 불린다.

역사 History

Designe Patterns: Elements of Reusable Object-Oriented Software 책에 따르면, 플라이웨이트 패턴은 1990년에 Paul Calder와 Mark Linton이 WYSIWYG 문서 편집기의 글자모양 정보를 효율적으로 다루기 위해 처음 도입되고 널리 연구되어졌다. 비록 비슷한 기술이 다른 시스템에서 이미 사용되고 있었지만 말이다.(1988년에 Weinand 등의 한 애플리케이션 프레임웍에서..)

자바 예 Example in Java

Posted by Code-Moon

댓글을 달아 주세요

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

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

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



구조적 패턴 - 콤포짓 패턴



소프트웨어 공학에서 콤포짓 패턴은 분할하는(partitioning) 디자인 패턴이다. (왜 partitioning이라는 단어가 나왔는지 잘 이해가 안되네요.;;) 콤포짓 패턴은 객체 그룹이 단일 객체와 같이 취급되도록 한다. 컴포짓의 목적(의도, intent)은 객체들을 트리(tree) 구조로 "구성(compose)"하여 부분-전체 구조(part-whole hierarchies)를 표현하기 위함이다. 콤포짓 패턴을 구현하는 것은 단일 객체(individual objects)와 구성(composition, 여기에서는 그룹 정도로 이해해도 괜찮을 것 같네요.)을 동일하게 취급하도록 해준다.

동기 Motivation

트리 구조의 데이터를 다룰 때, 프로그래머는 종종 리프노드(leaf-node)와 브랜치(branch)를 구분해야만 한다. 이는 코드를 더욱 복잡하게 만들고 따라서 에러가 발생하기도 쉽다. 해결책은 복합객체(complex)와 원시객체(primitive)를 동일하게 다루는 인터페이스를 사용하는 것이다. 객체 지향 프로그래밍에서 컴포짓은 하나 이상의 비슷한 객체들을 가지고 하나의 구성(composition)으로 설계된 객체이다. 이는 객체 사이의 "has-a"관계로 알려져있다. 중요한 컨셉은 객체의 한 인스턴스를 다룰 때, 그 객체들의 그룹을 다루는 것처럼 다룰 수 있다는 것이다. 모든 컴포짓 객체들에서 당신이 수행할 수 있는 동작들(operations)은 종종 최소 공통 분모(least common denominator) 관계에 있다. 예를 들어, 그룹핑된(grouped) 도형(shapes)들을 화면에 그리는 시스템을 정의한다고 하면, 도형들의 그룹을 리사이징(resizing)하는 것이 하나의 도형을 리사이징하는 것과 같은 효과를 가지도록 정의하는 것이 유용할 것이다. 

언제사용하는가? When to use

컴포짓은 클라이언트 프로그램이 객체들의 구성(composition)과 개개의 객체 사이에 차이점을 무시해야할 때 사용될 수 있다. 만약 프로그래머가 여러개의 객체들을 같은 방법으로 사용하거나, 종종 그 객체 각각을 다루기 위해 거의 비슷한 코드를 사용하고 있는 자신의 모습을 발견한다면, 컴포짓이 좋은 선택이 될 수 있다. ; 이러한 상황에서 원시(primitive)객체와 구성 객체를 동일하게 다루는 것이 덜 복잡하다. 

구조 Structure

Component

  • 컴포넌트 자신을 포함한 모든 컴포넌트에 대한 추상화이다.
  • 구성(composition)에 있는 객체들의 인터페이스를 정의한다.
  • (선택적으로) component의 부모에 대한 접근하기 위한 재귀적인 구조(recursive structure)의 인터페이스를 정의하고, 적절하다면 구현한다.

Leaf

  • 구성(composition)에 있는 leaf 객체들을 표현한다.
  • component의 모든 메서드를 구현한다.

Composite

  • composite 컴포넌트를 표현한다.(자식들을 가지는 컴포넌트)
  • 자식들을 다루기 위한 메서드를 구현한다.
  • 모든 component 메서드를 구현한다.(일반적으로 메서드 기능을 자식에게 위임해서 구현한다. 여기에서 위임이라함은 자식의 기능을 호출한다고 보면 될 것 같네요.)

변형 Variation

Design Patterns에 묘사되어 있듯이,  이 패턴에는 Composite 클래스 뿐만 아니라 메인 Component 인터페이스에도 자식을 관리하는 메서드를 포함하고 있다. 더 최근의 몇몇 설명에서는 이러한 메서드를 누락시키고 있다. 

예 Example

자바로 작성된 다음의 예제는 Graphic 클래스를 구현하는데, 이 클래스는 Ellipse(타원)이나 다른 몇몇 그래픽의 구성(composition)이 될 수 있다. 모든 그래픽은 출력될 수 있다.


수학적으로 표현하면 다음과 같다.

Graphic = ellipse | GraphicList
GraphicList = empty | Graphic GraphicList


이는 직사각형과 같은 다른 도형들과 translate와 같은 다른 메서드들을 구현하게 끔 확장될 수도 있다.


Posted by Code-Moon

댓글을 달아 주세요

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

영어 실력이 부족한 관계로 오역이 있을 수도 있으니 이해해주세요^^;

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



생성 패턴 - 싱글톤



소프트웨어 공학에서 싱글톤 패턴은 클래스의 인스턴스화를 하나의 객체로 제한을 둠으로써 싱글톤의 수학적 컨셉(수학에 싱글톤이라는게 있나 보군요. 참고 : http://en.wikipedia.org/wiki/Singleton_(mathematics))을 구현한 디자인 패턴이다. 이 패턴은  시스템에 걸쳐 딱 하나의 객체가 필요할 때에 유용하다. 


이 컨셉은 다음과 같은 시스템에서 일반적으로 사용된다. 

1. 오직 하나의 객체가 존재하는 것이 더 효과적일 때

2. 일정 개수의 객체만 생성하도록 제한을 둬야할 때


싱글톤 패턴을 사용함에 있어서 비판적인 시각도 있는데, 일부에서는 싱글톤이 남용되고 있거나 굳이 하나의 유일한 객체가 필요한 상황이 아닌 곳에서도 불필요하게 도입될 수도 있고, 애플리케이션 안에서 전역 상태(global state)를 도입한다고 판단하기 때문에 안티패턴(자주 사용되기는 하나 효과적이지 못하거나 부작용이 낳는 패턴)으로 간주하기도한다. 


C++에서는 싱글톤이 프로그래머에게 제어를 넘김으로써, 동적 초기화의 순서에 대한 예측 불가능으로부터 분리시킬 수 있다. (정확히는 모르겠지만, 의미를 추측해보면 c++에서는 전역 변수를 사용할 때 어떤 변수가 먼저 초기화될지 순서를 예측하기 힘들 수 있는데, 싱글톤을 이용하면 명시적으로 그 초기화 순서를 정할 수 있다는 의미인 것 같습니다.)


일반적인 사용 Common Uses

  • 추상 팩토리, 빌더, 프로토타입 패턴을 구현할 때에 싱글톤을 사용할 수 있다.
  • 오직 하나의 퍼사드 객체만이 필요하기 때문에, 퍼사드(Facade) 객체는 종종 싱글톤이다.
  • 상태 객체(State Object, 스테이트 패턴 객체를 의미하는 듯 합니다.)는 종종 싱글톤이다.
  • 싱글톤은 종종 아래와 같은 이유때문에 전역 변수보다 선호된다.
    • 싱글톤은 불필요한 변수들로 전역 네임스페이스를 오염시키지 않는다. 
    • 많은 언어에서 전역 변수가 항상 자원(메모리)을 소비하는데에 비해, 싱글톤은 게으른 할당과 초기화를 지원한다.


구조 Structure


구현 Implementation

싱글톤 패턴의 구현은 하나의 인스턴스와 전역 접근 원칙을 만족해야만한다. 이는 클래스 객체를 생성하지 않고 싱글톤 클래스 멤버에 접근할 수 있는 메카니즘과 클래스 객체들 사이에서 클래스 멤버의 값을 유지하는 메커니즘을 필요로 한다. 싱글톤 패턴은 객체가 없을 때에 새로운 클래스 객체를 생성하는 메서드를 가지는 클래스를 만듦으로써 구현할 수 있다. 만약 객체가 이미 존재한다면 해당 메서드에서는 이미 생성된 객체에 대한 레퍼런스를 반환한다. 객체가 다른 방법으로 생성되지 않도록 하기 위해서 생성자는 private으로 한다. 


간단한 스태틱 객체와 싱글톤의 차이에 주목하라. : 싱글톤은 스태틱 객체로 구현되기는 하지만, 늦게 생성될 수 있고 필요하기 전까지는 메모리나 자원을 필요로하지 않는다. 또다른 주목할만한 차이점은 인터페이스가 단순히 마커(marker)가 아닐 경우에, 스태틱 멤버 클래스는 그 인터페이스를 구현할 수 없다는 점이다. 따라서 클래스가 어떤 인터페이스를 구현하기 위해서는 스태틱 멤버 클래스가 아닌 싱글톤으로 구현해야 한다. (두번째 차이는 이해가 잘 안되네요;; 스태틱 멤버 클래스도 사용하는 부분에서만 스태틱으로 선언해서 사용하는 것이지 정의 부분은 일반 클래스랑 다를게 없는데, 왜 인터페이스를 구현하는 것이 안된다는지....?)


싱글톤 패턴은 멀티 스레드 기반의 애플리케이션에서 조심스럽게 사용해야 한다. 만약 싱글톤 객체가 아직 생성되지 않은 시점에서 두개의 스레드가 동시에 싱글톤의 생성 메서드를 호출하면, 두 스레드에서 동시에 싱글톤 객체의 생성여부를 체크할 것이고 이때 단 하나의 객체만 생성하도록 해야한다. 만약 프로그래밍 언어에서 동시(concurrent) 처리 능력(capabilities)을 가지고 있다면 그 메서드(싱글톤 객체를 체크하고 생성하는 메서드)는 상호 보완(mutually exclusive)적으로 만들어져야만 한다.(번역을 하니 말이 조금 어려운데요, 쉽게 풀자면 프로그래밍 언어에서 뮤텍스나 세마포어 같은 기능을 지원할 경우에 싱글톤의 객체 생성 메서드의 원자성을 보장해야한다는 의미입니다.)


이 문제에 대한 고전적인 해결방법은 그 클래스에 상호배제(mutual exclusion)를 사용해서 객체가 생성 중임을 알리는 것이다. (싱글톤 객체 생성 메서드에서 세마포어 등을 이용해서 두 개 이상의 스레드가 동시 접근이 불가능하도록 하라는 의미입니다.)


예 Example

여기에서 보여주는 자바 솔루션은 모두 스레드 세이프(thread-safe, 멀티 스레드 환경에서 상호배제를 보장해 준다는 것을 의미)하도록 만들었다. 자바 5.0부터는 싱글톤을 생성하는 가장 쉬운 방법은 enum 타입 방식인데 이 색션의 가장 마지막에 보여줄 것이다. 


게으른 초기화 Lazy initialization


public class Singleton {
        private static Singleton _instance = null;
 
        private Singleton() {   }
 
        public static synchronized Singleton getInstance() {
                if (_instance == null) {
                        _instance = new Singleton();
                }
                return _instance;
        }
}


전통적인 간단한 방법 Traditional simple way

이 방법은 언어 상의 특별한 구조가 필요없이 스레드 세이프한 방법이지만 위에서 보여준 게이른 초기화를 하지 않는 방법이다. 싱글톤 클래스가 초기화될 때 그 객치(instance)또한 생성된다. 이는 getInstance()를 호출하기 훨~씬 전일 수도 있다. 예를들어 싱글톤 클래스의 스태틱 메서드가 호출될 때 싱글톤 객체가 생성될 수 있다. 게으른 초기화가 필요없거나, 애플리케이션이 실행된 초기에 싱글톤 객체가 생성될 필요가 있거나, 당신의 싱글톤 클래스에 다른 스태틱 멤버나 스태틱 메서드가 없다면(스태틱 멤버나 메서드가 없다는 것은 유일한 스태틱 메서드인 getInstance()가 호춭될 때 싱글톤 객체가 생성됨을 의미한다.), 이 간단한 방법이 사용될 수 있을 것이다.


public class Singleton {
        private static final Singleton instance = new Singleton();
 
        // Private constructor prevents instantiation from other classes
        private Singleton() { }
 
        public static Singleton getInstance() {
                return instance;
        }
}


스태틱 블럭 초기화 Static block initialization

몇몇 저자들은 전처리(pre-processing)를 하는 비슷한 방법을 말한다. 이 방법에서 보면 전통적인 방법은 이 방법의 특별한 케이스로 볼 수 있다. 

public class Singleton {
  private static final Singleton instance;
 
  static {
    try {
      instance = new Singleton();
    } catch (IOException e) {
      throw new RuntimeException("Darn, an error's occurred!", e);
    }
  }
 
  public static Singleton getInstance() {
    return instance;
  }
 
  private Singleton() {
    // ...
  }
}


빌 퍼그의 방법 The solution of Bill Pugh

Maryland 대학의 컴퓨터 공학 연구자인 Bill Pugh는 자바에서 구현될 때 싱글톤 패턴의 코드 이슈에 관해 글을 썼었다. "Double-checked locking" 에 들어있는 Pugh의 노력은 자바5의 메모리 모델의 변화를 야기했고, 자바에서 싱글톤을 구현하는 표준 방법으로 간주됐다. initialization on demand holder idiom으로 알려진 이 기술은 가능한 게으르게 하고, 모든 자바 버전에서 동작한다.  이 방법은 클래스 초기화에 대한 언어의 보증(guarantees)을 이용하고, 따라서 모든 자바를 따르는 컴파일러나 가상머신에서 잘 동작한다.


중첩된 클래스는 getInstance()가 호출되기 전까지는 참조되지 않는다. 따라서 이 방법은 특별한 언어의 구조가 필요없는(예를들면 volatile이나 synchronized) 스레드 세이프(thread-safe)한 방법이다.


public class Singleton {
        // Private constructor prevents instantiation from other classes
        private Singleton() { }
 
        /**
        * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
        * or the first access to SingletonHolder.INSTANCE, not before.
        */
        private static class SingletonHolder { 
                public static final Singleton instance = new Singleton();
        }
 
        public static Singleton getInstance() {
                return SingletonHolder.instance;
        }
}

내부 클래스인 Singletonholder는 static final이나 읽기만 가능한 클래스 멤버에 접근 가능한 프로퍼티(Property)를 구현함으로써 대체될 수 있다. C#에 있는 게으른(lazy) 객체처럼 Singleton.Instance 프로퍼티가 호출될 때마다, 이 싱글톤은 제일 처음 인스턴스화된다.


Enum을 이용한 방법 The Enum way

Joshua Bloch는 그의 책 "Effective Java"2판에서 enums을 지원하는 어떤 자바 버전이든 상관없이 "유일한 enum 타입은 싱글톤을 구현하는데 가장 좋은 방법이다.(a single-element enum type is the best way to implement singleton)"라고 언급했다. enum의 사용은 구현하기 매우 쉬운 방법이고, 직렬화 가능한 객체(serializable objects)와 관련된 문제가 없다.(이러한 문제가 있다면 다른 방법으로 우회해서 구현해야한다.)


public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //... perform operation here ...
        }
}

public메서드는 필요한 타입의 인자를 사용하는데 쓰일 수 있다. ; 여기에서는 String 인자가 사용됐다.

이 접근법은 자바의 enum 값이 자바 프로그램 내에서 오직 한번만 인스턴스화 되는 점을 이용함으로써 싱글톤을 구현한다. 자바의 enum 값은 전역적으로 접근 가능하기 때문에, 이는 싱글톤이다. 이 방법의 문제점은 enum 타입이 유연성이 떨어질 수 있다는 점이다. ; 예를들면 늦은 초기화를 지원하지 못한다는 점이다.


프로토타입에 기반한 싱글톤 Prototype-based singleton

클래스 대신에 객체가 사용되는 프로토타입 기반 프로그래밍(prototype-based programming) 언어에서 "싱글톤"은 단순히 복제되지 않거나 다른 객체의 프로토타입으로 사용되지 않는 것을 의미한다. 


Foo := Object clone
Foo clone := Foo


팩토리 메서드 패턴과 함께 사용되는 예 Example of use with the factory method pattern

싱글톤 패턴은 구체적인 타입을 사용하는 부분에서 모르는 시스템 전역 자원을 생성하기 위해 종종 팩토리 메서드 패턴과의 조합으로 사용되고는 한다. 이 두 패턴을 사용하는 한 예는 자바의 Abstract Window Toolkit(AWT)이다.

java.awt.Toolkit은 다양한 AWT 컴포넌트를 특정 네이티브 툴킷 구현으로 바인딩시키는(bind) 추상클래스이다. Toolkit클래스는 Toolkit.getDefaultToolkit() 팩토리 메서드를 가지고 있는데, 이 메서드는 플랫폼에 특화된 Toolkit의 서브클래스를 리턴한다. AWT가 바인딩을 위한 오직 하나의 객체만 필요로 하고, 이 유일한 객체는 상대적으로 생성 비용이 크게 들기 때문에, Toolkit 객체는 싱글톤으로 한다. toolkit 메서드는 한 객체에서 구현되어야 하고, 클래스의 static 메서드로 구현되어서는 안되는데 그 이유는 구체적인 구현은 플랫폼 독립적인 컴포넌트에게 알려져 있지 않기 때문이다. Toolkit클래스의 서브클래스 이름은 System.getProperties()를 통해 얻어지는 "awt.toolkit" 환경 속성으로 정해진다.


예를들어, toolkit에 의해 수행되는 바인딩은 java.awt.Window를 플랫폼 특화된 java.awt.peer.WindowPeer 구현으로 바인딩하는 지원 구현(backing implementation)을 허락한다. Window클래스와 window를 사용하는 애플리케이션도 peer의 어떤 플랫폼 특화된 서브클래스가 사용되는지 알 필요가 없다.


문제점 Drawbacks

이 패턴은 전역 상태(global state)를 애플리케이션에 도입함으로써, 유닛 테스트를 더 어렵게 만든다. 또한 멀티 스레드에서 싱글톤에 접근하기 위해서는 직렬화되어야(serialised) 하기 때문에(예를들면 lock을 걸어서) 프로그램에서 동시성 가능성도 낮춘다는 것을 잘 알아야 한다. dependency injection의 대변자들은 이 패턴이 private과 static메서들르 사용하기 때문에 안티패턴으로 간주한다. 일부에서는 자바나 PHP같은 언어에서 리플렉션(reflection)을 이용해서 싱글톤 패턴을 부수는 방법을 제안하기도 했다. 

Posted by Code-Moon

댓글을 달아 주세요