팩토리 메서드 패턴과 추상 팩토리 패턴

dev_hobin·2022년 4월 10일
0

디자인 패턴

목록 보기
1/2
post-thumbnail

팩토리 메서드 패턴과 추상 팩토리 패턴

  • 팩토리 메서드 패턴 - 구체적으로 어떤 객체를 만들것인지에 대한 책임을 서브 클래스에게 넘기는 패턴
  • 추상 팩토리 패턴 - 팩토리가 구체적인 객체에 의존하지 않도록 팩토리 자체를 추상화하는 패턴

배를 만드는 공장

  • 배를 만드는 공장을 코드로 구현해보며 패턴을 이해해보자
type ShipType = "whiteShip" | "blackShip";
// 배 클래스
class Ship {
  #type: ShipType;
  #color: string;
  #logo: string;
  #wheel: Wheel;
  #anchor: Anchor;

  constructor(
    type: ShipType,
    color: string,
    logo: string,
    wheel: Wheel,
    anchor: Anchor,
  ) {
    this.#type = type;
    this.#color = color;
    this.#logo = logo;
    this.#wheel = wheel;
    this.#anchor = anchor;
  }
  
  get type() {
    return this.#type;
  }
  get color() {
    return this.#color;
  }
  get logo() {
    return this.#logo;
  }
  get wheel() {
    return this.#wheel;
  }
  get anchor() {
    return this.#anchor;
  }
}
// 배를 만드는 팩토리 클래스
class ShipFactory {
  public static orderShip(type: ShipType): Ship {
    if (type === 'whiteShip') {
      return new Ship(type, "white", "whiteLogo", new WhiteWheel(), new WhiteAnchor());
    } else if (type === 'blackShip') {
      return new Ship(type, "white", "whiteLogo", new BlackWheel(), new BlackAnchor());
    } else {
      throw new Error(`type 에러: 존재하지 않은 타입: ${type}`);
    }
  }
}
// 클라이언트 코드 (팩토리를 이용하는 부분)
// 어떤 종류의 배를 만들지를 인자로 넘겨준다
const whiteShip: Ship = ShipFactory.orderShip('whiteShip');
const blackShip: Ship = ShipFactory.orderShip('blackShip');

코드의 문제점

class ShipFactory {
  // 1. 여러 type 의 Ship 을 만드는 책임을 ShipFactory 클래스 하나가 모두 책임지고 있다
  // = Ship 의 종류가 추가될수록 내부 로직이 더 복잡해진다
  // 2. ShipFactory 에서 Ship 이라는 구체적인 클래스를 사용하고 있다.
  // = Ship 클래스가 변경된다면 ShipFactory도 함께 변경될 가능성이 높다
  public static orderShip(type: ShipType): Ship {
    if (type === 'whiteShip') {
      return new Ship(type, "white", "whiteLogo", new WhiteWheel(), new WhiteAnchor());
    } else if (type === 'blackShip') {
      return new Ship(type, "white", "whiteLogo", new BlackWheel(), new BlackAnchor());
    } else {
      throw new Error(`type 에러: 존재하지 않은 타입: ${type}`);
    }
  }
}

팩토리 메서드 패턴 적용

// ShipFactory 를 추상 클래스로 변경
abstract class ShipFactory {
  // 서브 클래스에게 구체적인 구현을 넘김 -> ShipFactory 자체의 복잡성이 줄어들었다
  abstract createShip(): Ship;
}
// Ship 객체를 만들어주는 팩토리 클래스들 (기존의 ShipFactory의 책임을 분산해서 가져감)
// 이렇게 함으로써 Ship의 종류가 추가되더라도 기존의 코드를 변경할 필요없이 클래스를 추가해나가는 식으로 기능의 확장이 가능해진다 (OCP)
class WhiteShipFactory extends ShipFactory {
  // 어떤 종류의 배를 만들지를 결정
  createShip(): WhiteShip {
    return new WhiteShip();
  }
}
class BlackShipFactory extends ShipFactory {
  createShip(): BlackShip {
    return new BlackShip();
  }
}

