[이노캠] 디자인 패턴 - 2. 구조패턴

Chanyoung Park·2024년 6월 8일
0

2. 구조(Structural) 패턴

구조 패턴은 객체의 구성과 관련되며, Entity가 어떻게 사용될 수 있는지에 관한 것입니다.

1. 🔌 어댑터(Adapter)

어댑터 패턴을 사용하면 호환되지 않은 객체를 다른 클래스와 호환되도록 할 수 있다.

class Hunter {
  hunt(lion: Lion): void {
    lion.roar();
  }
}

// 이제 Hunter클래스는 Lion객체만 사냥할 수 있다.

interface Lion {
  roar(): void;
}

class ALion implements Lion {
  roar(): void {
    console.log("사자 죽어요");
  }
}

// 여기서, Hunter가 다른 동물도 사냥하고 싶다면 어떻게 해야 할까?

class Wolf {
  bark() {
    console.log("늑대 죽네");
  }
}

class WolfAdapter implements Lion {
  protected wolf: Wolf;

  constructor(wolf: Wolf) {
    this.wolf = wolf;
  }

  roar() {
    this.wolf.bark();
  }
}

// Usage
const hunter = new Hunter();

const lion = new ALion();

const wolf = new Wolf();
const wolfAdapter = new WolfAdapter(wolf);

hunter.hunt(lion);
hunter.hunt(wolfAdapter);
  • hunter는 Lion객체만 사냥할 수 있다.
  • 하지만, 다른 객체도 사냥할 수 있게끔, Wolf를 사냥할 수 있도록 Lion객체를 모방한 WolfAdapter를 통해, 사냥꾼을 변경하지 않고도 wolf를 사냥할 수 있게 만들 수 있다.

3. 브릿지(Bridge)

/**
 * @desc 브릿지 패턴
 *
 * 구현체에서 속성을 분리한다.
 *
 * 여기서는 [테마]에 따른 웹사이트 색상을 예시로 들어볼 것이다.
 *
 * 예를 들어, 페이지 A,B,C가 있고, 테마 1,2,3이 있다면 어떻게 할 것인가
 *   무식하게 한다면, A(1,2,3), B(1,2,3), C(1,2,3)을 모두 구현할 것이다.
 *
 * 여기서 브릿지 패턴을 적용한다면, 테마 1,2,3을 별도 분리하여, 활용할 때 가져다 쓰게 할 수 있다.
 */

// WebPage속성에서 Theme은 별도의 Type으로 빼놓는다.
type WebPage = {
  theme: Theme;
  getContent(): string;
};

class About implements WebPage {
  theme: Theme;

  constructor(theme: Theme) {
    this.theme = theme;
  }

  getContent(): string {
    return "About Page in " + this.theme.getColor();
  }
}

type Theme = {
  getColor(): string;
};

class DarkTheme implements Theme {
  getColor(): string {
    return "Dark";
  }
}

class WhiteTheme implements Theme {
  getColor(): string {
    return "White";
  }
}

// Usage
const dark = new DarkTheme();
const white = new WhiteTheme();

const aboutPage_Black = new About(dark);
const aboutPage_White = new About(white);

console.log(aboutPage_Black.getContent());
console.log(aboutPage_White.getContent());

Property를 별도분리하여, 활용할 때 조립하는 방식
추상화<->구현체를 분리

3. 컴포지트(Composite)

복합체 패턴, 복합객체와 단일객체를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는 구조 패턴 (...?, 명확히 이해가 되지 않는다)

  • 아마도, 공통된 속성을 기반으로 활용할 수 있게 하는 패턴인 것 같은데,
  • 예시를 들어보자면, 모든 사람들은 조금씩 다르지만 Age를 가진다 -> 20대가 몇명이지? 라고 구해볼 수 있는 것같다.

4. 데코레이터(Decorator)

객체를 Wrapping함으로써, 동적으로 객체를 변경할 수 있게 한다.

type Coffee = {
  cost: number;
};

class BasicCoffee implements Coffee {
  cost = 0;
}

class Americano implements Coffee {
  cost: number;
  constructor(coffee: Coffee) {
    this.cost = 20;
  }
}

