zoukankan      html  css  js  c++  java
  • 面向对象和函数式

    阅读前,请先封印以下能力:类、闭包、继承&多态、高阶函数……

    现在,你只会全局变量和函数,开始写一个带 cache 的 Fibonacci。

    const cache = new Map();
    
    const fib = n => {
      if (cache.has(n)) {
        console.log("use cache", n);
        return cache.get(n);
      } else {
        let result;
        if (n === 1 || n === 2) result = 1;
        else result = fib(n - 1) + fib(n - 2);
        cache.set(n, result);
        return result;
      }
    };
    
    fib(10);
    

    再要求你写几十个类似的函数,你会陷入两难的境地:是把全局变量定义在操作它的函数附近,还是把全体全局变量定义在一处好?

    • 把全局变量定义在操作它的函数附近,容易因为变量名冲突造成程序错误。
    • 把全局变量定义在一处,代码不好拆分成独立文件,导致不好复用。

    chaos

    引入命名空间是缓解全局变量污染的解法,使用面向对象的类是消除全局变量的解法。

    类把变量和操作变量的函数聚在一起,变量不再是全局的,从而减少了全局变量。

    class

    class FibCalculator {
      #cache = new Map();
    
      calc(n) {
        if (this.#cache.has(n)) {
          console.log("use cache", n);
          return this.#cache.get(n);
        } else {
          let result;
          if (n === 1 || n === 2) result = 1;
          else
            result = this.calc(n - 1) + this.calc(n - 2);
          this.#cache.set(n, result);
          return result;
        }
      }
    }
    
    const fib = new FibCalculator();
    fib.calc(10);
    

    函数的闭包也一样,把变量和操作变量的函数聚在一起,变量不再是全局的。

    const fib = (function () {
      const cache = new Map();
    
      const fib = n => {
        if (cache.has(n)) {
          console.log("use cache", n);
          return cache.get(n);
        } else {
          let result;
          if (n === 1 || n === 2) result = 1;
          else result = fib(n - 1) + fib(n - 2);
          cache.set(n, result);
          return result;
        }
      };
    
      return fib;
    })();
    

    闭包等价于「只有一个函数的对象」,可以用闭包替代下图中的 class Aclass B

    class-closure


    类、闭包解决了全局变量的问题,我们再来谈代码复用的问题,有两种复用:

    1. 复用整个代码块
    2. 复用代码块的流程

    还以这段 Fibonacci 为例:

    class FibCalculator {
      #cache = new Map();
    
      calc(n) {
        if (this.#cache.has(n)) {
          console.log("use cache", n);
          return this.#cache.get(n);
        } else {
          let result;
          if (n === 1 || n === 2) result = 1;
          else
            result = this.calc(n - 1) + this.calc(n - 2);
          this.#cache.set(n, result);
          return result;
        }
      }
    }
    
    const fib = new FibCalculator();
    fib.calc(10);
    

    程序需要计算 Fibonacci 时,可以导入 class, new 出实例,实现复用,这个复用就是「复用整个代码块」。

    另外,不管是计算 Fibonacci 还是计算 Factorial, cache 的逻辑都是一样的:

    • 添加一个 cache 私有变量
    • 计算前先看 cache 中有没有
      • 有就直接返回
      • 没有则计算,计算完了存入 cache,再返回

    复用 cache 的逻辑就是我说的「复用代码块的流程」。

    面向对象是靠继承&多态实现「复用代码块的流程」的。

    class Calculator {
      calc(n) {}
    }
    
    class CachedCalculator extends Calculator {
      #cache = new Map();
      #calculator;
      constructor(calculator) {
        super();
        this.#calculator = calculator;
      }
      calc(n) {
        if (this.#cache.has(n)) {
          console.log("use cache", n);
          return this.#cache.get(n);
        } else {
          const result = this.#calculator.calc(n);
          this.#cache.set(n, result);
          return result;
        }
      }
    }
    
    class FibCalculator extends Calculator {
      calc(n) {
        if (n === 1 || n === 2) return 1;
        else return this.calc(n - 1) + this.calc(n - 2);
      }
    }
    
    class FactorialCaculator extends Calculator {
      calc(n) {
        if (n === 1) return 1;
        else return n * this.calc(n - 1);
      }
    }
    
    const fib = new CachedCalculator(
      new FibCalculator()
    );
    fib.calc(10);
    
    const factorial = new CachedCalculator(
      new FactorialCaculator()
    );
    factorial.calc(10);
    

    有些看官也许看出这版 cache 有问题,递归的部分并没有存入 cache。计算 fib.calc(10),按理说,1-9 都计算了一遍,但 cache 中只存了 10 的结果。代码改进一下,让递归的部分也存入 cache。

    class Calculator {
      calc(n, self) {}
    }
    
    class CachedCalculator extends Calculator {
      #cache = new Map();
      #calculator;
      constructor(calculator) {
        super();
        this.#calculator = calculator;
      }
      calc(n, self = null) {
        if (this.#cache.has(n)) {
          console.log("use cache", n);
          return this.#cache.get(n);
        } else {
          const result = this.#calculator.calc(n, this);
          this.#cache.set(n, result);
          return result;
        }
      }
    }
    
    class FibCalculator extends Calculator {
      calc(n, self) {
        if (n === 1 || n === 2) return 1;
        else return self.calc(n - 1) + self.calc(n - 2);
      }
    }
    
    class FactorialCaculator extends Calculator {
      calc(n, self) {
        if (n === 1) return 1;
        else return n * self.calc(n - 1);
      }
    }
    
    const fib = new CachedCalculator(
      new FibCalculator()
    );
    fib.calc(10);
    
    const factorial = new CachedCalculator(
      new FactorialCaculator()
    );
    factorial.calc(10);
    

    函数式是靠高阶函数「复用代码块的流程」的,之前写过一篇高阶函数的博客,这里就不赘述了,感兴趣的同学可以点这里


    最后,把面向对象和函数式放到表格里对比一下:

    问题 面向对象 函数式
    消除全局变量 类&对象 闭包
    复用代码 继承&多态 高阶函数

    尽管面向对象和函数式代码表现形式不一样,但解决的问题却是同样的。

  • 相关阅读:
    PHP 文件上传
    浅析文件上传漏洞
    JS之Number类
    JS之String类
    Java中的多态
    JS之数据类型
    JavaScript的组成
    双向链表与LRU算法实现
    字符串比较--小问题大智慧
    龙生九子-浅谈Java的继承
  • 原文地址:https://www.cnblogs.com/apolis/p/14592408.html
Copyright © 2011-2022 走看看