디자인 패턴 - Factory Method

코이그·2023년 5월 10일

노드반 스터디

목록 보기
3/5

a.k.a 가상 생성자

공장에서 상품을 생산하듯이 팩토리 메소드로 비슷한 형태의 (공통의 성질을 가진) 객체들을 찍어낸다

객체를 생성할 때 어떤 클래스의 인스턴스를 만들 지 서브클래스에서 결정함

의도

부모 클래스: 인터페이스
자식 클래스: 인터페이스를 바탕으로 클래스를 구현하여 객체의 유형을 변경할 수 있음

인터페이스

자바스크립트에서는 인터페이스라는 개념이 존재하지 않지만 알아두면 나쁠 건 없다!

인터페이스는 클래스의 틀이다.

클래스가 어떤 속성을 갖고 어떤 메소드를 구현해야 하는지 지정은 해주지만 직접 구현은 하지 못한다.

예시)

Interface Transport {
	type: String,
	route: String,
	deliver: Function
}

(작동하는 코드 아님)
Transport라는 인터페이스를 만들어서 type, route가 String 타입으로 존재해야 하고, deliver라는 메소드가 구현되어야 한다.

class Ship extends Transport {
	type: "ship",
	route: "ocean",
	deliver: function () {
		console.log("I am " + transportation + " and deliver by " + route);
	}
}

Ship이라는 클래스는 Transport 인터페이스를 구현한다.
이 때 필수로 있어야 하는 transportation, route, deliver를 각각 정의한다.

문제

가정)
1. 물류 관리 앱의 첫 번째 버전: 트럭 운송만 처리 (Truck 클래스 생성 / 구현)

class Truck {
	constructor(type) {
		this.type: type,
		this.route: "land",
		this.deliver: function () {
			console.log(`${this.type}: delivers by ${this.route}.`)
		}
	}
}
  1. 선박 운송을 추가하려면 Ship이라는 클래스를 만들고 전체 코드 베이스를 변경해야됨.
class Ship {
	constructor(type) {
		this.type: type,
		this.route: "ocean",
		this.deliver: function () {
			console.log(`${this.type}: delivers by ${this.route}.`)
		}
	}
}
  1. 그 외에 다른 교통 수단이 추가될 때마다 2번을 반복해야됨.

해결책

생성자 함수의 직접 호출을 팩토리 메소드에 대한 호출로 대체.
* 제품: 팩토리 메소드에서 반환된 객체

코드

// 1
class Factory {
	constructor() {
		// 2
	    this.createTransport = function (type) {
	        let transport;

			// 2-1
	        if (type === "truck") {
	            transport = new Truck();
	        } else if (type === "ship") {
	            transport = new Ship();
	        } else if (type === "plane") {
	            transport = new Plane();
	        }
	        // 2-2
	        transport.type = type;

			// 2-3
	        transport.deliver = function () {
	            console.log(`${transport.type}: delivers by ${transport.route}.`);
	        }

	        return transport;
	    }
	}
}

// 3
class Truck {
	constructor() {
		this.route = "land";
	}
}

// 3
class Ship {
	constructor() {
		this.route = "ocean";
	}
}

// 3
class Plane {
	constructor() {
		this.route = "sky";
	}
}

let transports = [];
const factory = new Factory();

transports.push(factory.createTransport("truck"));
transports.push(factory.createTransport("ship"));
transports.push(factory.createTransport("plane"));

for (var i = 0, len = transports.length; i < len; i++) {
	transports[i].deliver();
}
  1. Factory 클래스는 제품(인스턴스/객체)을 찍어내는 공장(Creator)이다.
  2. createTransport 메소드가 바로 팩토리 메소드이다.
    2-1. 어떤 운송수단을 만들 것인지 type을 받아서 그에 해당하는 운송수단(객체)을 생성한다.
    2-2. 운송수단의 type을 할당한다.
    2-3. 운송수단의 deliver 메소드를 구현한다.
  3. 팩토리 메소드가 반환할 운송수단의 클래스를 정의한다.
    • 생성자에서 type을 굳이 할당하지 않는 이유는 해당 작업을 팩토리 메소드에서 수행하기 때문이다.

공장에서 truck, ship, plane 운송수단을 찍어내서 transports라는 배열에 담는다.
배열의 각 요소에는 객체가 들어있을 것이고 deliver를 호출하면 객체마다 다른 출력을 한다.

truck: delivers by land.
ship: delivers by ocean.
plane: delivers by sky.

언제 팩토리 메소드를 사용하는가?

  1. 팩토리 메소드의 핵심 목표는 확장성이다.

  2. 공통적인 성질을 가진 객체들을 다룰 때 주로 사용한다.

    • 위의 예시에서는 Truck, Ship, Plane 객체들에 전부 공통적인 속성과 메소드가 존재한다.
  3. 클라이언트는 원하는 객체를 받되 공장에 어떤 객체들이 또 있는지 알게 하면 안된다.

    • 공장에 제품을 의뢰할 때 공장이 어떤 제품들을 찍어낼 수 있는 지 알게 하면 안된다.
  4. 객체의 생성(인스턴스화)는 클라이언트에게 위임하되 인스턴스화할 유형에 대한 제어는 공장이 유지한다.

장단점

장점

  1. 공장과 제품의 확실한 분리
  2. 단일 책임 원칙. 제품 생성(클래스 구현) 코드를 다른 위치로 옮겨서 더 쉬운 유지보수 가능
  3. 개방/폐쇄 원칙. 기존 코드의 훼손 없이 제품을 추가할 수 있음

단점

제품(클래스)가 많아질수록 코드가 길어지고 복잡해진다.
가장 좋은 방법은 크리에이터 클래스(공장)의 기존 계층구조에 패턴을 도입하는 것이다.

참고

리팩토링구루
dofactory

profile
COYG🔴⚪

0개의 댓글