不了解 SOLID 的可以扫一眼这个:SOLID,面向对象设计五大基本原则。
今天扫 SOLID JS 的时候,授课者对 OCP 的一个实现方式利用了 Specification Pattern,所以这里也进行一下补充学习。
不过讲道理来说,因为 JavaScript 是一个弱类型的语言,所以很难强制让子类去实现其他语言中 interface/abstract 的函数,这也是一个局限了。
使用案例为,假设需要实现一个过滤器(filter),最初的实现方式如下:
const COLOR = Object.freeze({
RED: 'red',
GREEN: 'green',
BLUE: 'blue',
});
const SIZE = Object.freeze({
SMALL: 'small',
MEDIUM: 'medium',
LARGE: 'large',
});
class Product {
constructor(name, color, size) {
this.name = name;
this.color = color;
this.size = size;
}
}
class ProductFilter {
filterByColor(products, color) {
return products.filter((p) => p.color === color);
}
filterBySize(products, size) {
return products.filter((p) => p.size === size);
}
}
const apple = new Product('Apple', COLOR.GREEN, SIZE.SMALL);
const tree = new Product('Tree', COLOR.GREEN, SIZE.LARGE);
const house = new Product('House', COLOR.BLUE, SIZE.LARGE);
const products = [apple, tree, house];
const pf = new ProductFilter();
console.log('Green products:');
for (const p of pf.filterByColor(products, COLOR.GREEN)) {
console.log(` * ${p.name} is green.`);
}
可以看到之后如果要写其他的 filter,也是可以继续在 ProductFilter 实现的,不过这里有几个问题:
一个解决这个问题的方法是使用 specification pattern,修改代码如下:
// should be done through inheritance, but js doesn't reenforce inheritance
// class Specification {}
class ColorSpecification {
constructor(color) {
this.color = color;
}
isSatisfied(item) {
return item.color === this.color;
}
}
class SizeSpecification {
constructor(size) {
this.size = size;
}
isSatisfied(item) {
return item.size === this.size;
}
}
class FilterSpecs {
filter(items, spec) {
return items.filter((x) => spec.isSatisfied(x));
}
}
const fs = new FilterSpecs();
console.log('Green products with specs:');
for (const p of fs.filter(products, new ColorSpecification(COLOR.GREEN))) {
console.log(` * ${p.name} is green.`);
}
对于 AND 操作的实现补充如下:
class AndSpecifications {
constructor(...specs) {
this.specs = specs;
}
isSatisfied(item) {
return this.specs.every((x) => x.isSatisfied(item));
}
}
console.log('Large and green products:');
const specs = new AndSpecifications(
new ColorSpecification(COLOR.GREEN),
new SizeSpecification(SIZE.LARGE)
);
for (const p of fs.filter(products, specs)) {
console.log(` * ${p.name} is green and large.`);
}
使用 specification pattern 的优势就在于极大地减少了函数的实现,原本对于 AND 的操作,3 个条件就需要实现 7 个方法,而后如果有更多的需求,就需要些更多的方法去满足这些需求,对于
n
n
n 个需求来说,就会有
n
+
n
−
1
,
+
.
.
.
+
1
n + n-1, + ... + 1
n+n−1,+...+1,也就是
n
2
n^2
n2 个方法。。使用了 specification pattern 现在只需要实现 3+1 个,并且如果之后有其他的需求了,在实现对应的基类后,只需要调用 AndSpecifications 并将对应的 specs 传进去就行,换言之,只需要实现
n
+
1
n+1
n+1 个方法。
同样的,如果对 OR,XOR 操作也有需求,那么 specification pattern 就能省下大量的代码和开发时间。
wiki 上提供了不少的代码案例,包括 C#,Python,C++和 TS。