class Latte implements Coffee {
  cost: number;
  constructor(coffee: Coffee) {
    this.cost = 40;
  }
}

let coffee = new BasicCoffee();

coffee = new Americano(coffee);
console.log(coffee.cost);

coffee = new Latte(coffee);
console.log(coffee.cost);

속성이 동일한 인스턴스에 한해서, 객체의 변경을 객체를 Wrap하여 바꾼다.

5. 퍼사드(Facade)

마치 물위의 백조와 같다

  • 여러 동작들을 하나의 함수로 묶어서, 클라이언트가 사용하기 쉽게 단순화하는 패턴이다.
  • 예를 들어, 컴퓨터를 켜본다고 생각해보자.
    • 우리는 컴퓨터 전원을 켜기 위해 전원버튼만 누르면 된다 (간단~)
    • 하지만, 컴퓨터 내부동작은 그렇지 않다.
      • 전원공급
      • bios세팅
      • os ...
class Computer {
  전원공급() {
    console.log("전원!");
  }
  소리내기() {
    console.log("소리!");
  }
  화면켜기() {
    console.log("화면!");
  }
  준비완료() {
    console.log("준비!");
  }
  BIOS세팅() {
    console.log("BIOS!");
  }
  OS() {
    console.log("OS!");
  }
}

class ComputerFacade {
  computer: Computer;

  constructor(computer: Computer) {
    this.computer = computer;
  }

  turnOn() {
    this.computer.BIOS세팅();
    this.computer.OS();
    this.computer.소리내기();
    this.computer.전원공급();
    this.computer.화면켜기();
    this.computer.준비완료();
  }
}

const computer = new Computer();
const user = new ComputerFacade(computer);

user.turnOn();

6. 플라이웨이트(Flyweight)

공유! 유사한 객체를 공유하여 메모리, 비용을 최소화

class Coffee {
  진함 = 0;
}

class CoffeeMaker {
  makedCoffee: Map<string, Coffee> = new Map();

  make(key: string): Coffee {
    // 만들어진 커피가 없다면, 커피를 만들어둔다.
    if (!this.makedCoffee.has(key)) {
      this.makedCoffee.set(key, new Coffee());
    }

    return this.makedCoffee.get(key)!;
  }
}

const coffeeMaker = new CoffeeMaker();

const coffeeList = [
  coffeeMaker.make("아메리카노"),
  coffeeMaker.make("라떼"),
  coffeeMaker.make("아메리카노"),
  coffeeMaker.make("아메리카노"),
];

// 하지만 객체공유 이기에, 다른 객체까지 영향이 간다.
coffeeList[0].진함 = 2;
console.log(coffeeList);

⚠️ 비용 최소화 관점에서 공유하는 것은 좋으나, 객체 변경에 따라 다른 객체까지 변경되므로 주의해서 사용해야 할 것 같다.

7. 프록시(Proxy)

하나의 클래스가 다른 클래스의 기능을 나타내는 것
👍 이런 경우에 활용할 수 있다.

  • 특정 객체의 조건 확인
  • 추가기능 제공
type Door = {
  open(): void;
  close(): void;
};

class HomeDoor implements Door {
  open() {
    console.log("Home Door Open");
  }

  close() {
    console.log("Home Door Closed");
  }
}

class SecureDoor implements Door {
  isLock = true;
  door: Door;

  constructor(door: Door) {
    this.door = door;
  }

  unLock(password: string) {
    if (password == "secret") this.isLock = false;
  }

  open() {
    // 프록시 패턴 : 조건 확인
    if (this.isLock) {
      console.log("Door is Locked");
    } else {
      this.door.open();
    }
  }

  close() {
    this.door.close();
  }
}

// 집에 문을 달았다.
const door = new HomeDoor();

door.open();
door.close();

// 하지만, 우리집은 누구나 열수 있기에, 도어락을 설치했다.
const secureDoor = new SecureDoor(door);
secureDoor.open();
// 이제 우리집은 암호를 풀어야만 열 수 있다.

secureDoor.unLock("secret");
secureDoor.open();

profile
더 나은 개발경험을 생각하는, 프론트엔드 개발자입니다.

0개의 댓글