前言:

刚刚开始学习设计模式,之前也接触过一些,但是从来都没有系统的学过,这次打算好好的学习一下。这里就当是对学习过程的一个记录、整理,以便可以在以后不时的温故知新。

这一节采用一个鸭子的示例,层层推进,引入策略模式。具体如下:

1.   基本需求:创建有一些特性的鸭子

鸭子拥有如下的一些特性:游泳戏水、呱呱叫、外观

初步实现鸭子的特性:

鸭子超类:

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("红头鸭");
}
}

2.   新的需求:让鸭子飞

2.1 在Duck类中添加飞的方法

如下:

 public abstract class Duck
{
public void Quack()
{
Console.WriteLine("鸭子叫:呱呱呱");
}
public void Swim()
{
Console.WriteLine("鸭子游泳");
} public void Fly()
{
Console.WriteLine("鸭子飞了");
}
public abstract voidDisplay();
}

2.2 并非所有的鸭子都能飞

这个时候会带来一下新的问题:

并非所有的鸭子都能飞,比如:橡皮鸭

并非所有的鸭子都能呱呱叫:比如:橡皮鸭

对应的解决办法:

2.3 覆盖鸭子超类中的Fly和Quack方法

通过在橡皮鸭的实现中覆盖鸭子超类中的Fly和Quack方法

如下:

public class RubberDuck : Duck
{
public new void Fly()
{
//Do nothing
} public new void Quack()
{
Console.WriteLine("鸭子叫:吱吱吱");
}
}

2.4 有些鸭子既不能飞也不能叫

这样带来的新的问题:

比如木头鸭,既不会游泳 也不会叫

如果直接继承自Duck超类,还需要对Fly和Quack方法进行重写。

这个时候我们可能意识到继承不是解决问题的最终办法,因为每一个继承自鸭子的子类都需要去被迫的检查并且可能要覆盖Fly和Quack方法。

我们可能只需要让某些鸭子具有Fly和Quack即可

2.5 通过接口让某些鸭子具有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
}
}

通过接口的方式虽然结局了一部分鸭子不会飞或者不会叫的问题,

2.6 针对实现编程,代码不能复用

使用接口产生新的问题:

代码不能复用,产生过多的重复代码。如果你想要修改某个行为,必须在买个定义了此行为的类中修改它,这样很可能造成新的错误。

2.7 封装变化,针对接口编程

这个时候出现了第一个设计原则:

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码放在一起。

即:

把会变化的部分抽取并封装起来,以便以后可以轻易的改动或者扩充此部分,而不影响其他不变的部分。

分开变化和不会变化的部分,在本例中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,当你将两个类结合起来使用,就是组合,这种做法和继承的不同之处在于,鸭子的行为不是继承来的,而是合适的对象组合来的。这里用到了面向对象的第三个设计原则:

多用组合,少用继承

通过刚刚讲的这么多,引出了本章的第一个模式:

策略模式:定义了算法簇(这里的算法簇就相当于一组行为),分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

3.   总结

通过本章的学习,我们可以了解掌握以下知识:

1.    面向对象设计的原则(部分):

l 封装变化

l 多用组合,少用继承

l 针对接口编程,不针对实现编程

2.    面向对象模式---策略模式:

定义算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法变化独立于使用算法的客户。

