零.덕 타이핑 (API 계약)
시작하기 전에 재미있는 것을 살펴봅시다, 덕 타이핑 간단히 말해 "행동이 타입을 결정한다"입니다.
Java 등의 강제 컴파일 시 타입 체크를 가진 언어와는 달리, JavaScript 는 동적 타입이며, 덕 타이핑을 지원합니다. 예를 들어:
function getDinner(chief) {
chief.cook('DINNER');
}
var littleBoy = {
cook: function() {
// ...
}
}
var dog = {
wang: {
// ...
}
}
var dinner1 = getDinner(littleBoy);
// var dinner2 = getDinner(dog); // Uncaught TypeError: chief.cook is not a function
getDinner 메서드를 정의하고, 파라미터는 chief 이어야 하며, 의미적으로 타입 Chief 를 제약합니다. 이 타입의 정의는 cook 메서드가 있으면 모두 해당합니다. 위의 테스트 결과에서 알 수 있습니다: 타입 체크는 실행 시 (Runtime) 에 발생하며, 타입이 일치하지 않으면 Error 를 스로우합니다. "행동이 타입을 결정한다"没错吧?
一.팩토리 패턴
function LittleDog() {
// ...
}
function LovelyDog() {
// ...
}
function BeautifulDog() {
// ...
}
function DogFactory() {}
DogFactory.prototype.type = LittleDog; // 디폴트로 LittleDog 를 생성
DogFactory.prototype.getDog = function(spec) {
spec = spec || {};
if (spec.type === 'LovelyDog') {
this.type = LovelyDog;
}
else if (spec.type === 'BeautifulDog') {
this.type = BeautifulDog;
}
return new this.type(spec);
}
// test
var dogFactory = new DogFactory();
var dog1 = dogFactory.getDog(); // 디폴트의 littledog 를 생성
var dog2 = dogFactory.getDog({
type: 'LovelyDog',
attr: 'val' // 기타 초기화 데이터, spec 에서 구체적인 생성자에 전달
});
위의 구현은 JavaScript 판의 팩토리 패턴으로, 팩토리의 모든 속성을 프로토타입 객체에 배치하고, getDog 내부에서 프로토타입 객체상의 type 을 오버라이트합니다. 조금 싱글톤 패턴의 味道가 있습니다.
二.팩토리 패턴의 장단점
###1.장점
- 객체 생성 프로세스의 복잡도를 저감
팩토리는 객체와 현재의 환경의 연락을 차폐하고, 팩토리를 호출하여 객체를 생성할 때는 현재의 환경을 고려할 필요가 없습니다
- 유사한 소형 객체 또는 컴포넌트의 관리가 용이
객체/컴포넌트 간에 약간의 차이만 있는 경우, 팩토리 패턴을 사용하여 직접 객체를 생산하는 것은 상속보다 더 나은 선택입니다
- "덕 타입"의 객체를 생산하기 쉬움
예를 들어 덕 타입 Chief 를 정의한 경우, 팩토리 메서드를 사용하여 해당 타입의 객체를 생산할 수 있습니다 (팩토리 메서드 내에서 객체에 cook 메서드를 추가하기만 하면 됨), 타입을 정의하기 위해 타입을 정의할 필요가 없습니다
###2.단점
- 추가의 오버헤드가 존재
팩토리 패턴의 본질은 한 조의 관련 생성자의 이차 캡슐화이므로, 추가의 오버헤드를 가져옵니다 (층이 1 개 증가)
- 유닛 테스트에 불리
객체 생성의 프로세스는 팩토리 뒤에 숨겨져 있으며, 객체 생성 프로세스가 매우 복잡한 경우, 유닛 테스트에 문제를 가져올 수 있습니다
三.추상 팩토리 패턴
JavaScript 에서도 추상 팩토리 패턴을 구현할 수 있으며, 게다가 구현은 Java 보다도 심플합니다. 샘플 코드는 다음과 같습니다:
var factory = (function() {
// 存储车辆类型
var types = {};
return {
getVechicle: function(type, customizations) {
var Vechicle = types[type];
return (Vechicle) ? new Vechicle(customizations) : null;
},
registerVechicle: function(type, Vechicle) {
var proto = Vechicle.prototype;
// 只注册实现车辆契约的类
if (proto.drive && proto.breakDown) {
types[type] = Vechicle;
}
}
}
})();
추상 팩토리는 registerXXX 인터페이스를 제공하여 생성 메서드를 통일 관리하고, 내부에서 "API 계약"으로 타입을 제약합니다. 생성 메서드를 등록한 후 getVechicle 을 호출하여 객체를 생성하기만 하면 됩니다. 예를 들어:
// 构造方法(简单起见,直接返回参数对象)
function Car(arg) {
return arg;
}
Car.prototype.drive = function() {};
Car.prototype.breakDown = function() {};
function Truck(arg) {
return arg;
}
Truck.prototype = Car.prototype;
// 用法
factory.registerVechicle('car', Car);
factory.registerVechicle('truck', Truck);
// 基于抽象车辆类型实例化一个新 car 对象
var car = factory.getVechicle('car', {
color: 'lime green',
state: 'like new'
});
// 同理,实例化一个新 truck 对象
var truck = factory.getVechicle('truck', {
wheelSize: 'medium',
color: 'neon yellow'
});
console.log(car);
console.log(truck);
팩토리 패턴의 첫 번째 예와 비교하면, 여기서는 생성자와 팩토리를 분리했을 뿐입니다 (if(type === 'xxx') 를 factory.create 에서 꺼냄), 생성자를 유연하게 등록할 수 있습니다
고전적인 추상 팩토리 패턴의 의미와는 크게 다르며, 최대해도 팩토리 패턴의 일종의 강화 (최적화) 로 간주할 정도이지만, 그래도 상관없습니다. 결국 디자인 패턴을 사용하기 위해 사용하는 것이 아니며, 적당한 결합 해제 의 목적을 달성하면 좋기 때문입니다
추상 팩토리 패턴에 관한 더욱 많은 정보는, 참고 자료 부분에서 인용한 2 편의 블로그 기사를 참조
참고 자료
-
《JavaScript 디자인 패턴》
-
[디자인 패턴 之 팩토리 패턴 (Factory Pattern)](/articles/디자인 패턴 之 팩토리 패턴 (factory-pattern)/)
아직 댓글이 없습니다