枚举变继承,状态代多态
来看看这个例子吧:
我们正在做一个影片租赁管理系统,系统有三种影片类型普通影片、儿童影片、新发售影片,它们分别有自己的租赁计费方法 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
子类,单独作为不会飞的鸟类的父类,但是这种做法也不够灵活,不会飞的鸟类有单独的类,不会飞也不会叫的鸟类是否也需要有单独的类,以此类推。
于是就有了第三种,以组合代继承的策略模式:将飞行、游泳、迁徙等存在于部分鸟类中的行为特征单独写成类,在不同的鸟的类中根据是否会飞、是否进行迁徙等来组合这些行为类。