Head First设计模式之策略模式(Strategy Pattern)的更多相关文章

  1. 乐在其中设计模式(C#) - 策略模式(Strategy Pattern)

    原文:乐在其中设计模式(C#) - 策略模式(Strategy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 策略模式(Strategy Pattern) 作者:webabc ...

  2. 反馈法学习设计模式(一)——策略模式Strategy Pattern

    简介(Introduction) 之前学习Java8实战时,遇到一个很好的策略模式示例.便想着借着这个示例结合反馈式的方法来,学习策略设计模式,也以便后面反复琢磨学习. 首先我们通过练习,逐步写出符合 ...

  3. 8.6 GOF设计模式四: 策略模式… Strategy Pattern

    策略模式… Strategy Pattern  在POS系统中,有时需要实行价格优惠, 该如何处理?  对普通客户或新客户报全价  对老客户统一折扣5%  对大客户统一折扣10%  注:课件 ...

  4. 二十四种设计模式:策略模式(Strategy Pattern)

    策略模式(Strategy Pattern) 介绍定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换.本模式使得算法的变化可独立于使用它的客户. 示例有一个Message实体类,对它的操作有 ...

  5. 设计模式原来如此-策略模式(Strategy Pattern)

    策略模式中体现了两个非常基本的面向对象设计的原则:1.封装变化的概念.2.编程中使用接口,而不是对接口的实现. 策略模式的定义:定义一组算法,将每个算法都封装起来,并使它们之间可以互换.策略模式使这些 ...

  6. 【UE4 设计模式】策略模式 Strategy Pattern

    概述 描述 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法的变化不会影响到使用算法的客户. 套路 Context(环境类) 负责使用算法策略,其中维持了一 ...

  7. 设计模式(一):“穿越火线”中的“策略模式”(Strategy Pattern)

    在前段时间呢陆陆续续的更新了一系列关于重构的文章.在重构我们既有的代码时,往往会用到设计模式.在之前重构系列的博客中,我们在重构时用到了“工厂模式”.“策略模式”.“状态模式”等.当然在重构时,有的地 ...

  8. 设计模式 - 策略模式(Strategy Pattern) 具体解释

    策略模式(Strategy Pattern) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26577879 本文版权全 ...

  9. HeadFirst设计模式读书笔记(1)-策略模式(Strategy Pattern)

    策略模式(Strategy Pattern): 定义了了算法簇,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户端. 第一个设计原则:找出应用中可能需要变化之处,把他们独立 ...

  10. JAVA设计模式之策略模式 - Strategy

    在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 ...

随机推荐

  1. iOS的触摸事件的用法以及和手势识别器的区别

    1.首先来介绍下触摸事件和手势识别器的利与弊 触摸事件和手势识别器二者之间有直接的关系 手势识别器是在触摸事件的基础上演变过来的 当我们用到触摸事件时 默认的uiview是没有什么效果的 只能自定义v ...

  2. Enable rsh on MAC OS with command line

    1. Enable rsh on macos. 1). os version (10.0) Enabling the "Allow remote login" option tur ...

  3. Django实现表单验证、CSRF、cookie和session、缓存、数据库多表操作(双下划綫)

    通常验证用户输入是否合法的话,是前端js和后端共同验证的,这是因为前端js是可以被禁用的,假如被禁用了,那就没法用js实现验证合法与否了,也就是即使用户输入的不合法,但是也没提示,用户也不知道怎么输入 ...

  4. java 调用 sql server存储过程

    Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句.这样就可以提高存储过程的性能. Ø ...

  5. maven 配置

    四.eclipse配置maven eclipse---window---maven------User Settings: 之前设置的仓库的位置: 五.idea15配置maven idea14---s ...

  6. AJAX实现异步登录

    //代码较为简单,只是测试所用 1.html登录页面代码 <table> <tr> <td>用户名:</td> <td><input ...

  7. iOS UITableViewCell滑动删除

    一般我们使用列表的形式展现数据就会用到UITableView.在熟练掌握了用UITableView展示数据以后,开发过程中可能会遇到需要删除数据的需求,我们想实现在一行数据上划动一下,然后出现一个删除 ...

  8. c# 常量,变量

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  9. Fiddler2 主菜单

    Fiddler2 主菜单 六个主菜单分别是: 文件(File) Capture Traffic ——启用捕获功能,快捷键 F12 此功能的开启/关闭状态,程序安装后默认是开启的.可以在 Fiddler ...

  10. CLR via C# 3rd - 05 - Primitive, Reference, and Value Types

    1. Primitive Types        Any data types the compiler directly supports are called primitive types. ...