5.26 分享 SOLID & IOC

本次分享主题是依赖注入与控制反转。在进入主题前我们可以先复习一下 Robert C. Martin 总结出的面向对象设计的 SOLID 五条设计原则。 这五条原则具有很好的建设性,一般遵循这五条原则进行面向对象编程设计迭代项目,可以尽可能的避免代码出現坏味道,提升代码的质量和可维护性。

SOLID 简单介绍

S - Single-responsibility Principle 单一职责原则

There should never be more than one reason for a class to change.

单一职责指出:一个类的的实现最好只做一件事,换句话说就是需要保证每一个类只负责一类事。

举个例子:有 Square 和 Circle 两个类,我们提供一个计算面积和输出结果的 AreaCalculator 类

class Square {
    constructor(public length: number) { }
}

class Circle {
    constructor(public radius: number) { }
}

class AreaCalculator {
    constructor(protected shapes: (Square|Circle)[]) { }

    public sum() {
        return this.shapes.reduce((sum,shape: Square|Circle,index: number) => {
            if(shape instanceof Square) {
                return sum+=Math.pow(shape.length,2);
            }
            if(shape instanceof Circle) {
                return sum+=Math.PI*Math.pow(shape.radius,2);
            }
            return sum;
        },0)

    }

    public output() {
        console.log('Sum of the areas of provided shapes: '+this.sum())
    }
}

// 使用:

const shapes=[
    new Circle(2),
    new Square(5),
    new Square(6),
];

const areas=new AreaCalculator(shapes);

areas.output();

// 上面的实现的 AreaCalculator 并未遵循 单一职责原则。 (即实现了计算面积的方法,也实现了输出方法)
// 假如我们希望 output 方法能打印出结构化数据,而不止打印出字符串。
// 我们可以做如下改造:
// 把 output 抽象成一个 SumCalculatorOutputter 类

class SumCalculatorOutputter {
    constructor(protected calculator: AreaCalculator) { }

    public toJSON() {
        const data={sum: this.calculator.sum()};
        console.log(JSON.stringify(data));
    }

    public toString() {
        console.log('Sum of the areas of provided shapes: '+this.calculator.sum())
    }
}

// 使用:

const shapes = [
  new Circle(2),
  new Square(5),
  new Square(6),
];

const areas = new AreaCalculator(shapes);
const output = new SumCalculatorOutputter(areas);

output.toJSON();
output.toString();

O - Open-closed Principle 开闭原则

Objects or entities should be open for extension but closed for modification.

开闭原则指出:在面向对象设计结构的时候,保证结构具有可扩展性而无需对其内部进行修改。

回顾之前的 AreaCalculator 类,当我们需要增加新的形状,例如 三角形或者长方形。则需要对 sum 方法进行改写。这违背了开闭原则。

class AreaCalculator {
    public constructor(protected shapes: (Square|Circle)[]) { }

    public sum() {
        return this.shapes.reduce((sum,shape: Square|Circle,index: number) => {
            if(shape instanceof Square) {
                return sum+=Math.pow(shape.length,2);
            }
            if(shape instanceof Circle) {
                return sum+=Math.PI*Math.pow(shape.radius,2);
            }
            return sum;
        },0)

    }
}

// 我们可以考虑把 sum 方法中的 caculator area 方法分散到每一个 shape 类中。
interface IShape{
    area():number;
}

class Square implements IShape{
    constructor(public length: number) { }
    
    public area() {
        return Math.pow(this->length, 2);
    }
}

class Circle implements IShape {
    constructor(public radius: number) { }

    public area() {
        return Math.PI*Math.pow(this.radius,2);
    }
}

class AreaCalculator {
    public constructor(protected shapes: IShape[]) { }

    public sum() {
      return this.shapes.reduce((sum,shape: IShape,index: number) => (sum+shape.area()),0)
    }
}

// 我们还可以添加 Rectangle 类

class Rectangle implements IShape {
    constructor(public width: number,public height: number) { }

    public area() {
        return this.width*this.height;
    }
}