《Head First 设计模式》读书笔记之策略模式

这篇博客是作者读《Head First 设计模式》后的策略模式笔记。通过模拟鸭子应用,阐述了如何在设计中应对鸭子是否能飞的需求变化。文章对比了直接继承和接口实现的优缺点,并介绍了通过委托实现策略模式以提高代码灵活性,同时提及了面向接口编程和多用组合的设计原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正式开始坚持写一些技术博客,我只是一个刚刚踏入计算机行业的菜鸟,能够写的东西也没有多少技术含量,但不管怎样,我已在路上,希望和同样走在这条道路上的小伙伴们共勉。文中的一些观点或是对技术的理解仅仅是建立在我目前水平基础之上的,难免会有错误或是可能误导别人的地方,还请各位看客批评指正,大家共同讨论,一起进步。

最近看了《Head First 设计模式》这本书,就拿它的读书笔记来当作我技术博客的开篇吧。第一章的策略模式以一个模拟鸭子应用为背景,应用中有各种类型的鸭子,主要可以完成swim、quack、display等动作。刚开始的设计如下图所示:

 捕获 (2)

对于初学OOP者来说脑子里最先冒出这样的方案是再正常不过的了,构造一个Duck基类,然后所有种类的鸭子都继承自Duck,这正是面向对象程序设计中所提到的方法。但是现在考虑对这些鸭子做些有趣的改变,让它们能够飞起来,于是理所当然地考虑在Duck这个基类里面加一个fly()方法,让所有子类都能继承这个fly()方法,这也是作者开始埋坑的地方。看上去这样的修改符合了新的需求,但是随着需求的不断发展,有了更多新品种的鸭子,比如,橡皮鸦。然后问题就来了,不是所有的鸭子都会飞,这样造成不能飞的鸭子继承到了基类飞的能力,牵一发而动全身,造成其他鸭子不想要的改变。如果使用Duck基类引用一个橡皮鸭,调用fly方法,输出的结果则会与意料中的不符。

于是开始考虑对代码进行修改,有两种方案备选:

1.把不能够飞的鸭子的fly方法覆盖掉,这样就可以保证程序不会有意想不到的结果。

2.如下图所示的设计:

捕获

每种不同鸭子根据不同的需求实现不同的接口,来扩充自己的能力,实现不同的鸭子可以有不同的飞法和不同的叫法。

以上两种方案好像也可以解决问题,但是都有各自的新问题。对于方案1来说,fly方法产生的结果在运行时就不能再改了,如果橡皮鸭的fly行为要发生改变,就必须修改橡皮鸭的代码。而fly的行为很难确定,在后期的发展,fly的行为可能要经常发生改变,比如开发了一种新道具,使得红头鸭可以使用火箭加速飞行,所以这种方式有很大的局限性。对于方案2来说,除了拥有方案1的局限性之外,还使得添加一种新类型鸭子变得更加复杂,要一个个实现接口,引入了很多重复的工作,而且对于相同的飞行方法也不能做到复用(不同于复制),而是分别在不同类型鸭子里面编写相同的代码。

但是可以针对方案2的两种不足进行一点点修改,比如,为了代码能够复用,不直接实现接口,而是将fly、quack等方法委托给实现了某一个接口的类来处理,所谓“委托”就是这个方法自己不提供实现的代码,而是通过调用其他类的方法来实现,这样一来可以在不同鸭子中复用这个类,二来由于这个类是实现的某一个接口,根据多态的原理,可以用这个接口来引用实现类,在运行时通过设置不同的实现类实例来动态切换不同的运行效果,从而解决了方案2中的两个不足。这也是策略模式的基本原理。

于是,有了如下的设计:

2

 

在Duck基类中保持FlyBehavior和QuackBehavior的引用,performFly和performQuack方法分别委托给这两个引用指向的实例中相关的方法,至于这些实例中的相关方法是怎么实现的,Duck并不用关心,而是交给实现这两个接口的coder来完成。下面则是书中对策略模式的定义:

策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立与使用该算法的客户。

在整个设计过程中,书中还提到了三个设计原则:

1.封装变化

即在OO设计中,找出应用中可能需要变换之处,把它们独立出来,不要和那些不需要变化的代码混在一起。在上述的设计中display方法是需要变化的,因为不同类型的鸭子display输出的结果不用,因此将他声明为abstract,在子类中必须重写这个方法。fly和quack方法也是需要变化的,但是为什么不像display方法那样声明为抽象的呢?因为display方法已经十分明确地确定下来不同类型的Duck有不用的display方法,在子类中必须重写,而不同类型的Duck的fly和quack方法则可能有相同的实现,如果声明为abstract则没法做到代码复用,而采用了将处理委托给实现了相关接口的类来处理,则可以解决这样的问题,这也是策略模式的定义。

2.针对接口编程,而不是针对实现编程。

或针对“超类”编程。为了使代码更有弹性,重复利用多态的特性,要学会合适地选择针对接口变成这一条原则。

3.多用组合,少用继承。

合理利用组合,可以避免继承到不必要的方法,导致牵一发而动全身,造成一些子类中不想要的改变。

源代码如下:

public abstract class Duck {
	protected FlyBehavior flyBehavior;
	protected QuackBehavior quackBehavior;
	

	abstract public void display();
	public void swim(){
		System.out.println("I am swimming!");
	}
	public void performQuack(){
		quackBehavior.quack();
	}
	public void performFly(){
		flyBehavior.fly();
	}
	
	public void setFlyBehavior(FlyBehavior fly){
		this.flyBehavior = fly;
	}
	
	public void setQuackBehavior(QuackBehavior quack){
		this.quackBehavior = quack;
	}
}


public interface FlyBehavior {
	void fly();
}

public interface QuackBehavior {
	void quack();
}


public class FlyNoWay implements FlyBehavior {

	@Override
	public void fly() {
		System.out.println("I can't fly!");
	}

}


public class Quack implements QuackBehavior {

	@Override
	public void quack() {
		System.out.println("鸭子嘎嘎叫!");
	}

}

public class MallardDuck extends Duck {
	
	public MallardDuck(){
		this.flyBehavior = new FlyWithWings();
		this.quackBehavior = new Quack();
	}
	
	@Override
	public void display() {
		System.out.println("我是一只绿头鸭子!");
		
	}

}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值