ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL_200919(GoF pattern)
    Today I Learned 2020. 9. 18. 18:23

    개발자는 유지 관리 가능하고 읽기 쉬우며 재사용 가능한 코드를 작성하려고 노력합니다.

    애플리케이션이 커짐에 따라 코드 구조화가 더욱 중요해 집니다.

    디자인 패턴은 특정 상황에서 일반적인 문제에 대한 조직 구조를 제공하여 이러한 문제를 해결하는 데 매우 중요합니다.

     

    Gang of Four Design Patterns

    1. Creational Design Patterns - 객체의 생성 과정을 제어하여 문제를 해결합니다.

    • Constructor Pattern
    더보기

    클래스 기반 디자인 패턴입니다. 사실, 패턴이라기보다 기본 언어 구조에 가깝습니다. 그러나 자바스크립트에서는 객체를 생성자 함수나 클래스 없이 만들 수 있습니다.

    다음 예제는 메서드를 가진 클래스를 정의하고 생성자 메서드를 호출하여 객체를 인스턴스화 합니다. 

    function Hero(name, specialAbility) {
      // setting property values
      this.name = name;
      this.specialAbility = specialAbility;
    
      // declaring a method on the object
      this.getDetails = function() {
        return this.name + ' can ' + this.specialAbility;
      };
    }
    
    // ES6 Class syntax
    class Hero {
      constructor(name, specialAbility) {
        // setting property values
        this._name = name;
        this._specialAbility = specialAbility;
    
        // declaring a method on the object
        this.getDetails = function() {
          return `${this._name} can ${this._specialAbility}`;
        };
      }
    }
    
    // Hero의 새 인스턴스 생성
    const  IronMan  =  new  Hero ( 'Iron Man' ,  'fly' ) ;
    • Factory Pattern
    더보기

    팩토리 패턴은 클래스 기반 생성 패턴입니다.

    객체 인스턴스화의 책임을 하위 클래스에 위임하는 일반 인터페이스를 제공합니다.

    이 패턴은 유사한 특성이 많은 객체 컬렉션을 관리하거나 조작해야 할 때 자주 사용됩니다.

     

    다음 예제에서 BallFactory 라는 팩토리 클래스에는 매개 변수를 받는 메서드가 있습니다.

    이 메서드는 매개 변수에 따라서 각 클래스에 책임을 위임합니다.

    class BallFactory {
      constructor() {
        this.createBall = function(type) {
          let ball;
          // 매개변수에 따라서 위임받는 클래스가 다르다.
          if (type === 'football' || type === 'soccer') ball = new Football();
          else if (type === 'basketball') ball = new Basketball();
          // 위임받은 클래스에 메서드 할당
          ball.roll = function() {
            return `The ${this._type} is rolling.`;
          };
    
          return ball;
        };
      }
    }
    
    class Football {
      constructor() {
        this._type = 'football';
        this.kick = function() {
          return 'You kicked the football.';
        };
      }
    }
    
    class Basketball {
      constructor() {
        this._type = 'basketball';
        this.bounce = function() {
          return 'You bounced the basketball.';
        };
      }
    }
    
    // 객체 생성
    const factory = new BallFactory();
    
    const myFootball = factory.createBall('football');
    const myBasketball = factory.createBall('basketball');
    
    // Football 클래스
    console.log(myFootball.roll()); // The football is rolling.
    console.log(myFootball.kick()); // You kicked the football.
    
    // Basketball 클래스
    console.log(myBasketball.roll()); // The basketball is rolling.
    console.log(myBasketball.bounce()); // You bounced the basketball.
    • Prototype Pattern
    더보기

    프로토 타입 패턴은 객체 기반 디자인 패턴입니다.

    기존 객체의 골격을 사용하여 새 객체를 만들거나 인스턴스화합니다.

    이 패턴은 객체 지향 상속 대신에 프로토 타입 상속을 사용하기 때문에 자바 스크립트에 특히 중요합니다.

    자바 스크립트의 강점을 발휘합니다.

    // ES5 표준에 따른 Object.create를 사용하는 방법입니다.
    const car = {
      noOfWheels: 4,
      start() {
        return 'started';
      },
      stop() {
        return 'stopped';
      },
    };
    
    // Object.create(proto[, propertiesObject])
    
    const myCar = Object.create(car, { owner: { value: 'John' } });
    
    console.log(myCar.__proto__ === car); // true
    var moleGame = function() {
      this.numMoles    = 4;
      this.delayTime   = 200;
      this.score       = 10;
    }
    
    TeslaModelS.prototype.start = function() {
      // 게임시작
    }
    
    TeslaModelS.prototype.catch = function() {
      // 두더지 잡음
    }
    var moleGame = function() {
      this.numMoles    = 4;
      this.delayTime   = 200;
      this.score       = 10;
    }
    
    TeslaModelS.prototype = function() {
    
      var start = function() {
        // 게임시작
      };
    
      var catch = function() {
        // 두더지 잡음
      };
    
      return {
        pressButton: start,
        pressMole: catch
      }
    
    }();
    • Singleton Pattern
    더보기

    클래스의 인스턴스가 하나만 존재할 수 있는 디자인 패턴입니다.

    이는 다음과 같이 작동합니다. 

    Singleton 클래스의 인스턴스가 없으면 새로운 인스턴스가 생성되고 반환되지만 인스턴스가 이미 존재하면 기존 인스턴스에 대한 참조가 반환됩니다.

    class Database {
      constructor(data) {
      	// 존재확인
        if (Database.exists) {
          return Database.instance;
        }
        this._data = data;
        Database.instance = this;
        Database.exists = true;
        return this;
      }
    
      getData() {
        return this._data;
      }
    
      setData(data) {
        this._data = data;
      }
    }
    
    // 사용법
    const mongo = new Database('mongo');
    console.log(mongo.getData()); // mongo
    
    const mysql = new Database('mysql');
    console.log(mysql.getData()); // mongo

     

    2. Structural Design Patterns - 전체 시스템에 영향을 주지 않고 하나 이상의 부품을 구조화, 재구성합니다.

    • Adapter Pattern
    더보기

    한 클래스의 인터페이스가 다른 클래스로 변환되는 구조적 패턴입니다.

    호환되지 않는 인터페이스로 인해 다른 방법으로는 불가능했던 클래스가 동작할 수 있습니다.

    이 패턴은 새로 리팩토링 된 API에 대한 래퍼를 만드는 데 자주 사용되어 기존의 다른 API를 계속 작동시킵니다.

     

     다음 예시를 보면 NewCalculator에 operation 메서드를 추가하기 위해 CalcAdapter가 사용되었습니다.

    // old interface
    class OldCalculator {
      constructor() {
        this.operations = function(term1, term2, operation) {
          switch (operation) {
            case 'add':
              return term1 + term2;
            case 'sub':
              return term1 - term2;
            default:
              return NaN;
          }
        };
      }
    }
    
    // new interface
    class NewCalculator {
      constructor() {
        this.add = function(term1, term2) {
          return term1 + term2;
        };
        this.sub = function(term1, term2) {
          return term1 - term2;
        };
      }
    }
    
    // Adapter Class
    class CalcAdapter {
      constructor() {
        const newCalc = new NewCalculator();
    
        this.operations = function(term1, term2, operation) {
          switch (operation) {
            case 'add':
              return newCalc.add(term1, term2);
            case 'sub':
              return newCalc.sub(term1, term2);
            default:
              return NaN;
          }
        };
      }
    }
    
    // 사용법
    const oldCalc = new OldCalculator();
    console.log(oldCalc.operations(10, 5, 'add')); // 15
    
    const newCalc = new NewCalculator();
    console.log(newCalc.add(10, 5)); // 15
    
    const adaptedCalc = new CalcAdapter();
    console.log(adaptedCalc.operations(10, 5, 'add')); // 15;
    • Composite Pattern
    더보기

    이 패턴은 전체 부분 계층을 표현하기 위해 객체를 트리같은 구조로 구성하는 구조적 디자인 패턴입니다.

    각 노드는 고유하거나 하위로 여러 옵션이 있을 수 있습니다.

    자식이 있는 노드 구성 요소는 복함 구성 요소이고 자식이 없는 노드 구성 요소는 리프 구성 요소입니다.

    다단계 메뉴의 예를 들 수 있습니다.

     

    class Component {
      constructor(name) {
        this._name = name;
      }
    
      getNodeName() {
        return this._name;
      }
    
      // abstract methods that need to be overridden
      getType() {}
    
      addChild(component) {}
    
      removeChildByName(componentName) {}
    
      removeChildByIndex(index) {}
    
      getChildByName(componentName) {}
    
      getChildByIndex(index) {}
    
      noOfChildren() {}
    
      static logTreeStructure(root) {
        let treeStructure = '';
        function traverse(node, indent = 0) {
          treeStructure += `${'--'.repeat(indent)}${node.getNodeName()}\n`;
          indent++;
          for (let i = 0, length = node.noOfChildren(); i < length; i++) {
            traverse(node.getChildByIndex(i), indent);
          }
        }
    
        traverse(root);
        return treeStructure;
      }
    }
    
    class Leaf extends Component {
      constructor(name) {
        super(name);
        this._type = 'Leaf Node';
      }
    
      getType() {
        return this._type;
      }
    
      noOfChildren() {
        return 0;
      }
    }
    
    class Composite extends Component {
      constructor(name) {
        super(name);
        this._type = 'Composite Node';
        this._children = [];
      }
    
      getType() {
        return this._type;
      }
    
      addChild(component) {
        this._children = [...this._children, component];
      }
    
      removeChildByName(componentName) {
        this._children = [...this._children].filter(component => component.getNodeName() !== componentName);
      }
    
      removeChildByIndex(index) {
        this._children = [...this._children.slice(0, index), ...this._children.slice(index + 1)];
      }
    
      getChildByName(componentName) {
        return this._children.find(component => component.name === componentName);
      }
    
      getChildByIndex(index) {
        return this._children[index];
      }
    
      noOfChildren() {
        return this._children.length;
      }
    }
    
    // usage
    const tree = new Composite('root');
    tree.addChild(new Leaf('left'));
    const right = new Composite('right');
    tree.addChild(right);
    right.addChild(new Leaf('right-left'));
    const rightMid = new Composite('right-middle');
    right.addChild(rightMid);
    right.addChild(new Leaf('right-right'));
    rightMid.addChild(new Leaf('left-end'));
    rightMid.addChild(new Leaf('right-end'));
    
    // log
    console.log(Component.logTreeStructure(tree));
    /*
    root
    --left
    --right
    ----right-left
    ----right-middle
    ------left-end
    ------right-end
    ----right-right
    */
    • Decorator Pattern
    더보기

    기존 클래스에 동적으로 동작 또는 기능을 추가하는 기능에 초점을 맞춘 구조적 디자인 패턴입니다.

    데코레이터 유형은 자바스크립트에서 구현하기 매우 쉽습니다.

    메서드와 속성을 객체에 동적으로 추가할 수 있기 때문입니다.

     

    // 기본 클래스
    class Book {
      constructor(title, author, price) {
        this._title = title;
        this._author = author;
        this.price = price;
      }
    
      getDetails() {
        return `${this._title} by ${this._author}`;
      }
    }
    
    // decorator 1
    function giftWrap(book) {
      book.isGiftWrapped = true;
      book.unwrap = function() {
        return `Unwrapped ${book.getDetails()}`;
      };
    
      return book;
    }
    
    // decorator 2
    function hardbindBook(book) {
      book.isHardbound = true;
      book.price += 5;
      return book;
    }
    
    // 사용법
    // decorator 1로 기본 클래스를 래핑
    const alchemist = giftWrap(new Book('The Alchemist', 'Paulo Coelho', 10));
    
    console.log(alchemist.isGiftWrapped); // true
    console.log(alchemist.unwrap()); // 'Unwrapped The Alchemist by Paulo Coelho'
    
    // decorator 2로 기본 클래스를 래핑
    const inferno = hardbindBook(new Book('Inferno', 'Dan Brown', 15));
    
    console.log(inferno.isHardbound); // true
    console.log(inferno.price); // 20
    • Facade Pattern
    더보기

    자바 스크립트 라이브러리에서 널리 사용되는 구조적 디자인 패턴입니다.

    하위 시스템 쪼는 하위 클래스의 복잡성을 방지하는 사용 편의성을 위해 통합되고 더 간단한 공용 인터페이스를 제공하는데 사용됩니다. jQuery와 같은 라이브러리에서 매우 일반적입니다.

     

    예를 보면, ComplainRegistry로 생성된 인스턴스에서 registerComplaint 메서드를 사용할 수 있습니다.

    type 인자를 통해서 ServiceComplaints 또는 ProductComplaints로 필요한 객체를 인스턴스화합니다.

    고유 아이디 생성, 컴플레인 저장 같은 복잡한 일을 처리하지만 파사드 패턴에 의해 숨겨졌습니다.

    let currentId = 0;
    
    class ComplaintRegistry {
      registerComplaint(customer, type, details) {
        const id = ComplaintRegistry._uniqueIdGenerator();
        let registry;
        if (type === 'service') {
          registry = new ServiceComplaints();
        } else {
          registry = new ProductComplaints();
        }
        return registry.addComplaint({ id, customer, details });
      }
    
      static _uniqueIdGenerator() {
        return ++currentId;
      }
    }
    
    class Complaints {
      constructor() {
        this.complaints = [];
      }
    
      addComplaint(complaint) {
        this.complaints.push(complaint);
        return this.replyMessage(complaint);
      }
    
      getComplaint(id) {
        return this.complaints.find(complaint => complaint.id === id);
      }
    
      replyMessage(complaint) {}
    }
    
    class ProductComplaints extends Complaints {
      constructor() {
        super();
        if (ProductComplaints.exists) {
          return ProductComplaints.instance;
        }
        ProductComplaints.instance = this;
        ProductComplaints.exists = true;
        return this;
      }
    
      replyMessage({ id, customer, details }) {
        return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Products Complaint Department.`;
      }
    }
    
    class ServiceComplaints extends Complaints {
      constructor() {
        super();
        if (ServiceComplaints.exists) {
          return ServiceComplaints.instance;
        }
        ServiceComplaints.instance = this;
        ServiceComplaints.exists = true;
        return this;
      }
    
      replyMessage({ id, customer, details }) {
        return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Service Complaint Department.`;
      }
    }
    
    // usage
    const registry = new ComplaintRegistry();
    
    const reportService = registry.registerComplaint('Martha', 'service', 'availability');
    // 'Complaint No. 1 reported by Martha regarding availability have been filed with the Service Complaint Department.'
    
    const reportProduct = registry.registerComplaint('Jane', 'product', 'faded color');
    // 'Complaint No. 2 reported by Jane regarding faded color have been filed with the Products Complaint Department.'
    • Flywieght Pattern
    더보기

    세분화 된 객체를 통한 효율적인 데이터 공유에 중점을 둔 구조 설계 패턴입니다.

    효율성 및 메모리 보존 목적으로 사용됩니다.

    모든 종류의 캐싱 목적으로 사용할 수 있습니다.

    실제로 최신 브라우저에서 동일한 이미지가 두 번 로드되는 것을 방지하기 위해 사용합니다.

     

    예를 보면, 데이터 공유를 위한 flyweight 클래스와 flyweight 객체를 생성하기 위한 팩토리 클래스를 만듭니다.

    메모리 보존을 위해 동일한 객체가 두번 인스턴스화 된다면 이전의 객체를 재활용합니다.

     

    // flyweight 클래스
    class Icecream {
      constructor(flavour, price) {
        this.flavour = flavour;
        this.price = price;
      }
    }
    
    // flyweight 객체를 위한 factory
    class IcecreamFactory {
      constructor() {
        this._icecreams = [];
      }
    
      createIcecream(flavour, price) {
        let icecream = this.getIcecream(flavour);
        if (icecream) {
          return icecream;
        } else {
          const newIcecream = new Icecream(flavour, price);
          this._icecreams.push(newIcecream);
          return newIcecream;
        }
      }
    
      getIcecream(flavour) {
        return this._icecreams.find(icecream => icecream.flavour === flavour);
      }
    }
    
    // 사용법
    const factory = new IcecreamFactory();
    
    const chocoVanilla = factory.createIcecream('chocolate and vanilla', 15);
    const vanillaChoco = factory.createIcecream('chocolate and vanilla', 15);
    
    // reference to the same object
    console.log(chocoVanilla === vanillaChoco); // true
    • Proxy Pattern
    더보기

    다른 객체에 대한 액세스를 제어하기 위해 대리 역할을 합니다.

    프록시는 일반적으로 클라이언트에 동일한 인터페이스를 제공하고 과도한 압력을 피하기 위해 대상 객체에 대한 간접 액세스를 지원합니다.

    네트워크 요청이 많은 애플리케이션으로 작업 할 때 불필요하거나 중복 된 네트워크 요청을 방지하는 데 유용하게 사용됩니다.

     

    예시에서는 ES6의 새로운 기능인 Proxy와 Reflect를 사용합니다. javascript.info

    Proxy 객체는 자바 스크립트 객체의 사용자 지정 동작을 정의하는데 사용됩니다.

    생성자 메서드인 Proxy는 프록시되어야하는 객체인 target과 필요한 사용자 정의를 정하는 handler를 받습니다.

    핸들러 객체에는 동작을 가로채는 메서드인 trap이 담긴 객체로서 get, set, has, apply라는 내부 메서드가 있습니다.

    get(target, property, receiver) - 프로퍼티를 읽으려 할 때 작동합니다.

    set(target, property, value, receiver) - 프로퍼티에 값을 쓰려고 할 때 사용됩니다.

    has(target, property) - in 호출을 가로챕니다.

    apply(target, thisArg, args) - 프록시를 함수처럼 호출하려 할 때 동작합니다.

     

    // Target
    function networkFetch(url) {
      return `${url} - Response from network`;
    }
    
    // Proxy
    // ES6 Proxy API = new Proxy(target, handler);
    const cache = [];
    const proxiedNetworkFetch = new Proxy(networkFetch, {
      apply(target, thisArg, args) {
        const urlParam = args[0];
        if (cache.includes(urlParam)) {
          return `${urlParam} - Response from cache`;
        } else {
          cache.push(urlParam);
          return Reflect.apply(target, thisArg, args);
        }
      },
    });
    
    // usage
    console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from network'
    console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from cache'

     


    3. Behavioral Design Patterns - 객체 간의 통신을 개선합니다.

    • Chain of Responsibility Pattern
    • Command Pattern
    • Iterator Pattern
    • Mediator Pattern
    • Observer Pattern
    • State Pattern
    • Strategy Pattern
    • Template Pattern

     

     

     


     

    다음과 같은 디자인 패턴이 있습니다.

    • Module
    • Prototype
    • Observer
    • Singleton

    디자인 패턴의 핵심 사항은 다음과 같습니다.

    • context - 어디에 / 어떤 상황에 사용되는가?
    • problem - 무엇을 해결하려는가?
    • soultion - 이 패턴을 사용하면 문제가 어떻게 해결되는가?
    • Implementation - 어떻게 구현하는가?

    Module

    특정 코드 조각을 다른 구성 요소와 독립적으로 유지하기 위해 자주 사용되는 디자인 패턴입니다.

    객체 지향언어에 익숙하다면 Javascript의 class가 익숙합니다.

    class의 장점 중 하나는 캡슐화입니다. 다른 class에서 상태와 동작에 접근하지 못하도록 보호합니다.

    모듈에서 공개 범위를 허용하기 위해서 클로저를 사용할 수 있습니다.

    (function() {
    
        // 비공개 변수 또는 함수를 여기에 정의합니다.
    
        return {
    
            // 공개 변수 또는 함수를 여기에 정의합니다.
    
        }
    
    })();

    Prototype

    프로토 타입 디자인 패턴은 주로 성능 집약적인 상황에서 개체를 만드는 데 사용됩니다.

    생성 된 객체는 전달되는 원본 객체의 복제본입니다.

    개체를 복제하려면 첫 번째 개체를 인스턴스화하는 생성자가 있어야합니다.

    var moleGame = function() {
      this.numMoles    = 4;
      this.delayTime   = 200;
      this.score       = 10;
    }
    
    TeslaModelS.prototype.start = function() {
      // 게임시작
    }
    
    TeslaModelS.prototype.catch = function() {
      // 두더지 잡음
    }

    프로토 타입도 closure를 활용하여 노출 할 항목을 선택하여 액세스 수준을 정할 수 있습니다.

    var moleGame = function() {
      this.numMoles    = 4;
      this.delayTime   = 200;
      this.score       = 10;
    }
    
    TeslaModelS.prototype = function() {
    
      var start = function() {
        // 게임시작
      };
    
      var catch = function() {
        // 두더지 잡음
      };
    
      return {
        pressButton: start,
        pressMole: catch
      }
    
    }();

    Observer

    응용 프로그램은 한 부분이 변경되거나 업데이트되어야하는 경우가 많습니다.

    대표적인 예로 MVC (model - view - controller) 아키텍처가 있습니다.

    모델이 변경되면 뷰가 업데이트됩니다.

    한 가지 이점은 모델에서 뷰를 분리하여 종속성이 줄어드는 것 입니다.

    var Subject = function() {
      this.observers = [];
    
      return {
        subscribeObserver: function(observer) {
          this.observers.push(observer);
        },
        unsubscribeObserver: function(observer) {
          var index = this.observers.indexOf(observer);
          if(index > -1) {
            this.observers.splice(index, 1);
          }
        },
        notifyObserver: function(observer) {
          var index = this.observers.indexOf(observer);
          if(index > -1) {
            this.observers[index].notify(index);
          }
        },
        notifyAllObservers: function() {
          for(var i = 0; i < this.observers.length; i++){
            this.observers[i].notify(i);
          };
        }
      };
    };
    
    var Observer = function() {
      return {
        notify: function(index) {
          console.log("Observer " + index + " is notified!");
        }
      }
    }
    
    var subject = new Subject();
    
    var observer1 = new Observer();
    var observer2 = new Observer();
    var observer3 = new Observer();
    var observer4 = new Observer();
    
    subject.subscribeObserver(observer1);
    subject.subscribeObserver(observer2);
    subject.subscribeObserver(observer3);
    subject.subscribeObserver(observer4);
    
    subject.notifyObserver(observer2); // Observer 2 is notified!
    
    subject.notifyAllObservers();
    // Observer 1 is notified!
    // Observer 2 is notified!
    // Observer 3 is notified!
    // Observer 4 is notified!

    Singleton

    클라이언트가 여러 객체를 만들지 못하도록 제한합니다.

    첫 번째 객체가 생성된 후 자체 인스턴스를 반환합니다.

    프린터를 예로 들수 있습니다.

    var printer = (function () {
    
      var printerInstance;
    
      function create () {
    
        function print() {
          // underlying printer mechanics
        }
    
        function turnOn() {
          // warm up
          // check for paper
        }
    
        return {
          // public + private states and behaviors
          print: print,
          turnOn: turnOn
        };
      }
    
      return {
        getInstance: function() {
          if(!printerInstance) {
            printerInstance = create();
          }
          return printerInstance;
        }
      };
    
      function Singleton () {
        if(!printerInstance) {
          printerInstance = intialize();
        }
      };
    
    })();

    create 메소드는 비공개이지만 getInstance 메소드는 공개입니다. 그러므로 getInstance 메서드로 프린터 인스턴스를 생성할 수 있습니다.

    댓글

Designed by CHANUL