CRUD很无聊?一起学设计模式吧!--策略模式

简介: 策略模式属于对象的行为模式。

定义与特点


策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。


策略模式的主要优点如下:


多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。


策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。


策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。


策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。


主要缺点如下:


  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。


  • 策略模式造成很多的策略类。


UML


1.jpg


角色定义


策略模式涉及三个角色:


抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色定义所有支持的算法的公共接口。


具体策略(ConcreteStrategy)角色,封装了具体的算法或行为。


环境(Context)角色:用一个ConcreteStrategy来配置,持有一个Strategy的引用。


场景实战


提到策略模式不得不说一下《三十六计》,它是根据中国古代军事思想和丰富的斗争经验总结而成的兵书,是中华名族悠久非物质文化遗产之一,它的身影在留下来的战争故事中无处不在。


知己知彼方能百战不殆,在战争中使用哪种谋略需要因人而异。张三好色,使用美人计获取情报,而后图之;李四狡诈,使用苦肉计使其麻痹,放下戒心,然后破之;王二与麻子有仇,只需坐山观虎斗,使用借刀杀人让麻子杀了王二即可。


这个场景可以套用策略模式来实现


代码示例


抽象策略


首先定义所有计策的抽象类,所有策略的目的都是为了击败对手,定义公共方法 fightEnemy


/** * 策略接口,定义所有的接口 * @date 2019/5/22 9:50 */publicinterfaceFightStrategy {  
/** * 杀敌之法 */publicvoidfightEnemy(); 
}


具体策略


我们定义三种策略,分别是美人计,苦肉计,借刀杀人计:


/** * 三十六计之美人计 */publicclassHoneyTrapStrategyimplementsFightStrategy{  
@OverridepublicvoidfightEnemy() {  
System.out.println("使用‘美人计’取得胜利");  
    } 
}


/** * 三十六计之苦肉计 */publicclassSelfInjuryStrategyimplementsFightStrategy {  
@OverridepublicvoidfightEnemy() {  
System.out.println("使用’苦肉计‘取得胜利");  
    } 
}


/** * 三十六计之借刀杀人  */publicclassCollateralStrategyimplementsFightStrategy{ 
@OverridepublicvoidfightEnemy() {  
System.out.println("使用’借刀杀人‘取得胜利"); 
    } 
}


环境角色


环境角色主要是持有一个具体的策略,我们使用构造器在初始化环境类时传入具体的策略


/** * 环境角色-持有具体策略的引用 */publicclassStrategyContext {  
privateFightStrategystrategy; 
publicStrategyContext(FightStrategystrategy) {  
this.strategy=strategy; 
    } 
publicvoidfight(){  
this.strategy.fightEnemy(); 
    } 
}


客户端


/** * 客户端需要根据具体的对手选择具体的策略  */publicclassFightClient {  
publicstaticvoidmain(String[] args) {  
FightClientclient=newFightClient(); 
client.fightEnemy("李四");  
    } 
privatevoidfightEnemy(StringenemyName) { 
StrategyContextcontext=null; 
switch (enemyName){ 
case"张三" : 
context=newStrategyContext(newHoneyTrapStrategy()); 
break;  
case"李四":  
context=newStrategyContext(newSelfInjuryStrategy());  
break;  
case"王二":  
context=newStrategyContext(newCollateralStrategy());  
break;  
        } 
context.fight();  
    } 
}


执行结果


2.png


扩展


在上面例子中客户端需要承担根据敌人选择具体的策略职责,即上面的 case语句的实现逻辑,把这样一大段代码放在客户端会造成客户端臃肿,影响阅读体验,我们有2种优化策略:


简单工厂


使用简单工厂方法,将选择策略的判断逻辑抽取到工厂类中,客户端传入 enemyName给简单工厂生成具体策略,实现逻辑如下:


/** *  简单工厂方法  *      根据敌人名称选择具体的策略 */publicclassStrategyFactory {  
publicstaticFightStrategycreateFightStrategy(StringenemyName){  
FightStrategystrategy; 
switch (enemyName){ 
case"张三" : 
strategy=newHoneyTrapStrategy(); 
break;  
case"李四":  
strategy=newSelfInjuryStrategy();  
break;  
case"王二":  
strategy=newCollateralStrategy();  
break;  
default:  
thrownewIllegalStateException("Unexpected value: "+enemyName);  
        } 
returnstrategy;  
    } 
}


接下来改造客户端,选择具体策略的方法使用简单工厂生成:


