Java技术栈
www.javastack.cn
关注阅读更多优质文章
我们使用的app大多都有分享的功能,我们可以选择分享到不同的地方,比如微博、微信、QQ等等,虽然是同一个内容,但是分享到不同的平台就会有不同的处理方式,比如要跳转到不同的app或者直接复制链接等等。如果让你来实现这个功能,你会如何实现呢?
如果你对设计模式不熟悉,那么第一反应就是有if...else或者switch语句来进行条件判断,根据用户的不同选择而使用不同的处理方法。我们用代码简化地处理一下:
public void Share{
public void shareOptions(String option){
if(option.equals("微博")){
//function1();
//...
}else if(option.equals("微信")){
//function2();
//...
}else if(option.equals("朋友圈")){
//function3();
//...
}else if(option.equals("QQ")){
//function4();
//...
}
//...
}
如果只是写一个这么简单的功能,那么这样的代码也未尝不可,因为这样的代码量不多,后续也不需要怎么拓展和修改,维护起来也不算麻烦。但是,我们工作中遇到的都是一些比较复杂的项目,要保证项目的可读性、可维护性和可拓展性,就必须在代码上下功夫了。
这里我们就要提到一个概念,那就是设计模式了。设计模式是指针对软件开发过程中重复发生的问题的解决办法。其中以被称为Gang of Four(GoF)的4人整理出的23种设计模式最为有名。
当然,我不可能在这里把23种设计模式全部介绍完,就算全部介绍一遍,而且你还能全部记住,也不一定奏效。首先,你没有必要全部记下来,因为这23种设计模式并非都是经常使用到的设计模式。其次,死记硬背下这些设计模式没有任何意义,重要的是在自己脑海中理解设计模式是怎样解决问题的。
今天我们就谈谈可以优化以上问题的设计模式,策略模式(Strategy Design Pattern)。
策略指的是计策、谋略,而模式一般指人为整理而成的,在某种场景下重复发生的问题的解决办法。在编程中,我们可以把策略看做是“算法”,而策略模式,按照GoF的定义,就是我们设计一些算法,把它们封装起来,让它们可以相互替换,这样就可以轻松地切换不同的算法来解决同一个问题。
我们看一下策略模式中有哪些角色。
Strategy(策略)Strategy角色负责决定实现策略所必需的接口(API)。在示例程序中,由strategy接口扮演此角色。
ConcreteStrategy(具体的策略)ConcreteStrategy角色负责实现Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。
Context(上下文)负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口(API))。
再看看策略模式的类图:
介绍到这里,相信你对策略模式有了初步的认识,那我们就用策略模式来重构前面的代码,让你加深对策略模式的理解。
//定义策略接口
public interface DealStrategy{
void dealMythod(String option);
}
//定义具体的策略1
public class DealSina implements DealStrategy{
@override
public void dealMythod(String option){
//...
}
}
//定义具体的策略2
public class DealWeChat implements DealStrategy{
@override
public void dealMythod(String option){
//...
}
}
//定义上下文,负责使用DealStrategy角色
public static class DealContext{
private String type;
private DealStrategy deal;
public DealContext(String type,DealStrategy deal){
this.type = type;
this.deal = deal;
}
public DealStrategy getDeal(){
return deal;
}
public boolean options(String type){
return this.type.equals(type);
}
}
public void Share{
private static List algs = new ArrayList();//静态代码块,先加载所有的策略static {
algs.add(new DealContext("Sina",new DealSina()));
algs.add(new DealContext("WeChat",new DealWeChat()));
}public void shareOptions(String type){
DealStrategy dealStrategy = null;for (DealContext deal : algs) {
if (deal.options(type)) {
dealStrategy = deal.getDeal();break;
}
}
dealStrategy.dealMythod(type);
}
}
再回忆一下策略模式中的各个角色,代码中的DealStrategy接口就是策略,DealSina和DealWeChat是具体的策略,DealContext就是使用策略的上下文。
所以这样的代码已经符合策略模式的代码结构了。我们通过策略模式将策略的定义、创建、使用解耦,让每一部分都不至于太复杂,也去除了if...else这样的条件判断语句,代码的可维护性和可拓展性都提高了。
我们把可变的部分放到 了Share 类中的静态代码段中。如果有新的需求,要添加一个分享的方式时,只需要定义好具体的策略,然后修改 Share 类中的静态代码段,其他代码都不需要修改。
策略模式还是比较常用的一种设计模式,比如java中给我定义好的Comparator 接口就是策略模式的一个实践。
如果需要对某个类的排序,而该类没有实现Comparable接口,那么可以建立一个实现Comparator接口的比较器即可。通过实现Comparator类来新建一个比较器,然后通过该比较器来对类进行排序。
//策略接口
public interface Comparator {
int compare(T o1, T o2);
}
//需要我们来定义具体的策略
public class sorter implements Comparator{
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
//一般我们会用更简洁的Lambda表达式来实现
如果你使用的是Java ,想更符合开闭原则,并对反射有一定了解,那还可以通过反射来避免对类的修改。
你可以通过一个配置文件或者定义一个注解来标注定义的策略类;通过读取配置文件或者搜索被标注的策略类,通过反射动态地加载这些策略类、创建策略对象;当我们新添加一个策略的时候,只需要将这个新添加的策略类添加到配置文件或者用定义的注解标注即可。
Strategy模式特意将算法与其他部分分离开来,只是定义了与算法相关的接口(APl),然后在程序中以委托的方式来使用算法。
这样看起来程序好像变复杂了,其实不然。例如,当我们想要通过改善算法来提高算法的处理速度时,如果使用了Strategy模式,就不必修改Strategy角色的接口(API)了,仅仅修改ConcreteStrategy角色即可。
而且,使用委托这种弱关联关系可以很方便地整体替换算法。例如,使用Strategy模式编写棋类程序时,可以方便地根据棋手的选择切换AI电脑的水平。
至此,我们可以小结出策略模式的使用场景:
一个项目中有许多类,它们之间的区别仅在于它们的行为,希望动态地让一个对象在许多行为中选择一种行为时;
一个项目需要动态地在几种算法中选择一种时;
一个对象有很多的行为,不想使用多重的条件选择语句来选择使用哪个行为时。
策略模式不仅仅可以优化if else代码,其主要的作用还是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。除此之外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险。
可能有人会问:状态模式也可以优化if else,那么策略模式和状态模式又有什么不同呢?
让我们来简单回顾一下状态模式的类图:
使用策略模式和状态模式都可以替换被委托对象,而且它们的类之间的关系也很相似。但是两种模式的目的不同。
在策略模式中,ConcreteStrategy角色是表示算法的类。在Strategy模式中,可以替换被委托对象的类。当然如果没有必要,也可以不替换。
而在状态模式中,ConcreteState角色是表示“状态”的类。在State模式中,每次状态变化时,被委托对象的类都必定会被替换。
好了,关于策略模式我们就是介绍到这里。你在做项目时用过策略模式吗?是在什么场景中使用呢?欢迎留言说说。
最近热文: 1、Spring Boot 2.3.1 发布, 10 个新特性! 2、一周面试了 30 人,面到我心态爆炸… 3、求求你们别再写满屏的 try catch 了! 4、写了个全局变量的bug,被同事们打脸! 5、Java 14 祭出神器,Lombok 被干掉了? 6、为什么 Redis 单线程能达到百万+QPS? 7、Spring Boot 2.3 优雅关闭新姿势,真香! 8、Redis 到底是单线程还是多线程? 9、我天!xx.equals(null) 是什么骚操作?? 10、Struts2 为什么被淘汰?自己作死! 扫码关注 Java技术栈 公众号阅读更多干货。点击「」获取面试题大全~