ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • GoF Pattern
    Javascript 2020. 9. 26. 22:28

     

    객체 지향 프로그램 설계를 할 때 자주 발생하는 문제를 해결하기 위한 패턴

     

    애플리테이션이 커짐에 따라서 코드 구조화가 중요해 집니다. 복잡한 코드를 유지 관리 가능하고 읽기 쉬우며 재사용 가능한 코드로 작성하기 위해서 디자인 패턴이 사용됩니다. 또한 소프트웨어를 설계할 때 특정한 맥락에서 자주 발생하는 문제를 처리하는 해결책에 공통점이 있습니다. 또한 패턴은 팀원 간의 공통된 언어를 만들어 의사 소통을 원활하게 만들어 주기도 합니다.

     

    그러나 모든 문제를 디자인 패턴으로 해결하기보다 코드 베이스의 간결성이 생각해야 합니다. 디자인 패턴에 얽매이기 보다 그 패턴이 왜 효율적인지 이해하는 것이 좋습니다. 

     

    Gang of Four 디자인 패턴

    23가지의 디자인 패턴이 Creational, Structural, Behavioral 3가지로 분류되어 있습니다.

    1. Creational (생성 패턴) - 객체의 생성 과정을 제어

    2. Structural (구조 패턴) - 전체 시스템에 영향을 주지 않고 하나 이상의 부품을 구조화, 재구성

    3. Behavioral (행위 패턴) - 통신 시스템을 통신을 개선

     

     

    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 라는 팩토리 클래스에는 매개 변수를 받는 메서드가 있습니다.

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

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

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

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

     

    다음 예제에서 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 클래스의 인스턴스가 없으면 새로운 인스턴스가 생성되고 반환되지만 인스턴스가 이미 존재하면 기존 인스턴스에 대한 참조가 반환됩니다.

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

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

    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가 사용되었습니다.

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

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

    이 패턴은 새로 리팩토링 된 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로 필요한 객체를 인스턴스화합니다.

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

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

    하위 시스템 쪼는 하위 클래스의 복잡성을 방지하는 사용 편의성을 위해 통합되고 더 간단한 공용 인터페이스를 제공하는데 사용됩니다. 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 클래스와 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) - 프록시를 함수처럼 호출하려 할 때 동작합니다.

     

     

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

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

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

     

    예시에서는 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

    더보기

    느슨하게 결합 된 객체의 체인을 제공하는 동작 디자인 패턴입니다.

    책임 패턴의 좋은 예는 일련의 중첩된 DOM 요소를 통해 전파되는 DOM의 이벤트 버블링입니다.

    그 중 하나에 이벤트를 수신하고 그에 대해 작동하기 위해 연결된 "이벤트 리스너"가 있을 수 있습니다.

     

    느슨하게 결합 된 객체의 체인을 제공하는 동작 디자인 패턴입니다.

    책임 패턴의 좋은 예는 일련의 중첩된 DOM 요소를 통해 전파되는 DOM의 이벤트 버블링입니다.

    그 중 하나에 이벤트를 수신하고 그에 대해 작동하기 위해 연결된 "이벤트 리스너"가 있을 수 있습니다.

     

    class CumulativeSum {
      constructor(intialValue = 0) {
        this.sum = intialValue;
      }
    
      add(value) {
        this.sum += value;
        return this;
      }
    }
    
    // usage
    const sum1 = new CumulativeSum();
    console.log(sum1.add(10).add(2).add(50).sum); // 62
    
    
    const sum2 = new CumulativeSum(10);
    console.log(sum2.add(10).add(20).add(5).sum); // 45

     

    Command Pattern

    더보기

    동작이나 작업을 객체로 캡슐화하는 것을 목표로 하는 패턴입니다.

    작업을 요청하거나 메서드를 호출하는 객체를 분리하여 시스템과 클래스의 느슨한 결합을 허용합니다.

    만약에 Redux 유저라면 이 패턴에 익숙할 것입니다.

    동작이나 작업을 객체로 캡슐화하는 것을 목표로 하는 패턴입니다.

    작업을 요청하거나 메서드를 호출하는 객체를 분리하여 시스템과 클래스의 느슨한 결합을 허용합니다.

    만약에 Redux 유저라면 이 패턴에 익숙할 것입니다.

    class SpecialMath {
      constructor(num) {
        this._num = num;
      }
    
      square() {
        return this._num ** 2;
      }
    
      cube() {
        return this._num ** 3;
      }
    
      squareRoot() {
        return Math.sqrt(this._num);
      }
    }
    
    class Command {
      constructor(subject) {
        this._subject = subject;
        this.commandsExecuted = [];
      }
      execute(command) {
        this.commandsExecuted.push(command);
        return this._subject[command]();
      }
    }
    
    // usage
    const x = new Command(new SpecialMath(5));
    x.execute('square');
    x.execute('cube');

     

    Iterator Pattern

    더보기

    기본 표현을 노출하지 않고 객체 요소에 순차적으로 접근하는 방법을 제공하는 패턴입니다.

    Iterator는 끝에 도달 할 때까지 next()를 호출하여 한 번에 하나씩 순서가 지정된 값 집합을 단계별로 실행하는 동작을 가지고 있습니다. ES6에 Iterator 및 Generators가 도입되면서 패턴 구현이 간단해졌습니다.

     

    mdn Iterator & Generator

    기본 표현을 노출하지 않고 객체 요소에 순차적으로 접근하는 방법을 제공하는 패턴입니다.

    Iterator는 끝에 도달 할 때까지 next()를 호출하여 한 번에 하나씩 순서가 지정된 값 집합을 단계별로 실행하는 동작을 가지고 있습니다. ES6에 Iterator 및 Generators가 도입되면서 패턴 구현이 간단해졌습니다.

     

    mdn Iterator & Generator

    // using Iterator
    class IteratorClass {
      constructor(data) {
        this.index = 0;
        this.data = data;
      }
    
      [Symbol.iterator]() {
        return {
          next: () => {
            if (this.index < this.data.length) {
              return { value: this.data[this.index++], done: false };
            } else {
              this.index = 0; // to reset iteration status
              return { done: true };
            }
          },
        };
      }
    }
    
    // using Generator
    function* iteratorUsingGenerator(collection) {
      var nextIndex = 0;
    
      while (nextIndex < collection.length) {
        yield collection[nextIndex++];
      }
    }
    
    // usage
    const gen = iteratorUsingGenerator(['Hi', 'Hello', 'Bye']);
    
    console.log(gen.next().value); // 'Hi'
    console.log(gen.next().value); // 'Hello'
    console.log(gen.next().value); // 'Bye'

     

    Mediator Pattern

    더보기

    객체 집합이 서로 상호 작용하는 방식을 캡슐화하는 동작 패턴입니다.

    느슨한 결합을 촉진하여 객체가 서로를 명시적으로 참조하지 못하도록하여 객체 그룹에 대한 중앙 권한을 제공합니다.

     

    예를 보면 TrafficTower와 Airplane의 register 메서드로 느슨하게 결합합니다.

     

     

    객체 집합이 서로 상호 작용하는 방식을 캡슐화하는 동작 패턴입니다.

    느슨한 결합을 촉진하여 객체가 서로를 명시적으로 참조하지 못하도록하여 객체 그룹에 대한 중앙 권한을 제공합니다.

     

    예를 보면 TrafficTower와 Airplane의 register 메서드로 느슨하게 결합합니다.

     

     

    class TrafficTower {
      constructor() {
        this._airplanes = [];
      }
    
      register(airplane) {
        this._airplanes.push(airplane);
        airplane.register(this);
      }
    
      requestCoordinates(airplane) {
        return this._airplanes.filter(plane => airplane !== plane).map(plane => plane.coordinates);
      }
    }
    
    class Airplane {
      constructor(coordinates) {
        this.coordinates = coordinates;
        this.trafficTower = null;
      }
    
      register(trafficTower) {
        this.trafficTower = trafficTower;
      }
    
      requestCoordinates() {
        if (this.trafficTower) return this.trafficTower.requestCoordinates(this);
        return null;
      }
    }
    
    // usage
    const tower = new TrafficTower();
    
    const airplanes = [new Airplane(10), new Airplane(20), new Airplane(30)];
    airplanes.forEach(airplane => {
      tower.register(airplane);
    });
    
    console.log(airplanes.map(airplane => airplane.requestCoordinates())) 
    // [[20, 30], [10, 30], [10, 20]]

     

    Observer Pattern

    더보기

    하나의 객체가 상태를 변경하면 (게시자)

    다른 모든 종속 객체가 자동으로 업데이트되는 (구독자)

    객체 간의 일 대 다 종속성을 정의하는 패턴입니다.

    addEventListener 또는 jQuery를 사용한 적이 있다면 익숙 할 것입니다.

     

    예를 보면, Subject 클래스의 fire 메서드에서 Observer 클래스의 Update 메서드를 호출하여 state 값을 변경하고 있습니다.

    하나의 객체가 상태를 변경하면 (게시자)

    다른 모든 종속 객체가 자동으로 업데이트되는 (구독자)

    객체 간의 일 대 다 종속성을 정의하는 패턴입니다.

    addEventListener 또는 jQuery를 사용한 적이 있다면 익숙 할 것입니다.

     

    예를 보면, Subject 클래스의 fire 메서드에서 Observer 클래스의 Update 메서드를 호출하여 state 값을 변경하고 있습니다.

    class Subject {
      constructor() {
        this._observers = [];
      }
    
      subscribe(observer) {
        this._observers.push(observer);
      }
    
      unsubscribe(observer) {
        this._observers = this._observers.filter(obs => observer !== obs);
      }
    
      fire(change) {
        this._observers.forEach(observer => {
          observer.update(change);
        });
      }
    }
    
    class Observer {
      constructor(state) {
        this.state = state;
        this.initialState = state;
      }
    
      update(change) {
        let state = this.state;
        switch (change) {
          case 'INC':
            this.state = ++state;
            break;
          case 'DEC':
            this.state = --state;
            break;
          default:
            this.state = this.initialState;
        }
      }
    }
    
    // usage
    const sub = new Subject();
    
    const obs1 = new Observer(1);
    const obs2 = new Observer(19);
    
    sub.subscribe(obs1);
    sub.subscribe(obs2);
    
    sub.fire('INC');
    
    console.log(obs1.state); // 2
    console.log(obs2.state); // 20

     

    State Pattern

    더보기

    객체가 내부 state 변화에 따라 행동을 변경할 수 있도록 하는 패턴입니다.

    예를 보면, TrafficLight의 change 메서드가 current를 변경합니다.

    그래서 change 메서드를 호출하면 sign 메서드로 호출되는 클래스가 달라집니다.

    객체가 내부 state 변화에 따라 행동을 변경할 수 있도록 하는 패턴입니다.

    예를 보면, TrafficLight의 change 메서드가 current를 변경합니다.

    그래서 change 메서드를 호출하면 sign 메서드로 호출되는 클래스가 달라집니다.

    class TrafficLight {
      constructor() {
        this.states = [new GreenLight(), new RedLight(), new YellowLight()];
        this.current = this.states[0];
      }
    
      change() {
        const totalStates = this.states.length;
        let currentIndex = this.states.findIndex(light => light === this.current);
        if (currentIndex + 1 < totalStates) this.current = this.states[currentIndex + 1];
        else this.current = this.states[0];
      }
    
      sign() {
        return this.current.sign();
      }
    }
    
    class Light {
      constructor(light) {
        this.light = light;
      }
    }
    
    class RedLight extends Light {
      constructor() {
        super('red');
      }
    
      sign() {
        return 'STOP';
      }
    }
    
    class YellowLight extends Light {
      constructor() {
        super('yellow');
      }
    
      sign() {
        return 'STEADY';
      }
    }
    
    class GreenLight extends Light {
    	constructor() {
    		super('green');
    	}
    
    	sign() {
    		return 'GO';
    	}
    }
    
    // usage
    const trafficLight = new TrafficLight();
    
    console.log(trafficLight.sign()); // 'GO'
    trafficLight.change();
    
    console.log(trafficLight.sign()); // 'STOP'
    trafficLight.change();
    
    console.log(trafficLight.sign()); // 'STEADY'
    trafficLight.change();
    
    console.log(trafficLight.sign()); // 'GO'
    trafficLight.change();
    
    console.log(trafficLight.sign()); // 'STOP'

     

    Strategy Pattern

    더보기

    특정 작업에 대한 대체 알고리즘의 캡슐화를 허용하는 패턴입니다.

    알고리즘을 정의하고 클라이언트의 간섭이나 지식없이 런타임에서 상호교환 할 수 있도록 캡슐화합니다.

     

    예를 보면, Commute 클래스가 모든 strategy 클래스들을 감쌉니다.

    그리고 strategy 이름을 Bus, Taxi, PersonalCar로 지었습니다.

     

    특정 작업에 대한 대체 알고리즘의 캡슐화를 허용하는 패턴입니다.

    알고리즘을 정의하고 클라이언트의 간섭이나 지식없이 런타임에서 상호교환 할 수 있도록 캡슐화합니다.

     

    예를 보면, Commute 클래스가 모든 strategy 클래스들을 감쌉니다.

    그리고 strategy 이름을 Bus, Taxi, PersonalCar로 지었습니다.

     

    // encapsulation
    class Commute {
      travel(transport) {
        return transport.travelTime();
      }
    }
    
    class Vehicle {
      travelTime() {
        return this._timeTaken;
      }
    }
    
    // strategy 1
    class Bus extends Vehicle {
      constructor() {
        super();
        this._timeTaken = 10;
      }
    }
    
    // strategy 2
    class Taxi extends Vehicle {
      constructor() {
        super();
        this._timeTaken = 5;
      }
    }
    
    // strategy 3
    class PersonalCar extends Vehicle {
      constructor() {
        super();
        this._timeTaken = 3;
      }
    }
    
    // usage
    const commute = new Commute();
    
    console.log(commute.travel(new Taxi())); // 5
    console.log(commute.travel(new Bus())); // 10

     

    Template Pattern

    더보기

    알고리즘의 뼈대 정의 또는 작업 구현을 기반으로 하는 패턴입니다.

    알고리즘의 뼈대 정의 또는 작업 구현을 기반으로 하는 패턴입니다.

    class Employee {
      constructor(name, salary) {
        this._name = name;
        this._salary = salary;
      }
    
      work() {
        return `${this._name} handles ${this.responsibilities() /* gap to be filled by subclass */}`;
      }
    
      getPaid() {
        return `${this._name} got paid ${this._salary}`;
      }
    }
    
    class Developer extends Employee {
      constructor(name, salary) {
        super(name, salary);
      }
    
      // details handled by subclass
      responsibilities() {
        return 'application development';
      }
    }
    
    class Tester extends Employee {
      constructor(name, salary) {
        super(name, salary);
      }
    
      // details handled by subclass
      responsibilities() {
        return 'testing';
      }
    }
    
    // usage
    const dev = new Developer('Nathan', 100000);
    console.log(dev.getPaid()); // 'Nathan got paid 100000'
    console.log(dev.work()); // 'Nathan handles application development'
    
    const tester = new Tester('Brian', 90000);
    console.log(tester.getPaid()); // 'Brian got paid 90000'
    console.log(tester.work()); // 'Brian handles testing'

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    'Javascript' 카테고리의 다른 글

    프라미스(Promise)  (0) 2020.06.09
    값과 참조 (Value & Reference)  (0) 2020.05.16
    (loading)class  (0) 2020.05.10
    (loading)Prototype Chain  (0) 2020.05.08
    실행 컨텍스트 (execution context)  (0) 2020.04.30

    댓글

Designed by CHANUL