博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
策略模式
阅读量:6346 次
发布时间:2019-06-22

本文共 5649 字,大约阅读时间需要 18 分钟。

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

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

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

初步实现鸭子的特性:

鸭子超类:

1
2
3
4
5
6
7
8
9
10
11
12
public 
abstract 
class 
Duck
    
{
        
public 
void 
Quack()
        
{
            
Console.WriteLine(
"鸭子叫:呱呱呱"
);
        
}
        
public 
void 
Swim()
        
{
            
Console.WriteLine(
"鸭子游泳"
);
        
}
        
public 
abstract 
voidDisplay();
    
}

鸭子子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public 
class 
MallardDuck:Duck
    
{
        
public 
override 
voidDisplay()
        
{
            
Console.WriteLine(
"绿头鸭"
);
        
}
    
}
  
    
public 
class 
RedHeadDuck : Duck
    
{
        
public 
override 
voidDisplay()
        
{
            
Console.WriteLine(
"红头鸭"
);
        
}
    
}

2.   新的需求:让鸭子飞

2.1 在Duck类中添加飞的方法

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法

如下:

1
2
3
4
5
6
7
8
9
10
11
12
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特性

通过接口来解决此问题

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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  新的设计中,鸭子子类通过使用接口表示行为,所以实现不会被绑定在子类中,特定的行为编写在实现了接口的类中。

“针对接口编程”真正的意思是“针对超类型”编程

变量的声明类型应该是超类型,通常是一个抽象类或者接口,因此只要是实现了抽象类或者接口的对象,都可以指定给这个变量,即声明类型时不用理会以后执行时真正的对象类型。

接下来我们就可以实现鸭子具体的行为了,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/// 会飞的行为类
    
/// </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(),如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// 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类的实例变量,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public 
class 
MallardDuck : Duck
   
{
       
public 
MallardDuck()
       
{
           
flyBehavior = newFlyWithWings();
//使用FlyWithWings类处理飞行,当PerformFly()被调用时,飞的职责被委托给FlyWithWings对象,得到真正的飞
           
quackBehavior = newQuack();
       
}
 
       
public 
override 
voidDisplay()
       
{
           
Console.WriteLine(
"绿头鸭"
);
       
}
   
}

说明:

当MallardDuck实例化的时候,构造器会把继承自quackBehavior的实例变量初始化成Quack类型的新实例,同样对于飞的行为也是如此。

5.    测试,如下:

1
2
3
4
5
6
7
Duck.Duck mallard = 
new 
MallardDuck();
 
          
mallard.PerformFly();
 
          
mallard.PerformQuack();
 
          
Console.Read();

结果如下:

动态的设定行为

为了能够充分的用到我们之前建的一些鸭子的动态行为,我们可以在鸭子子类中通过设定方法来设定鸭子的行为,而不是在样子的构造器内实例化。具体步骤如下:

1.    在Duck类中新增两个设置方法,如下:

1
2
3
4
5
6
7
8
9
public 
void 
SetFlyBehavior(IFlyable fly)
        
{
            
flyBehavior = fly;
        
}
  
        
public 
voidSetQuackBehavior(IQuackable quack)
        
{
            
quackBehavior = quack;
        
}

2.    创建新的鸭子类型

1
2
3
4
5
6
7
8
9
10
11
12
public 
class 
ModelDuck : Duck
   
{
       
public 
ModelDuck()
       
{
           
flyBehavior = newFlyNoWay();
           
quackBehavior = newSqueak();
       
}
       
public 
override 
void 
Display()
       
{
           
Console.WriteLine(
"模型鸭"
);
       
}
   
}

3.    创建一个新的FlyBehavior类型

1
2
3
4
5
6
7
public 
class 
FlyRocket : IFlyable
    
{
        
public 
void 
Fly()
        
{
           
Console.WriteLine(
"Fly with Rocket!"
);
        
}
    
}

4.    测试

1
2
3
4
5
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.    面向对象模式---策略模式:

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

你可能感兴趣的文章
WebSocket跨域问题解决
查看>>
ECMAScript6基本介绍
查看>>
世界经济论坛发布关于区块链网络安全的报告
查看>>
巨杉数据库加入CNCF云原生应用计算基金会,共建开源技术生态
查看>>
Ubuntu 16.04安装Nginx
查看>>
从 JS 编译原理到作用域(链)及闭包
查看>>
flutter 教程(一)flutter介绍
查看>>
CSS面试题目及答案
查看>>
【从蛋壳到满天飞】JS 数据结构解析和算法实现-Arrays(数组)
查看>>
每周记录(三)
查看>>
Spring自定义注解从入门到精通
查看>>
笔记本触摸板滑动事件导致连滑的解决方式
查看>>
Runtime 学习:消息传递
查看>>
你了解BFC吗?
查看>>
linux ssh tunnel使用
查看>>
十、详解FFplay音视频同步
查看>>
自定义元素探秘及构建可复用组件最佳实践
查看>>
小猿圈Python教程之全面解析@property的使用
查看>>
mpvue开发小程序所遇问题及h5转化方案
查看>>
View和Activity的生命周期
查看>>