ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JavaScript 디자인 패턴
    JavaScript 2021. 5. 12. 22:11

    @ 업데이트 중...

    @ 이 글은 최하단에 위치해있는 References를 참고하여 작성된 글입니다.

    @ chat: 도서 때문인지는 모르겠지만 정말 배우기 거부감 드는 책이다... 이 글 작성하는 것도 매우 거부감 들고...

     

     

    Note)

    UML (통합 모델링 언어, Unified Modeling Language): 객체 관련 표준화 기구인 OMG(Object Management Group)가 개발한 컴퓨터 시스템을 기술하는 표준화된 언어이다. UML은 사용자 상호작용 다이어그램, 시퀀스 다이어그램과 상태 머신을 생성할 수 있는 어휘를 포함함

    Part 1. GoF에 소개된 패턴

    생성

    1) 추상 팩토리(Abstrarct Factory)

    예를 들어, 어떤 왕국이 있을 때 왕은 나라를 지배하고 있다. 왕의 영향력을 행사하려면 넓은 영토에 왕이 직접 다니면서 영향력을 행사하기엔 한계가 있기에 왕을 섬기는 신하와 의회를 통해서 영향력을 행사할 수 있다.

    하지만, 왕이 자주 바뀌게 되면 문제가 된다. 추상 팩토리는 왕과 관련된 다양한 클래스를 생성할 수 있는 인터페이스를 선언한다.

     

    아래 코드처럼 하나의 생성자 함수로 여러 생성자를 만들 수 있다.

    (CourtSession 하나로 KingJoffery, LordTywin의 여러가지 일을 할 수 있다.)

    var KingJoffery = (function() {
      function KingJoffery() {}
      KingJoffery.prototype.makeDecision = function() {}
      KingJoffery.prototype.marry = function() {}
      return KingJoffery;
    })()
    
    var LordTywin = (function() {
      function LordTywin() {}
      LordTywin.prototype.makeDecision = function() {}
      return LordTywin
    })()
    
    var LannisterFactory = (function() {
      function LannisterFactory() {}
      LannisterFactory.prototype.getKing = function() {
        return new KingJoffery()
      }
      LannisterFactory.prototype.getHandOfTheKing = function() {
        return new LordTywin()
      }
      return LannisterFactory
    })
    
    /*
    var TargaryenFactory = (function() {
      function TargaryenFactory() {}
      TargaryenFactory.prototype.getKing = function() {
        return new KingAerys()
      }
      TargaryenFactory.prototype.getHandOfTheKing = function() {
        return new LordConnington();
      }
      return TargaryenFactory;
    })
    */
    
    var CourtSession = (function() {
      function CourtSession(abstractFactory) {
        this.abstractFactory = abstractFactory
        this.COMPLAINT_THRESHOLD = 10
      }
      CourtSession.prototype.complaintPresented = function (complaint) {
        if (complaint.severity < this.COMPLAINT_THRESHOLD) {
          new this.abstractFactory().getHandOfTheKing().makeDecision()
        } else {
          new this.abstractFactory().getKing().makeDecision()
        }
      }
      return CourtSession;
    })()
    
    var courtSession1 = new CourtSession(new LannisterFactory())
    courtSession1.complaintPresented({ severity: 8 })
    courtSession1.complaintPresented({ severity: 12 })

     

    2) 빌더 (Builder)

    복잡한 클래스의 구축을 단순화하고 사용자가 전문지식없이 클래스를 구축할 때 사용가능하다. => 빌더들의 역할이 각각 다름 => 옵션이 다를 때 많이 사용한다.

     

    최하단 3개의 빌더들 중 가장 아래의 빌더는 빌더 클래스를 매개변수로 받고 매개변수의 prototype build 함수를 통해 역할을 수행한다.

    매개변수로 받는 빌더는 3개 중 상단에 위치한 2개의 빌더 클래스인데, 빌더 클래스는 build 함수를 통해서 토너먼트를 구성한다.

    토너먼트는 참석자와 상금이 주어질 수도 있고 주어지지 않을 수도 있기 때문에 이런 경우에 빌더 패턴을 사용할 수 있겠다.

    var Westeros = {};
    var Event = (function() {
      function Event(name) {
        this.name = name
      }
      return Event;
    })()
    
    Westeros.Event = Event;
    
    var Prize = (function() {
      function Prize(name) {
        this.name = name
      }
      return Prize
    })()
    
    Westeros.Prize = Prize
    
    var Attendee = (function() {
      function Attendee(name) {
        this.name = name;
      }
      return Attendee
    })()
    
    Westeros.Attendee = Attendee
    
    var Tournament = (function() {
      // this.Events = [];
      function Tournament() {}
      return Tournament
    })()
    
    Westeros.Tournament = Tournament
    
    var LannisterTournamentBuilder = (function() {
      function LannisterTournamentBuilder() {}
      LannisterTournamentBuilder.prototype.build = function() {
        var tournament = new Tournament()
        tournament.events.push(new Event('Joust'))
        tournament.events.push(new Event('Melee'))
    
        tournament.attendees.push(new Attendee('Jamie'))
        tournament.prizes.push(new Prize('Gold'))
        tournament.prizes.push(new Prize('More Gold'))
      }
    })()
    Westeros.LannisterTournamentBuilder = LannisterTournamentBuilder
    
    var BaratheonTounamentBuider = (function() {
      function BaratheonTounamentBuider() {}
      BaratheonTounamentBuider.prototype.build = function() {
        var tournament = new Tournament()
        tournament.events.push(new Event('Joust'))
        tournament.events.push(new Event('Melee'))
    
        tournament.attendees.push(new Attendee('Stannis'))
        tournament.attendees.push(new Attendee('Robert'))
    
        return tournament
      }
      return BaratheonTounamentBuider
    })()
    
    Westeros.BaratheonTounamentBuider = BaratheonTounamentBuider
    
    var TournamentBuilder = (function() {
      function TournamentBuilder() {}
      TournamentBuilder.prototype.build = function (builder) {
        return builder.build()
      }
      return TournamentBuilder
    })()
    
    Westeros.TournamentBuilder = TournamentBuilder

     

    3) 팩토리 메서드 (Factory Method)

    인터페이스를 어떻게 구현할지에 대한 결정 없이 인터페이스의 새로운 인스턴스를 요청할 수 있도록 허용한다.

    이 전략은 가끔 문자열 매개변수를 받거나 스위치로 동작하도록 전역 설정의 일부를 검토하기도 한다.

    var Religion = {};
    
    var WateryGod = (function() {
      function WateryGod() {}
      WateryGod.prototype.prayTo = function() {}
      return WateryGod;
    })()
    
    Religion.WateryGod = WateryGod
    
    var AncientGods = (function() {
      function AncientGods() {}
      AncientGods.prototype.prayTo = function() {}
      return AncientGods
    })()
    
    Religion.AncientGods = AncientGods
    
    var DefaultGod = (function() {
      function DefaultGod() {}
      DefaultGod.prototype.prayTo = function() {}
      return DefaultGod
    })()
    
    Religion.DefaultGod = DefaultGod
    
    var GodFactory = (function() {
      function GodFactory() {}
      GodFactory.Build = function(godName) {
        if (godName === 'watery') {
          return new WateryGod();
        }
        if (godName === 'ancient') {
          return new AncientGods()
        }
        return new DefaultGod()
      }
      return new GodFactory()
    })()
    
    var GodDeterminant = (function() {
      function GodDeterminant(religionName, prayerPurpose) {
        this.religionName = religionName
        this.prayerPurpose = prayerPurpose
      }
      return GodDeterminant
    })()
    
    var Prayer = (function() {
      function Prayer() {
      }
      Prayer.prototype.pray = function(godName) {
        GodFactory.Build(godName).prayTo()
      }
      return Prayer
    })()
    
    

     

     

    4) 단일체 (Singleton)

    단 하나의 객체를 만들 때 사용합니다. 비슷한 예로는 객체 리터럴이 있습니다.

    const obj1 = {}
    const obj2 = {}
    
    console.log(obj1 === obj2) // false

     

    var Westeros;
    (function (Westeros) {
        var Wall = (function () {
            function Wall() {
                this.height = 0;
                if (Wall._instance)
                    return Wall._instance;
                Wall._instance = this;
            }
            Wall.prototype.setHeight = function (height) {
                this.height = height;
            };
    
            Wall.prototype.getStatus = function () {
                console.log("Wall is " + this.height + " meters tall");
            };
            Wall.getInstance = function () {
                if (!Wall._instance) {
                    Wall._instance = new Wall();
                }
    
                return Wall._instance;
            };
            Wall._instance = null;
            return Wall;
        })();
        Westeros.Wall = Wall;
    })(Westeros || (Westeros = {}));

     

     

    5) 프로토타입 (Prototype)

    객체를 단 한 번만 생성하고 그 객체를 복사하여 사용하는 패턴

    var Westeros
    (function (Westeros) {
      (function (Families) {
        var Lannister = (function () {
          function Lannister() {}
          Lannister.prototype.clone = function() {
            var clone = new Lannister()
            for (var attr in this) {
              clone[attr] = this[attr]
            }
            return clone;
          }
          return Lannister
        })()
        Families.Lannister = Lannister
      })(Westeros.Families || (Westeros.Families = {}))
      var Families = Westeros.Families
    })(Westeros || (Westeros = {}))
      
    var obj1 = new Westeros.Families.Lannister()
    console.log(obj1)
    obj1.x = 1
    obj1.y = 2
    obj1.z = 3
    
    var obj2 = obj1.clone()
    obj2.x = 10
    
    console.log(obj1.x, obj2.x)

     

    구조

    적응자(Adapter)

    코드의 추상화 레벨을 변경하는데 사용한다. -> 기존 코드를 변경하거나 새 코드를 변경하거나

    // 원래 배의 요구사항
    interface Ship {
      SetRudderAngleTo(angle: number)
      SetSailConfiguration(configuration: SailConfiguration)
      SetSailAngle(sailId: number, sailAngle: number)
      GetCurrentBearing(): number
      GetCurrentSpeedEstimate(): number
      ShiftCrewWeightTo(weightToShift: number, locationId: number)
    }
    
    // 새로운 적응자의 배
    interface SimpleShip {
      TurnLeft()
      TurnRight()
      GoForward()
    }
    
    // 세부 구현사항을 새롭게 적용할 적응자에서 구현
    var ShipAdapter = (function() {
      function ShipAdapter() {
        this._ship = new Ship()
      }
    
      ShipAdapter.prototype.TurnLeft = function() {
        this._ship.SetRudderAngleTo(-30)
        this._ship.SetRailAngle(3, 12)
      }
      ShipAdapter.prototype.TurnRight = function() {
        this._ship.SetRudderAngleTo(30)
        this._ship.SetSailAngle(5, -9)
      }
      ShipAdapter.prototype.GoForward = function() {
        // ...
      }
    
      return ShipAdapter
    })()
    
    var Ship = new ShipAdapter()
    ship.GoForward()
    ship.TurnLeft()

     

    가교(Bridge)

    가교 패턴은 하나의 인터페이스를 가지고 각각 다른 구현의 중간자 역할을 하는 여러 적응자를 만들 수 있다.

    var Religion = {}
    
    var OldGods = (function() {
      function OldGods() {}
      OldGods.prototype.prayTo = function(sacrifice) {
        console.log('We Old Gods hear your prayer')
      }
      return OldGods
    })()
    
    Religion.OldGods = OldGods
    
    var DrownedGod = (function() {
      function DrownedGod() {}
      DrownedGod.prototype.prayTo = function(humanSacrifice) {
        console.log('Bubble')
      }
      return DrownedGod
    })()
    
    Religion.DrownedGod = DrownedGod
    
    var SevenGods = (function() {
      function SevenGods() {}
      SevenGods.prototype.prayTo = function(prayerPurpose) {
        console.log('Sorry there are a lot of us, it gets confusing here.')
      }
      return SevenGods
    })()
    
    Religion.SevenGods = SevenGods
    
    var OldGodsAdapter = (function() {
      function OldGodsAdapter() {
        this._oldGods = new OldGods()
      }
      OldGodsAdapter.prototype.prayTo = function() {
        var sacrifice = new Sacrifice()
        this._oldGods.prayTo(sacrifice)
      }
      return OldGodsAdapter
    })()
    
    Religion.OldGodsAdapter = OldGodsAdapter
    
    var DrownedGodAdapter = (function() {
      function DrownedGodAdapter() {
        this._drownedGod = new DrownedGod()
      }
      DrownedGodAdapter.prototype.prayTo = function() {
        var sacrifice = new HumanSacrifice()
        this._drownedGod.prayTo(sacrifice)
      }
      return DrownedGodAdapter
    })()
    Religion.DrownedGodAdapter = DrownedGodAdapter
    
    var SevenGodsAdapter = (function() {
      function SevenGodsAdapter() {
        this._prayerPurposeProvider = new PrayerPurposeProvider()
        this._sevenGods = new SevenGods()
      }
      SevenGodsAdapter.prototype.prayTo = function() {
        this._sevenGods.prayTo(this._prayerPurposeProvider.GetPurpose())
      }
      return SevenGodsAdapter
    })()
    Religion.SevenGodsAdapter = SevenGodsAdapter
    
    var god1 = new Religion.SevenGodsAdapter()
    var god2 = new Religion.DrownedGodAdapter()
    var god3 = new Religion.OldGodsAdapter()
    
    var gods = [god1, god2, god3]
    for (var i = 0; i < gods.length; i++) {
      gods[i].prayTo()
    }
    

     

    복합체(Composite)

    복합체는 자식 요소들과 컴포넌트의 상호교환이다.

    그러니까 자식 요소가 A라는 기능을 구현했다면 복합체의 모든 컴포넌트도 A라는 기능이 구현되어있다.

    마치 트리구조이다 (자식 노드가 없을 때까지 계속 내려감 = 리프노드를 만날 때까지)

    jQuery로 이런식으로 구현되어있다고 한다. 어떤 태그를 선택하든 같은 기능을 사용할 수 있기 때문

    var SimpleIngredient = (function () {
      function SimpleIngredient(name, calories) {
        this.name = name;
        this.calories = calories;
      }
      SimpleIngredient.prototype.getName = function () {
        return this.name;
      };
      SimpleIngredient.prototype.getCalories = function () {
        return this.calories;
      };
      return SimpleIngredient;
    })();
    
    var CompountIngredient = (function () {
      function CompountIngredient(name) {
        this.name = name;
        this.ingredients = [];
      }
      CompountIngredient.prototype.addIngredient = function (ingredient) {
        this.ingredients.push(ingredient);
        return this;
      };
      CompountIngredient.prototype.getName = function () {
        return this.name;
      };
      CompountIngredient.prototype.getCalories = function () {
        var total = 0;
        for (var i = 0; i < this.ingredients.length; i++) {
          total += this.ingredients[i].getCalories();
        }
        return total;
      };
      return CompountIngredient;
    })();
    
    var egg = new SimpleIngredient("egg", 155);
    var milk = new SimpleIngredient("milk", 42);
    var sugar = new SimpleIngredient("sugar", 388);
    var rice = new SimpleIngredient("rice", 370);
    
    var food = new CompountIngredient("food");
    food
      .addIngredient(egg)
      .addIngredient(milk)
      .addIngredient(sugar)
      .addIngredient(rice);
    
    console.log(food.getCalories());
    console.log(155 + 42 + 388 + 370);

     

    장식자(Decorator)

    @ 서브클래싱: 기존 컨트롤의 형태나 동작을 변경하는 기법

    데코레이터는 기존 클래스를 포장하거나 '확장'하는 데 쓰인다. 기존 컴포넌트의 서브 클래싱의 대안으로 쓰인다.

    서브클래싱은 일반적으로 컴파일 시 실행되고 한 번 실행되면 런타임에 변경할 수 있는 방법이 의미한다.

    함께 동작할 수 있는 다수의 서브클래싱이 있는 경우 조합 가능한 서브클래싱은 기하급수적으로 증가한다.

    var BasicArmor = (function() {
      function BasicArmor() {}
      BasicArmor.prototype.CalculateDamageFromHit = function(hit) {
        return 1
      }
      return BasicArmor
    })()
    
    var ChainMail = (function() {
      function ChainMail(decoratedArmor) {
        this.decoratedArmor = decoratedArmor
      }
      ChainMail.prototype.CalculateDamageFromHit = function(hit) {
        hit.Strength *= 0.8
        return this.decoratedArmor.CalculateDamageFromHit(hit)
      }
      ChainMail.prototype.GetArmorIntegrity = function() {
        return 0.9 * this.decoratedArmor.GetArmorIntegrity()
      }
      return ChainMail
    })()
    /* 
    export interface IArmor {
      CalculateDamageFromHit(hit: Hit): number
      GetArmorIntegrity(): number
    } */
    
    var armor = new ChainMail(new BasicArmor())
    console.log(
      armor.CalculateDamageFromHit(
        { 
          Location: 'head',
          Weapon: 'sock filled with pennies',
          Strength: 12
        }
      )
    )

     

    퍼사드(Facade)

    퍼사드는 클래스 집합의 간략화된 인터페이스를 제공하는 적응자 패턴이다. -> 서브 클래스를 묶어서 추상화할 수 있다. 이렇게 되면 서브클래스의 상세 구현 내용을 숨길 수 있다는 장점이 있다.

    // 해전이 벌어졌을 때 명령을 내리면 어떻게 작동하는지에 대한 예를 보여준다.
    // 함대는 각 배의 보급과 항로, 장교들에게 명령을 전달한다.
    // 각각의 서브클래스(Ship, Admiral, SupplyCoordinator)는 Fleet의 메소드를 호출했을 때
    // 서브클래스의 세부 구현이 실행되도록 설계하면 퍼사드 패턴으로 구현된다.
    
    var Ship = (function() {
      function Ship() {
        this._ship = new Ship()
      }
    
      Ship.prototype.TurnLeft = function() {
        this._ship.SetRudderAngleTo(-30)
        this._ship.SetRailAngle(3, 12)
      }
      Ship.prototype.TurnRight = function() {
        this._ship.SetRudderAngleTo(30)
        this._ship.SetSailAngle(5, -9)
      }
      Ship.prototype.GoForward = function() {
        // ...
      }
    
      return Ship
    })()
    
    var Admiral = (function() {
      function Admiral() {}
      return Admiral
    })()
    
    var SupplyCoordinator = (function() {
      function SupplyCoordinator() {}
      return SupplyCoordinator
    })()
    
    var Fleet = (function() {
      function Fleet() {}
      Fleet.prototype.setDestination = function (destination) {}
      Fleet.prototype.resupply = function() {}
      Fleet.prototype.attack = function() {}
      return Fleet
    })()
    
    var fleet = new Fleet()

     

    플라이급(FlyWeight)

    자바스크립트는 객체를 생성할 때 고가로 생성한다. 예를 들어 아주 많은 객체를 생성한다고 했을 때 (예를 들어 1만개 이상) 공통적인 요소를 묶어서 프로토타입으로 재정의하는 것을 의미한다. 이렇게 하면 메모리의 사용량을 획기적으로 줄일 수 있다.

    var Soldier = (function () {
      function Soldier(name) {
        this.hp = 100
        this.attack = 10
        this.name = name
      }
      return Soldier
    })()
    
    // 위처럼 작성하게 되면 객체를 생성할 때마다 hp와 attack이라는 속성이 추가되고
    // 중복 속성이 되어버려서 메모리를 낭비하게 된다.
    // 공통적인 사항은 프로토타입으로 정의하면 메모리 사용량을 줄일 수 있다.
    
    var Soldier2 = (function () {
      function Soldier2(name) {
        this.name = name
      }
      Soldier2.prototype.hp = 100
      Soldier2.prototype.attack = 10
      return Soldier2
    })()

     

    프록시 (Proxy)

    프록시 패턴은 원래 인스턴스의 인터페이스를 미러링하고 프록시를 거쳐서 에러 처리나 지연처리를 한 후 최종적으로 원래 인스턴스에 도달하도록 하는 패턴이다.

    var Barrel = (function() {
      function Barrel() {}
      Barrel.prototype.need = function(volume) {
        return Math.ceil(volume / 10)
      }
      return Barrel
    })()
    
    var ProxyBarrel = (function() {
      function ProxyBarrel(barrel) {
        this.barrel = barrel
      }
      ProxyBarrel.prototype.need = function(volume) {
        if (!this.barrel) {
          this.barrel = new Barrel()
        }
        return this.barrel.need(volume * 0.7)
      }
      return ProxyBarrel
    })()

     

    행동

    1. 책임 연쇄(Chain of responsibility)

    책임 연쇄는 마치 React의 Props처럼 넘겨주는 일을 한다.

     

    2. 명령(Command)

    메서드의 매개변수와 객체의 현재 상태 모두를 캡슐화하고 메서드를 호출하는 방법이다.

    (도서에서 적절한 예시를 찾지 못해 아래 링크로 대체합니다.)

    https://www.zerocho.com/category/JavaScript/post/57c667f2ee0b9e9043fed696

     

    3. 반복자(Iterator)

    이터레이터 프로토콜로 대체합니다.

    https://ko.javascript.info/iterable

     

    4. 중재자(Mediator)

    마치 채팅방의 서버 역할을 하는 객체이다.

    여러 객체 간의 통신을 마치 중개소에서 전달받고 전달하는 것처럼 동작한다.

    아래 코드에서 중재자는 HouseStark로 보면 되겠다.

    module Westeros.Alliances {
      export class HouseStark {
        karstark: Karstark;
        bolton: Bolton;
        frey: Frey;
        umber: Umber;
    
        constructor() {
          this.karstark = new Karstark(this);
          this.bolton = new Bolton(this);
          this.frey = new Frey(this);
          this.umber = new Umber(this);
        }
        routeMessage(message) {}
      }
    
      export class Karstark {
        constructor(public greatLord) {}
        public receiveMessage(message: string) {}
        public sendMessage(message: string) {
          this.greatLord.routeMessage(message);
        }
      }
    
      export class Bolton {
        constructor(public greatLord) {}
        public receiveMessage(message: string) {}
        public sendMessage(message: string) {
          this.greatLord.routeMessage(message);
        }
      }
    
      export class Frey {
        constructor(public greatLord) {}
        public receiveMessage(message: string) {}
        public sendMessage(message: string) {
          this.greatLord.routeMessage(message);
        }
      }
    
      export class Umber {
        constructor(public greatLord) {}
        public receiveMessage(message: string) {}
        public sendMessage(message: string) {
          this.greatLord.routeMessage(message);
        }
      }
    }
    

     

    5. 감시자(Observer)

    옵저버는 자바스크립트에서 널리 쓰이는 패턴 중의 하나인데 대표적으로 RxJS라던지 이벤트 리스너에서도 쓰인다고 한다.

    var Observer = (function() {
      function Observer() {
        this.list = []
      }
      Observer.prototype.subscribe = function(event) {
        this.list.push(event)
      }
      Observer.prototype.unsubscribe = function(event) {
        this.list.filter(function (item) {
          return item !== event
        })
      }
      Observer.prototype.execute = function() {
        for (var item of this.list) {
          console.log(item)
          // ...
        }
      }
      return Observer
    })()
    
    var Player = (function () {
      function Player() {}
      Player.prototype.do = function () {
        // ...
      }
      return Player
    })()
    
    var observer = new Observer()
    var player = new Player()
    observer.subscribe(player)
    observer.execute()
    observer.unsubscribe(player)

     

    6. 전략(Strategy)

    링크로 대체합니다. 아주 잘 설명되어 있어요

    https://www.zerocho.com/category/JavaScript/post/580f17ef77023c0015ee9688

     

    이 외의 패턴도 있지만 도서를 읽다보니 '이게 쓰일까?' 라는 생각에 넣진 않았습니다.

    궁금하신 분들은 도서를 구입하여 읽어보거나 아래에 작성한 키워드로 검색해보세요

    /*
    
    해석자(Interpreter)
    메멘토 (Memento)
    상태 (State)
    템플릿 메서드(Template method)
    방문자 (Visitor)
    
    */

     

    References

    도서: 자바스크립트 디자인 패턴 (www.yes24.com/Product/Goods/29390656?OzSrank=14)

    Blog: ZeroCho Blog (https://www.zerocho.com/category/JavaScript?page=3)

    Blog: Captain Pangyo (https://joshua1988.github.io/web-development/javascript/javascript-pattern-design/)

     

    @@@@@

    댓글

Designed by Tistory.