class WhiteShip extends Ship {
  constructor() {
    super('whiteShip', 'white', 'whiteLogo', new WhiteWheel(), new WhiteAnchor());
  }
}
class BlackShip extends Ship {
  constructor() {
    super('blackShip', 'black', 'blackLogo', new BlackWheel(), new BlackAnchor());
  }
}
// 클라이언트 코드
// 어떤 팩토리를 사용하는지에 따라 만들어지는 배의 타입이 결정된다
const whiteShip: WhiteShip = new WhiteShipFactory().createShip();
const blackShip: BlackShip = new BlackShipFactory().createShip();

기능 추가

지금까지는 배의 종류 하나만 정해지면 배를 만드는 데 필요한 부품들이 모두 함께 정해졌다.
하지만 같은 종류의 배라도 여러 종류의 부품을 장착할 수 있게된다면 기존의 코드에 어떤 문제점이 있을까?
같은 종류의 배라도 여러 종류의 Wheel과 Anchor를 장착할 수 있다고 생각해보자 (WhiteShip만 생각)

// Wheel, Anchor 종류 추가
interface Wheel {}
class WhiteWheel implements Wheel {}
class WhiteWheelPro implements Wheel {}

interface Anchor {}
class WhiteAnchor implements Anchor {}
class WhiteAnchorPro implements Anchor {}

코드의 문제점

class WhiteShipFactory extends ShipFactory {
  createShip(): WhiteShip {
    // 1. 특정 타입의 배를 생성해주는 역할만 하며 Wheel과 Anchor 부품에 대한 책임이 없다
    return new WhiteShip();
  }
}

class WhiteShip extends Ship {
  constructor() {
    // 2. 생성자가 구체적인 특정 객체(WhiteWheel, WhiteAnchor)에 너무 강하게 결합되어있다 -> 이것을 사용하는 팩토리 클래스도 커플링이 심해진다
    super('whiteShip', 'white', 'whiteLogo', new WhiteWheel(), new WhiteAnchor());
  }
}

추상 팩토리 패턴 적용

// 추상 팩토리: 어떤 부품을 만들것인지에 대한 추상화
interface ShipPartsFactory {
  createAnchor(): Anchor;
  createWheel(): Wheel;
}
// 어떤 부품을 만들지 결정하는 구체적인 팩토리 클래스들
class WhitePartsProFactory implements ShipPartsFactory {
  createAnchor(): Anchor {
    return new WhiteAnchorPro();
  }
  createWheel(): Wheel {
    return new WhiteWheelPro();
  }
}
class WhitePartsFactory implements ShipPartsFactory {
  createAnchor(): Anchor {
    return new WhiteAnchor();
  }
  createWheel(): Wheel {
    return new WhiteWheel();
  }
}
class WhiteShipFactory extends ShipFactory {
  #shipPartsFactory: ShipPartsFactory;
  constructor(shipPartsFactory: ShipPartsFactory) {
    super();
    // 장착할 부품을 만들어주는 팩토리를 외부에서 주입 받는다
    this.#shipPartsFactory = shipPartsFactory;
  }

  createShip(): WhiteShip {
    // 주입받은 팩토리가 알아서 부품 생성 -> 부품에 관련된 코드가 변경되더라도 WhiteShipFactory 클래스는 수정될 가능성이 없어졌다.
    return new WhiteShip(
      this.#shipPartsFactory.createWheel(),
      this.#shipPartsFactory.createAnchor(),
    );
  }
}

class WhiteShip extends Ship {
 	// 변경 가능한 부품들을 외부에서 주입받도록 변경
  constructor(wheel: Wheel, anchor: Anchor) {
    super('whiteShip', 'white', 'whiteLogo', wheel, anchor);
  }
}
// 클라이언트 코드
const whiteShip = new WhiteShipFactory(new WhitePartsFactory()).createShip();
const whiteProShip = new WhiteShipFactory(new WhitePartsProFactory()).createShip();
profile
무엇을 기억할지 고민하는 것이 공부다

0개의 댓글