HeadFirst设计模式(一)策略者模式
最近在看headfirst设计模式一书,作为一个半路出家的程序员,感觉很多东西需要学习,学习的路程中有些东西学了当时觉得理解了,但日常工作中没有使用到渐渐的自己就忘记了。----------------------上面就是写者系列的博客的原因,主要是为了巩固知识,忘记在那个博主那边看过这么一句话,知识学了后总结了才变成自己的。
策略者模式----定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
当然这只是理论的东西,说实话我现在都没理解这个理论是啥,下面我用代码和比较直观的语言给大家介绍一下策略者模式,希望能有所收获。
模拟情节:
joe上班的公司有一套鸭子游戏的系统:simuduck,游戏中有各种鸭子,一边游泳,一边呱呱叫。此系统使用了标准的oo(面向对象)技术。设计一个鸭子的超类(也就是父类-duck),并让各种鸭子继承此超类。
此时由于需求的改变需要增加飞行的功能,joe直接在超类中添加fly()方法:
1 public abstract class duck 2 { 3 public void quack() 4 { 5 console.writeline("我会叫"); 6 } 7 8 public void swim() 9 { 10 console.writeline("我会游泳"); 11 } 12 13 public void fly() 14 { 15 console.writeline("我会飞"); 16 } 17 public abstract void display(); 18 } 19 20 public class mallardduck : duck 21 { 22 public override void display() 23 { 24 console.writeline("我是一只绿头鸭"); 25 } 26 27 } 28 public class modelduck : duck 29 { 30 public override void display() 31 { 32 console.writeline("我是一只模型鸭子"); 33 } 34 } 35 public class redheadduck : duck 36 { 37 38 public override void display() 39 { 40 console.writeline("我是一只红头鸭"); 41 } 42 } 43 44 class program 45 { 46 static void main(string[] args) 47 { 48 duck modelduck=new modelduck(); 49 modelduck.quack(); 50 modelduck.display(); 51 modelduck.fly(); 52 console.readline(); 53 } 54 }
此时问题就来了,模型鸭子(modelduck)由于也是继承于超类也具备了飞行的功能,显然继承在这里并不是一个很好的解决方案。这里在超类中用到了抽象的方法,有个小技巧对于新学者来说经常搞不清抽象方法的语法,记住抽象方法是没有身体的,也就是说他是没有方法体的。
这时候就不能用继承了,这时候有人提出把fly()和quack()方法提到接口iflyable与iquackable中,这样可以飞行的鸭子实现接口,不可以飞行的模型鸭子就不实现接口,表面上看来是满足了要求,但这样的话不仅仅fly()在每个可以飞行的鸭子类中都要实现一边造成代码重复太多,如果要将可以飞行的鸭子的飞行行为稍微改动一下的话,那么面临的将是灾难,没给方法你都需要改动一下。
此时,你是否期望一种设计模式能来救你于苦海,这里我先卖一个关子,接下来我会用老方法找出一个解决之道,“采用良好的oo软件设计原则”;
通过上面的例子的一步步学习,我想我们应该知道继承并不能很好的解决问题,因为鸭子的行为在子类中是不断地变化,所以让所有的子类都有这些行为是不恰当的。接口iflyable与iquackable中一开始看起来还挺不错,解决了问题但是由于接口不具备实现代码,所以实现接口无法达到代码的复用。这意味者你无论何时要修改某个行为,都会造成你需要修改所有实现该接口的类中修改他们,这样很容易造成错误。有个设计原则能很好的解决此问题,
设计原则:找出应用中可能需要变化的部分,把他们独立出来,不要和那些不需要变化的代码混合在一起。
这是我们介绍的第一个设计原则,在后面的学习中我还会介绍被的设计原则,我们先对着上面的例子看看那部分是改变的,都的鸭子的行为是不断的变化的我们需要把行为单独提出来,我们知道duck类内的fly()和quack()会随着鸭子的改变而不断的改变,为了把这两个行为从duck类中分开,我们将把他们从duck类中取出来,建立一组新类来代表每个行为。
设计鸭子的行为
如何设计那组实现飞行和呱呱叫的行为呢?
首先我们希望一切具有弹性,一开始的鸭子系统正是因为没有弹性,才让我们走上这条路,我们还行能够‘指定’行为到鸭子的实例。我们想要产生一个新的绿鸭子的实例的时候,并指定特定‘类型’的飞行行为给它,干脆顺便让鸭子的行为可以动态的改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,这样就可以在‘运行时’动态地‘改变’绿头鸭子的行为。
有了这些目标的需求,接着我们来看我们的第二给设计原则
设计原则:针对接口编程,而不是针对实现编程
从现在开始,鸭子的行为将被放在分开的类中,此类专门提供某行为接口的实现。这样的话和之前的做法有了明显的不同,之前的做法是:行为来自duck超类的具体实现,或者继承某给接口并由子类自行实现行为。这两种做法都是依赖于‘实现’造成我们的系统不再有弹性,很难更改别的代码。
在我们新的实现中,鸭子的子类将使用接口(iflybehavior与iquackbehavior)所表示的行为,也就是实现此接口的行为类(flynoway等)具体的行为的实现现在编写在行为类中。这样的设计就使得我们的系统具备的弹性。
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。而我们也可以新增一些行为,不会影响到既有的行为类,也不会影响‘使用’到飞行行为的鸭子类。
整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫的动作‘委托’(delegate)别人处理,而不是使用定义在duck类(或子类)内的呱呱叫和飞行方法。
做法的这样的:
①首先,在duck类中‘加入两个实例变量’,分别为”iflybehavior“与”iquackbehavior“,声明为接口类型(而不是具体实现接口的类的类型)每个鸭子对象通过动态的实现这些变量以在运行时引用正确的行为类型。
我们用两个相似的方法performfly和performquack来取代duck类中fly()与quack()。稍后你就知道原因了。
②现在,我们来实现
performquack();public class duck
{
iquackbehavior iquackbehavior;//每只鸭子都会引用实现iquackbehavior接口的对象。
public void performquack()
{
iquackbehavior.quack();//鸭子对象不亲自处理呱呱叫行为,而是委托给iquackbehavior引用的对象。
}
}
duck对象只要叫iquackbehavior对象去呱呱叫就可以了,在这部分的代码中,我们不在关心iquackbehavior接口的对象到底是什么,我们只关心该对象知道如何进行呱呱叫就够了。
3.现在关心如何实现iquackbehavior与iflybehavior的实例变量,看看具体继承duck类的子类是怎么实现的吧
public class modelduck : duck
{
public modelduck()
{
iflybehaviro = new ducknofly();
iqueackbehaviro = new ducknoqueck();
}
public override void display()
{
console.writeline("我是一只木头鸭子");
}
}
在modelduck实例化时,它的构造器会把继承来的iflybehavior和iquackbehavior实例变量初始化成ducknofly与ducknoquack类型的新实例。(ducknofly与ducknoquack是接口的具体实现)
现在只剩下一个在运行时动态该百年鸭子的行为的功能还未实现,我们上面是在具体鸭子类的构造器内进行更改鸭子的行为的,如果换一个地方将更改鸭子的行为提取到duck超类中,那么只要继承自该超类的鸭子派生类不都具备了动态更改行为的能力了吗?
在duck类中,加入两个新方法:
public void setperformfly(iflybehaviro fb)
{
iflybehaviro = fb;
}
public void setperformquack(iquackbehaviro qb)
{
iqueackbehaviro = qb;
}
从此以后,我们可以‘随时’调用者两个方法改变鸭子的行为。下面我贴上更改后的全部代码:
public abstract class duck
{
public iflybehaviro iflybehaviro;
public iquackbehaviro iqueackbehaviro;
public abstract void display();
public void swim()
{
}
public void performfly()
{
iflybehaviro.fly();
}
public void performquack()
{
iqueackbehaviro.quack();
}
public void setperformfly(iflybehaviro fb)
{
iflybehaviro = fb;
}
public void setperformquack(iquackbehaviro qb)
{
iqueackbehaviro = qb;
}
}
public class modelduck : duck
{
public modelduck()
{
iflybehaviro = new ducknofly();
iqueackbehaviro = new ducknoqueck();
}
public override void display()
{
console.writeline("我是一只木头鸭子");
}
}
public interface iflybehaviro
{
void fly();
}
public interface iquackbehaviro
{
void quack();
}
public class ducknofly : iflybehaviro
{
public void fly()
{
console.writeline("我不会飞");
}
}
public class duckcanflay:iflybehaviro
{
public void fly()
{
console.writeline("我会飞");
}
}
public class ducknoqueck : iquackbehaviro
{
public void quack()
{
console.writeline("我不会叫");
}
}
public class duckguaguaqueck:iquackbehaviro
{
public void quack()
{
console.writeline("呱呱叫");
}
}
class program
{
static void main(string[] args)
{
duck modelduck = new modelduck();
modelduck.display();
modelduck.performfly();
modelduck.performquack();
modelduck.setperformfly(new duckcanflay());
modelduck.performfly();
console.readline();
}
}
封装行为的大局观
我们已经深入研究了鸭子模拟器的设计,该是看看整体的格局了:
这是重新设计后的类结构,你所期望的一切都有:鸭子继承duck,飞行行为实现iflybehavior接口,呱呱叫行为实现iquackbehavior接口。请特别注意类之间的”关系“,关系可以是is-a(是一个)、has-a(有一个)或implements(实现)。
”有一个“可能比”是一个“更好
”有一个“关系相当有趣:每一个鸭子都有一个iflybehavior和一个iquackbehavior,好将飞行和呱呱叫的委托给它们代为处理。
当你将两个类结合起来使用,如同本例一般,这就是组合。这种做法和”继承“不同的地方在于,鸭子的行为不是通过继承来的,而是和适当的行为对象”组合“来的。
这是一个很重要的技巧,其实也是我们介绍的第三给设计原则:
设计原则:多用组合,少用继承。
如你所见,使用组合建立系统具有很大的弹性,不仅可以将算法组封装成类,更可以”在运行时动态的改变行为“,只要组合的行为对象符合正确的接口标准即可。”组合“被广泛的应用在很多设计模式中,后面你会经常发现它的身影。
至此,这就是我们学习的第一个模式了。
在上面的内容中我们需要到了几项内容:
oo基础:抽象,封装,多态,继承。
oo原则:封装变化;多用组合,少用继承;针对接口编程,不针对实现编程。
oo模式:策略模式----定义算法族,分别封装起来,让他们直接可以互相替换,此模式让算法的变化独立于使用算法的客户。
初衷,只是想把学习的设计模式自己总结成博客记录下来,但是好像有点偏离初心更像是一个教别人怎么理解策略者模式了,水平有限,写的不是很好,对本内容感兴趣的读者,推荐你阅读《head first设计模式》一书,该书很生动的讲解了设计模式。我上面所写的内容也是基于此书。最后感谢您的阅读。