类和策略模式(strategy)

First Post:

Last Update:

策略模式封装了变化

面向对象编程——并非类越多越好,不至于每一个功能的实现都写一个具体的类,而是抽象出他们之间的共性,类的划分是为了封装,但是分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

策略模式解析:

策略模式是一种定义一系列算法的方法,从概念上面来看,所有的算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少各种算法类与使用算法类之间的耦合。


策略模式优点:

  1. Strategy类层次为Context定义了一系列的可供重用的算法和行为。继承有助于析取出这些算法中的公用功能。(例如CashContext中的GetResult来自于CashSuper)

  2. 策略模式的优点就是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口独立测试。 (哪个算法出了问题,只需要修改对应的算法即可。)

  3. 当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。

  4. 策略模式避免了过多条件判断(if-else)语句的使用

策略模式是怎么避免过多的条件判断的?

  • 策略模式就是用来封装算法的,但在实践当中,我们发现它可以用它来封装几乎任何类型的规则,只要在分析过程中知道需要在不同时间(不同应用场合)应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

  • 基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context上下文对象。

  • 在结合了工厂和策略模式后,选择所用具体实现的职责也可以由Context承担,最大化减轻了客户端的职责。

  • 要增加一种算法的话,还是需要增加Context对象中的switch分支

  • 但是——无论如何,任何需求的变更都是需要成本的。

  • 只是成本开销大小的关系,面对同样的需求当然成本开销越小越好。

  • 还提到一种反射技术——策略模式的改进

封装变化点的思想——Context上下文对象,通过使不同算法实现类继承同一个父类,创建该父类的一个引用来装载子类对象(父类可以装子类——就比如说Object对象可以装载所有的对象),通过这个父类的引用去调用不同子类中的算法实现。

主要的问题:

怎么装载子类?
——通过子类继承同一个父对象,创建一个父对象的引用来装载。
怎么装载不同的子类?
——通过构造方法传入不同策略对象来构造对不同子类的引用。
怎么调用子类的方法?
——子类通过继承override重写父类中的方法,然后通过父类的引用(这时候装载了子类)直接调用该方法即可。

实例:商场打折促销、满减促销等不同营销策略的实现——结合CashFactory——收费工厂类

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
54
55
56
57
58
59
60
61
商场打折促销、满减促销等不同营销策略的实现——

CashSuper——父类
CashNormal——算法1对应一个策略:常规收费
CashRebate——算法2对应一个策略:打折收费
CashReturn——算法3对应一个策略:满返收费
CashContext——收费上下文
///
可以在此继续拓展其他的策略类
///

public class CashContext{
private CashSuper cs;//创建父类对象的引用
public CashContext(CashSuper csuper)//通过传入具体的收费策略
{
this.cs = csuper;
}
public double GetResult(double money)
{
return cs.AcceptCash(money);
}
}


结合CashFactory——收费工厂类

public static CashSuper createCashAccept(string type)
{
CashSuper cs = null;
switch (type)
{
case "常规收费":{ cs = new CashNormal( ); break;}
case "打折收费":{ cs1 = new CashRebate( 0.8);break; }
/*打折力度0.8*/
case……
//根据type传入不同的收费策略实例化不同分支收费算法类
}
return cs;
}


客户端代码(main函数)
CashContext cc=null;
switch(int num)
{
case 0:{
cc = new CashContext( new CashNormal ( ) ) ;
break;
}
case 1:{
cc = new CashContext( new CashRebate( 0.8 ) ) ;
break;
}

}
double total=0f;
total += cc.GetResult();


可以发现——客户端中又多了这个switch判断的过程,将它转移是可以做到的
——简单工厂不一定要是单独的一个类,它可以和策略模式的Context上下文对象结合。

结合简单工厂和策略模式——修改后的CashContext

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
结合简单工厂和策略模式——修改后的CashContext

class CashContext
{
CashContext cc=null;
public CashContext(string type)//注意参数类型
{
switch(type)
{
case:"常规收费":{
cc = new CashContext( new CashNormal ( ) ) ;
break;
}
case:"打折收费":{
cc = new CashContext( new CashRebate( 0.8 ) ) ;
break;
}

}
}
public double GetResult(double money)
{
return cs.acceptCash(money);
}
}

看看两种方式调用的区别:

//用工厂方式
CashSuper csuper = CashFactory.createFactory(string type);
//工厂和策略模式的结合
CashContext cashContext = new CashContext(string type);

结合工厂模式与策略模式的好处:

用工厂方式需要了解两个类对象CashSuper和CashFactory
用工厂和策略模式结合可以只了解上下文对象CashContext
这使得具体的收费算法彻底地与客户端分离,连算法的父类CashSuper都不对客户端开放。

策略模式的思想:

  • 定义算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响使用该算法的客户。
  • 算法本身只是一种策略,最重要的是这些算法是随时可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。

策略模式标准实现

抽象算法类,Strategy
策略A继承自抽象算法(Strategy),ConcreteStrategyA
策略B继承自抽象算法(Strategy),ConcreteStrategyB
……
上下文对象,Context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。

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
抽象算法类
abstract class Strategy
{//算法方法
void abstract void AlgorithmInterface();
}


//策略A——封装了具体的算法及行为,继承于Strategy
class ConcreteStrategyA : Strategy
{
public override void AlgorithmInterface()
{
//具体实现
}
}


//策略B——封装了具体的算法及行为,继承于Strategy
class ConcreteStrategyB : Strategy
{
public override void AlgorithmInterface()
{
//具体实现
}
}


//上下文——Context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。
class Context
{
Strategy strategy;
public Context(Strategy strategy)
{
this.strategy = strategy;
}
public void ContextInterface()
{
strategy.AlgorithmInterface();
}
}



客户端代码(main函数):
Context context;

context = new Context(new ConcreteStrategyA( ));

context.ContextInterface( );//调用了策略A的算法实现

context = new Context(new ConcreteStrategyB( ));

context.ContextInterface( );//调用了策略B的算法实现

客户端是如何调用不同的算法的:
实例化了上下文对象,传入了继承Strategy类的ConcreteStrategyA类实例化的对象来初始化Context对象中的Strategy参数,此时使用的便是具体实现策略A的算法。调用Context对象的方法,即是调用策略A的方法实现,策略模式就是通过这样的上下文对象封装了“变化点”,每次采取不同的策略,就传入不同的继承自策略类Strategy的具体策略的实例即可。