• 规格模式 Specification Pattern


    Specification Pattern

    不了解 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.`);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    可以看到之后如果要写其他的 filter,也是可以继续在 ProductFilter 实现的,不过这里有几个问题:

    1. 继续实现其他的 filter 就要修改现有的代码
    2. 每一次新的需求来了,都需要额外实现新需求,这会引起 state space explosion 的问题,毕竟 3 个变量的 AND 操作,就需要 7 个方法去进行实现(A, B, C, A+B, B+C, A+C, A+B+C)。

    一个解决这个问题的方法是使用 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.`);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    对于 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.`);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用 specification pattern 的优势就在于极大地减少了函数的实现,原本对于 AND 的操作,3 个条件就需要实现 7 个方法,而后如果有更多的需求,就需要些更多的方法去满足这些需求,对于 n n n 个需求来说,就会有 n + n − 1 , + . . . + 1 n + n-1, + ... + 1 n+n1,+...+1,也就是 n 2 n^2 n2 个方法。。使用了 specification pattern 现在只需要实现 3+1 个,并且如果之后有其他的需求了,在实现对应的基类后,只需要调用 AndSpecifications 并将对应的 specs 传进去就行,换言之,只需要实现 n + 1 n+1 n+1 个方法。

    同样的,如果对 OR,XOR 操作也有需求,那么 specification pattern 就能省下大量的代码和开发时间。

    Reference

  • 相关阅读:
    网络安全运维工程师数据库的核心能力有什么?
    【C++】继承 ④ ( 继承对访问控制权限的影响 | 访问权限判定 | 继承不包括构造与析构函数 | 继承改变成员访问权限 )
    SpringBoot+AOP+自定义注解,优雅实现日志记录
    使用Easyocr处理图片遇到的问题合集
    33.nacos客户端读取多配置文件实例(springcloud)
    uniapp中简单的前端文字校验
    前端面试那些事【dt/dd、audio、onerror、标签、类、ID选择器、伪类选择器......
    设计原则学习
    python利用joblib进行并行数据处理
    uni-app点击复制指定内容(点击复制)
  • 原文地址:https://blog.csdn.net/weixin_42938619/article/details/128014246