枚举变继承,状态代多态

来看看这个例子吧:

我们正在做一个影片租赁管理系统,系统有三种影片类型普通影片、儿童影片、新发售影片,它们分别有自己的租赁计费方法 getCharge

于是,我们先创建了一个 Movie 类,定义了三种影片类型对应的影片编号和统一的 getCharge 方法:

class Movie {
    public int priceCode;
    public static int REGULAR = 0;
    public static int CHILDRENS = 1;
    public static int NEWRELEASE = 2;

    double getCharge() {
        // ...
    }
}

在计费时,再通过 switch 语句对不同的影片类型采用不同的计费方法:

class Movie {
    // ...
    double getCharge() {
        switch (this.priceCode) {
            case Movie.REGULAR:
                // do something
                break;
            case Movie.CHILDRENS:
                // do something
                break;
            case Movie.NEWRELEASE:
                // do something
                break;
        }
    }
}

来到继承

可以发现,上面的数种影片类型中以不同的方式实现了相同的功能,这听起来很像子类的工作。

于是,我们再针对三种影片类型编写相应的子类,使用多态来取代 switch 语句:

class RegularMovie extends Movie {
    double getCharge() {
        // ...
    }
}
class ChildrensMovie extends Movie {
    double getCharge() {
        // ...
    }
}
class NewReleaseMovie extends Movie {
    double getCharge() {
        // ...
    }
}

使用状态

在使用多个子类来代替原来的枚举之后,又出现了新的问题:由于对象无法修改自己所属的类,一部影片的类型无法被修改。

于是,我们采用 State 模式,去掉上面的几个子类,转而为价格计算单独创建类:

class Price {
    public abstract double getCharge();
}

class RegularPrice extends Price {
    public double getCharge() {
        // ...
    }
}
class ChildrensPrice extends Price {
    public double getCharge() {
        // ...
    }
}
class NewReleasePrice extends Price {
    public double getCharge() {
        // ...
    }
}

再修改 Movie 类:

class Movie {
    // ...

    private Price price;
    public double getCharge() {
        return this.price.getCharge();
    }
}

代码重构实例

假设我要编写一个图表渲染组件,使其能够根据输入的图表类型调用对应的图表渲染库来绘制图表(下面的示例代码仅为伪代码,不使用任何框架的组件语法)。

class ChartBlock {
    constructor(type, content) {
        this.content = content
        // 由于此图表类型来自用户输入,需要设置多个图表别名来方便使用
        switch (type) {
            case "": case "echarts":
                this.type = "echarts"
                break
            case "flow":       case "flowchart":
            case "flow chart": case "flow-chart":
                this.type = "flow-chart"
                break
            case "sequence":      case "sequencechart":
            case "sequence char": case "sequence-chart":
                this.type = "sequence-chart"
                break
            case "gantt":       case "ganttchart":
            case "gantt chart": case "gantt-chart":
                this.type = "gantt-chart"
                break
            case "rail": case "railroad": case "railroadchart":
            case "railroad chart":        case "railroad-chart":
                this.type = "railroad-chart"
                break
            case "qrcode": case "qr-code": case "qr code":
                this.type = "qrcode"
                break
            default:
                const errMsg = "Unknown chart type: " + type
                console.error(errMsg)
                this.type = "unknown"
        }
    }
    render() {
        switch(this.type) {
            case "echarts":
                // 处理图表数据及调用渲染库,下同,
                // 且不同类型图表需要的数据处理方式不同。
                break
            case "flow-chart":
                // do something
                break
            case "sequence-chart":
                // do something
                break
            case "gantt-chart":
                // do something
                break
            case "railroad-chart":
                // do something
                break
            case "qrcode":
                // do something
                break
            case "unknown":
                // do something
                break
        }
    }
}

显然,上述代码中的处理方式使得多种图表的处理代码耦合在一起,提高了这段代码在日后的维护成本。

根据前文的重构方法,我们可以使用多个状态类来代替这段代码中的枚举:

class EchartsChartType {
    constructor(content) {
        // 处理图表数据代码,下同
    }
    render() {
        // 调用渲染库进行图表渲染,下同
    }
}
class FlowChartType {
    constructor(content) {
        // do something
    }
    render() {
        // do something
    }
}
class SequenceChartType {
    constructor(content) {
        // do something
    }
    render() {
        // do something
    }
}
// 其它图表处理相同,在此省略 ...

前文的 ChartBlock 组件也需要对应的修改:

class ChartBlock {
    constructor(type, content) {
        switch (type) {
            case "": case "echarts":
                this.type = new EchartsChartType(content)
                break
            case "flow":       case "flowchart":
            case "flow chart": case "flow-chart":
                this.type = new FlowChartType(content)
                break
            case "sequence":      case "sequencechart":
            case "sequence char": case "sequence-chart":
                this.type = new SequenceChartType(content)
                break
            case "gantt":       case "ganttchart":
            case "gantt chart": case "gantt-chart":
                this.type = new GanttChartType(content)
                break
            case "rail": case "railroad": case "railroadchart":
            case "railroad chart":        case "railroad-chart":
                this.type = new RailroadChartType(content)
                break
            case "qrcode": case "qr-code": case "qr code":
                this.type = new QRCodeChartType(content)
                break
            default:
                const errMsg = "Unknown chart type: " + type
                console.error(errMsg)
                this.type = new UnknownChartType(content)
        }
    }
    render() {
        // 直接调用对应图表类型的渲染函数
        this.type.render()
    }
}

不难看出,本文的这种重构策略使用对象的组合来代替类的继承,将对象的状态(或类型)独立出数个相似的类,从而将与该状态相关的计算独立出来,降低代码的耦合。上面的这种模式被称为状态模式

与之类似地,还有策略模式

策略模式的简单介绍

下面的内容改自菜鸟教程

假设你现在要实现一个鸟类作为所有鸟的基类,你可能会从多数鸟的行为共性来编写这么一个类,而所有的鸟类都为这个类的子类:

class Bird {
    fly() {
        // ...
    }
    birdcall() {
        // ...
    }
    migrate() {
        // ..
    }
    // ...
}

但在具体实现每一种鸟类时,你可能会发现这么个问题:不是所有鸟类都会飞,不是所有鸟类都会迁徙...多数鸟的行为共性可能对于部分鸟类并不适用。

你自然想到,可以在子类中覆盖不需要的方法。但这种做法的弊端很明显:所有不会飞的鸟的子类都需要覆盖 fly 方法,这样重写的工作量是相当大的。

你又想到第二种方法:设计子抽象类。如把不会飞的鸟类单独设计一个 FlightlessBird 子类,单独作为不会飞的鸟类的父类,但是这种做法也不够灵活,不会飞的鸟类有单独的类,不会飞也不会叫的鸟类是否也需要有单独的类,以此类推。

于是就有了第三种,以组合代继承的策略模式:将飞行、游泳、迁徙等存在于部分鸟类中的行为特征单独写成类,在不同的鸟的类中根据是否会飞、是否进行迁徙等来组合这些行为类。

点此查看原文