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();