/** * 使用简单工厂构建具体的策略  */publicclassFightClient {  
publicstaticvoidmain(String[] args) {  
FightClientclient=newFightClient(); 
client.fightEnemy("张三");  
    } 
privatevoidfightEnemy(StringenemyName) { 
FightStrategystrategy=StrategyFactory.createFightStrategy(enemyName);  
StrategyContextcontext=newStrategyContext(strategy);  
context.fight();  
    } 
}


策略与简单工厂结合


这里主要改造环境角色类,构造方法不再接收具体的策略对象,而是使用 enemyName作为参数接收,让其拥有根据 enemyName选择策略的能力,改造后的环境类如下:


/** * 环境角色 结合简单工厂选择具体的策略 */publicclassStrategyContext {  
privateFightStrategystrategy; 
publicStrategyContext(StringenemyName) {  
switch (enemyName){ 
case"张三" : 
strategy=newHoneyTrapStrategy(); 
break;  
case"李四":  
strategy=newSelfInjuryStrategy();  
break;  
case"王二":  
strategy=newCollateralStrategy();  
break;  
default:  
thrownewIllegalStateException("Unexpected value: "+enemyName);  
        } 
    } 
publicvoidfight(){  
this.strategy.fightEnemy(); 
    } 
}


改造后的客户端代码如下:


/** * 环境角色拥有选择策略的能力,客户端只需要认识Context角色  */publicclassFightClient {  
publicstaticvoidmain(String[] args) {  
FightClientclient=newFightClient(); 
client.fightEnemy("王二");  
    } 
privatevoidfightEnemy(StringenemyName) { 
StrategyContextcontext=newStrategyContext(enemyName); 
context.fight();  
    } 
}


对比


使用简单工厂方法时客户端需要认识两个类: FightStrategy, StrategyContext,而使用策略与简单工厂结合的方式客户端只需要认识 StrategyContext即可,这使得算法类彻底与客户端分离,耦合度会更低。


应用场景


策略模式的使用场景很多,主要有以下几类:


一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。


《重构》一书中指出策略模式可以作为优化条件语句的技巧,一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。


系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。


系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。


多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

目录
相关文章
|
设计模式 算法 关系型数据库
设计模式系列教程(13) - 策略模式
设计模式系列教程(13) - 策略模式
37 0
|
6月前
|
设计模式 算法 Java
【设计模式系列笔记】策略模式
策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列的算法,将每个算法封装起来,并且使它们可以互相替换。策略模式使得算法可以独立于客户端而变化。
97 0
|
6月前
|
设计模式 算法 编译器
【设计模式系列笔记】访问者模式
访问者模式是一种行为设计模式,旨在将算法与对象结构分离,使得能够在不修改元素类的前提下定义新的操作。这一模式的核心思想是在元素类中添加一个接受访问者的方法,从而实现在不同元素上执行不同操作的能力。
61 0
|
设计模式 算法 uml
CRUD很无聊?一起学设计模式吧!--策略模式
CRUD很无聊?一起学设计模式吧!--策略模式
98 0
|
设计模式 Java
CRUD很无聊?一起学设计模式吧!— 观察者模式(二)
CRUD很无聊?一起学设计模式吧!— 观察者模式(二)
62 0
|
设计模式 uml
CRUD很无聊?一起学设计模式吧!— 观察者模式(一)
CRUD很无聊?一起学设计模式吧!— 观察者模式(一)
63 0
|
设计模式 算法 uml
CRUD很无聊?一起学设计模式吧!--模板模式​
CRUD很无聊?一起学设计模式吧!--模板模式​
53 0
|
设计模式 程序员 uml
CRUD很无聊?一起学设计模式吧! — 命令模式
CRUD很无聊?一起学设计模式吧! — 命令模式
71 0
|
设计模式 SQL 缓存
【设计模式】【第九章】【设计模式小结】
单一职责原则:一个类只负责一个功能领域中的相应职责 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展
|
设计模式 自然语言处理 Java
重新学习设计模式一:什么是设计模式
一直以来,设计模式是一个令人头疼的课题,记得之前在A公司做智能客服项目时,刚开始只是一个小项目,不管什么设计模式,系统架构,全程直接上手敲业务代码,两三天时间就把所有的代码敲完上线使用,结果谁也没想到突然项目大起来了,十几个业务部门的业务一拥而上,开始招人,上手业务,结果。。。大家都是苦力干嘛,拼命加班,拼命填坑,十几个人的代码乱七八糟,大量重复业务,重复代码,单简单的一样表单业务查询就有三四不同的版本,新来的员工也在抱怨没学到任何技术,倒学会怎么跟业务吵架,那日子实在是不忍直视。。。
99 0
下一篇
无影云桌面