《重构 改善既有代码的设计》 读书笔记(五)

1.4 运用多态取代与价格相关的条件逻辑

1.4.1 getCharge()的再加工

回顾一下这个方法:

//Rental类中
/**
 * 获取收费的金额
 * 
 * @author newre
 * @return
 */
public double getCharge() {
	double result = 0;
	switch (this.getMovie().getPriceCode()) {
		case Movie.REGULAR :
			result += 2;
			if (this.getDayRented() > 2)
				result += (this.getDayRented() - 2) * 1.5;
			break;
		case Movie.NEWRELEASE :
			result += this.getDayRented() * 3;
			break;
		case Movie.CHILDRENS :
			result += 1.5;
			if (this.getDayRented() > 3)
				result += (this.getDayRented() - 3) * 1.5;
			break;
		default :
			break;
	}
	return result;
}

我们仔细想想搬移函数的法则:

当A类中某部分代码与B类交往密切时,就需要考虑把这部分代码搬移到B类中去。

问题就出现在switch语句上。最好不要在另一个对象的属性基础上使用switch语句。如果不得不使用,也应该在对象自身数据上使用,而非寄居在别人那里。

重构需要解耦,哪怕方法变得越来越多也在所不惜。

在此时,getCharge()方法的主体又要发生移动——从Rental移动到Movie类中。在充分理解之前内容的前提下,这一步易如反掌。

//Movie类
/**
 * 根据天数及影片类型计算价格
 * 
 * @author newre
 * @param dayRented
 * @return
 */
public double getCharge(int dayRented) {
	double result = 0;
	switch (this.getPriceCode()) {
		case Movie.REGULAR :
			result += 2;
			if (dayRented > 2)
				result += (dayRented - 2) * 1.5;
			break;
		case Movie.NEWRELEASE :
			result += dayRented * 3;
			break;
		case Movie.CHILDRENS :
			result += 1.5;
			if (dayRented > 3)
				result += (dayRented - 3) * 1.5;
			break;
		default :
			break;
	}
	return result;
}

//Rental类
public double getCharge() {
	return this.getMovie()
        	.getCharge(this.getDayRented());
}

方法中的result变量其实是有些碍眼的,想办法去掉(这里我用的是三元,也可以用if的形式,但是原书上没有进行这一步简化):

public double getCharge(int dayRented) {
	switch (this.getPriceCode()) {
		case Movie.REGULAR :
			return dayRented > 2 ? 2 + (dayRented - 2) * 1.5 : 2;
		case Movie.NEWRELEASE :
			return dayRented * 3;
		case Movie.CHILDRENS :
			return dayRented > 3 ? 1.5 + (dayRented - 3) * 1.5 : 1.5;
		default:
			return 0;
	}
}

再观察Rental类中的getFrequentRenterPoint()方法,发现里面也有Movie对象的成分。

此处需要说明:之所以非要把Movie的类型都集中到本类中,是为了更好地满足‘可能会更改影片类型’这个需求。

//Movie类
/**
 * 获取常客积分
 * 
 * @author newre
 * @param dayRented
 * @return
 */
public int getFrequentRenterPoint(int dayRented) {
	if ((this.getPriceCode() == Movie.NEWRELEASE) && dayRented > 1)
		return 2;
	else
		return 1;
}

//Rental类
public int getFrequentRenterPoint() {
	return this.getMovie()
        .getFrequentRenterPoint(this.getDayRented());
}

再看看到目前为止,我们的类图变成了什么样:

/'在线作图(UML)网址:
http://www.plantuml.com/plantuml/uml/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000
如果要修改的的话,打开网址后,直接复制上图片链接(或者粘贴下方代码)修改即可'/
@startuml
Title "影片出租店程序"
class Movie{
- private int priceCode
+ public getCharge(int dayRented)
+ public getFrequentRenterPoint(int dayRented)
}
class Rental{
- private int daysRented
+ public getCharge()
+ public getFrequentRenterPoint()
}
class Customer{
+ public statement()
+ private getTotal()
+ private getTotalFrequentRenterPoints()
}
Rental --> Movie
Customer --> Rental
@enduml

在这里插入图片描述

1.4.2 getCharge()的继承处理

父类是Movie,子类是不同类型的Movie,子类继承父类的getCharge()方法,这是多态的一种应用。

看一下类图:

/'在线作图(UML)网址:
http://www.plantuml.com/plantuml/uml/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000
如果要修改的的话,打开网址后,直接复制上图片链接(或者粘贴下方代码)修改即可'/
@startuml
Title "影片父与子"
class Movie{
- private int priceCode
+ public getCharge()
+ public getFrequentRenterPoint()
}
class RegularMovie{
+ public getCharge()
+ public getFrequentRenterPoint()
}
class ChildrensMovie{
+ public getCharge()
+ public getFrequentRenterPoint()
}
class NewReleaseMovie{
+ public getCharge()
+ public getFrequentRenterPoint()
}
Movie <|-- RegularMovie
Movie  <|-- ChildrensMovie
Movie  <|-- NewReleaseMovie
@enduml

在这里插入图片描述

这里需要引入一个概念:理论与需求不能混为一谈
的确,按照这样的设计是不错,能够省略掉switch语句,也能尽可能降低耦合,带给程序可扩展性。
但是,在本例中,电影其实是可以修改其类型的,也就是说,在生成对象后,我们应该让它保留有更改类别的能力。(但如果采用这种多态的形式,会很尴尬地发现,不同类别是截然不同的对象,不能相互转换,无法满足需求)

在这里有一种比较巧妙的解决方法:利用设计模式中的状态模式

状态模式:当一个对象的行为取决于它的状态,并且该对象可能会在运行时刻中更改状态,就可以考虑使用状态模式。
本模式的实质在于将变化的状态抽象出来,以改变子类实例来达到目的。

拟定使用状态模式后的类图:

/'在线作图(UML)网址:
http://www.plantuml.com/plantuml/uml/SyfFKj2rKt3CoKnELR1Io4ZDoSa70000
如果要修改的的话,打开网址后,直接复制上图片链接(或者粘贴下方代码)修改即可'/
@startuml
Title "状态模式"
class Movie{
+ public getCharge()
}
abstract class Price{
+ public getCharge()
}
class RegularPrice{
+ public getCharge()
}
class ChildrensPrice{
+ public getCharge()
}
class NewReleasePrice{
+ public getCharge()
}
Movie o-- Price
Price <|-- RegularPrice 
Price <|-- ChildrensPrice
Price <|-- NewReleasePrice
@enduml

在这里插入图片描述

如果你很熟悉GoF所列的各种模式,可能会问:“这是一个状态,还是一个策略?”答案取决于Price类究竟代表计费方式,还是代表影片的某个状态。在这个阶段,对于模式(和其名称)的选择反映出你对结构的想法。此刻我把它视为影片的某种状态。如果未来我觉得策略能更好地说明我的意图,我会再重构它,修改名字,以形成策略。

这仅仅是提出了一个结构,要把原程序加工成这个样子,还需要继续重构,下面将分为三个步骤进行。

  1. 状态/策略取代类型码(8.15 Replace Type Code with State/Strategy)

  2. 搬移函数(7.1 Move Method)

  3. 以多态取代条件表达式(9.6 Replace Conditional with Polymorphism)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NewReErWen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值