刚刚开始学习设计模式,以前也接触过一些,可是历来都没有系统的学过,此次打算好好的学习一下。这里就当是对学习过程的一个记录、整理,以即可以在之后不时的温故知新。算法
这一节采用一个鸭子的示例,层层推动,引入策略模式。具体以下:编程
鸭子拥有以下的一些特性:游泳戏水、呱呱叫、外观设计模式
初步实现鸭子的特性:ide
鸭子超类:学习
public abstract class Duck { public void Quack() { Console.WriteLine("鸭子叫:呱呱呱"); } public void Swim() { Console.WriteLine("鸭子游泳"); } public abstract voidDisplay(); }
鸭子子类:测试
public class MallardDuck:Duck { public override voidDisplay() { Console.WriteLine("绿头鸭"); } } public class RedHeadDuck : Duck { public override voidDisplay() { Console.WriteLine("红头鸭"); } }
以下:设计
public abstract class Duck { public void Quack() { Console.WriteLine("鸭子叫:呱呱呱"); } public void Swim() { Console.WriteLine("鸭子游泳"); } public void Fly() { Console.WriteLine("鸭子飞了"); } public abstract voidDisplay(); }
这个时候会带来一下新的问题:orm
并不是全部的鸭子都能飞,好比:橡皮鸭对象
并不是全部的鸭子都能呱呱叫:好比:橡皮鸭blog
对应的解决办法:
经过在橡皮鸭的实现中覆盖鸭子超类中的Fly和Quack方法
以下:
public class RubberDuck : Duck { public new void Fly() { //Do nothing } public new void Quack() { Console.WriteLine("鸭子叫:吱吱吱"); } }
这样带来的新的问题:
好比木头鸭,既不会游泳 也不会叫
若是直接继承自Duck超类,还须要对Fly和Quack方法进行重写。
这个时候咱们可能意识到继承不是解决问题的最终办法,由于每个继承自鸭子的子类都须要去被迫的检查而且可能要覆盖Fly和Quack方法。
咱们可能只须要让某些鸭子具备Fly和Quack便可
经过接口来解决此问题
以下所示:
public interface IFlyable { public void Fly(); } public interface IQuackable { public void Quack(); } public class MoodDuck : IFlyable, IQuackable { public void Fly() { //Can't Fly } public void Quack() { //Can't Quack } }
经过接口的方式虽然结局了一部分鸭子不会飞或者不会叫的问题,
使用接口产生新的问题:
代码不能复用,产生过多的重复代码。若是你想要修改某个行为,必须在买个定义了此行为的类中修改它,这样极可能形成新的错误。
这个时候出现了第一个设计原则:
找出应用中可能须要变化之处,把它们独立出来,不要和那些不须要变化的代码放在一块儿。
即:
把会变化的部分抽取并封装起来,以便之后能够轻易的改动或者扩充此部分,而不影响其余不变的部分。
分开变化和不会变化的部分,在本例中Fly和Quack会随着鸭子的不一样而改变,因此咱们要将其抽离出Dock类,并创建一组新类来表明每一个行为。
这时出现了设计的另外一个原则:
针对接口编程,而不是针对实现编程。
这里咱们用接口表明每一个行为,例如:IFlyable和IQuackable,每一个行为的实现都将实现对应的接口。
因此鸭子类不会负责实现IFlyable和IQuackable接口,而是由一组其余专门的类来实现对应的接口,称之为“实现类”。
这种作法跟以前作法的区别:
l 以前的作法中行为的实现来自超类的具体实现,或者继承某个接口的子类实现,这些作法都依赖于实现。
l 新的设计中,鸭子子类经过使用接口表示行为,因此实现不会被绑定在子类中,特定的行为编写在实现了接口的类中。
“针对接口编程”真正的意思是“针对超类型”编程
即
变量的声明类型应该是超类型,一般是一个抽象类或者接口,所以只要是实现了抽象类或者接口的对象,均可以指定给这个变量,即声明类型时不用理会之后执行时真正的对象类型。
接下来咱们就能够实现鸭子具体的行为了,以下:
/// 会飞的行为类 /// </summary> public classFlyWithWings:IFlyable { public void Fly () { Console.WriteLine("用翅膀飞"); } } /// <summary> /// 不会飞的行为类 /// </summary> public class FlyNoWay : IFlyable { public void Fly() { Console.WriteLine("不会飞"); } } /// <summary> /// 呱呱叫的行为类 /// </summary> public class Quack : IQuackable { public void Quack() { Console.WriteLine("呱呱叫"); } } /// <summary> /// 吱吱叫的行为类 /// </summary> public class Squeak : IQuackable { public void Quack() { Console.WriteLine("吱吱叫"); } } /// <summary> /// 不会叫的行为类 /// </summary> public class MuteQuack :IQuackable { public void Quack() { Console.WriteLine("不会叫"); } }
接下来咱们要对鸭子的行为进行整合,其核心思想就是:将鸭子飞行和叫的行为委托别人处理,不使用定义在Duck类内的叫和飞行方法。
具体作法以下:
1. 在Duck类中新增2个实例变量,分别为“flyBehavior”和“quackBehavior”声明为接口类型(每一个鸭子对象会动态的设置这些变量,在运行时引用正确的行为类型)
2. 将Duck类中的Quack()和Fly()方法删除,同时新增PerformFly()和PerformQuack()来取代这两个类
3. 实现PerformFly()和PerformQuack(),以下所示:
/// Description:鸭子超类 /// </summary> public abstract class Duck { public IFlyable flyBehavior; public IQuackablequackBehavior; public void PerformFly() { flyBehavior.Fly(); } public void PerformQuack() { quackBehavior.Quack(); } public void Swim() { Console.WriteLine("鸭子游泳"); } public abstract voidDisplay(); }
这样作的结果就是咱们能够忽略flyBehavior和quackBehavior接口对象究竟是什么,只须要关心该对象如何进行相应的行为便可。
4. 设定flyBehavior类和quackBehavior类的实例变量,以下所示:
public class MallardDuck : Duck { public MallardDuck() { flyBehavior = newFlyWithWings();//使用FlyWithWings类处理飞行,当PerformFly()被调用时,飞的职责被委托给FlyWithWings对象,获得真正的飞 quackBehavior = newQuack(); } public override voidDisplay() { Console.WriteLine("绿头鸭"); } }
说明:
当MallardDuck实例化的时候,构造器会把继承自quackBehavior的实例变量初始化成Quack类型的新实例,一样对于飞的行为也是如此。
5. 测试,以下:
Duck.Duck mallard = new MallardDuck(); mallard.PerformFly(); mallard.PerformQuack(); Console.Read();
结果以下:
动态的设定行为
为了可以充分的用到咱们以前建的一些鸭子的动态行为,咱们能够在鸭子子类中经过设定方法来设定鸭子的行为,而不是在样子的构造器内实例化。具体步骤以下:
1. 在Duck类中新增两个设置方法,以下:
public void SetFlyBehavior(IFlyable fly) { flyBehavior = fly; } public voidSetQuackBehavior(IQuackable quack) { quackBehavior = quack; }
2. 建立新的鸭子类型
public class ModelDuck : Duck { public ModelDuck() { flyBehavior = newFlyNoWay(); quackBehavior = newSqueak(); } public override void Display() { Console.WriteLine("模型鸭"); } }
3. 建立一个新的FlyBehavior类型
public class FlyRocket : IFlyable { public void Fly() { Console.WriteLine("Fly with Rocket!"); } }
4. 测试
Duck.Duck model = new ModelDuck(); model.PerformFly(); model.PerformQuack(); model.SetFlyBehavior(newFlyRocket()); model.PerformFly();
结果以下:
从上边的结果中咱们能够看出,在初始化ModelDuck的时候,咱们对flyBehavior对象进行了FlyNoWay的初始化,因此显示的飞行的行为为:不会飞,后边咱们又经过SetFlyBehavior方法动态设置了flyBehavior的实例,因此后边就有了Fly with Rocket的行为啦。
上边的例子对于每个鸭子都有一个FlyBehavior和QuackBehavior,当你将两个类结合起来使用,就是组合,这种作法和继承的不一样之处在于,鸭子的行为不是继承来的,而是合适的对象组合来的。这里用到了面向对象的第三个设计原则:
多用组合,少用继承
经过刚刚讲的这么多,引出了本章的第一个模式:
策略模式:定义了算法簇(这里的算法簇就至关于一组行为),分别封装起来,让他们之间能够相互替换,此模式让算法的变化独立于使用算法的客户。
经过本章的学习,咱们能够了解掌握如下知识:
1. 面向对象设计的原则(部分):
l 封装变化
l 多用组合,少用继承
l 针对接口编程,不针对实现编程
2. 面向对象模式---策略模式:
定义算法簇,分别封装起来,让它们之间能够相互替换,此模式让算法变化独立于使用算法的客户。