设计模式之策略模式+工厂模式

1、前序

问题背景是最近在看代码时,看到好多if else,然后就在看能否对应做出些优化,所以就有了这篇文章。

思路来源:https://www.bilibili.com/video/BV1b5411a7oa?spm_id_from=333.880.my_history.page.click&vd_source=b120cbf8ba336884303a76b168648a14

2、直接上代码

随便写个测试类,假设我们原先代码是这样的,全是if-else,这种

@SpringBootTest
public class FansApplication {
    @Test
    public void noDesign(){
        String name = "zhangsan";
        if (name.equals("zhangsan")){
            System.out.println("zhangsan AAA");
        }else if (name.equals("lisi")){
            System.out.println("zhangsan AAA");
        }else if (name.equals("wangwu")){
            System.out.println("zhangsan AAA");
        }else {
            System.out.println("qt AAA");
        }
    }
}

如果后续,我们再出现其他场景,则还需要再在后面添加相应的if,else,不符合我们代码的开闭原则,需要改动我们原有的代码。
所以,就需要上本次主角,策略模式和工厂模式;
在使用策略模式前,我们先来看下到底什么是策略模式?
-引用自: https://www.runoob.com/design-pattern/strategy-pattern.html

  • 介绍意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
  • 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
  • 如何解决:将这些算法封装成一个一个的类,任意地替换。
  • 关键代码:实现同一个接口。
  • 应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
  • 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
  • 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
  • 使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
  • 注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

然后,我们来把上述所讲的几种实现方法,抽离出成为单独的策略;

import org.springframework.beans.factory.InitializingBean;

public interface Handler extends InitializingBean {
     /**
      * 实现策略模式的总接口
      * @param name
      */
     void doSomeThing(String name);
}

然后把每种情况要做的事情,放到我们单独的实现类去实现。
张三的实现类

public class ZhangsanHandler implements Handler {
    @Override
    public void doSomething(String name) {
        System.out.println("张三 doSomething");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
    }
}

李四的实现类

public class LisiHandler implements Handler {
    @Override
    public void doSomething(String name) {
        System.out.println("李四 doSomething");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
    }
}

这样,我们总体的代码实现,就优化成了第二个版本:

@SpringBootTest
public class MyFactoryTest {
    public void noDesign(){
        String name = "zhangsan";
        if (name.equals("zhangsan")){
            new ZhangsanHandler().doSomething(name);
        }else if (name.equals("lisi")){
            new LisiHandler().doSomething(name);
        }else {
            System.out.println("qt AAA");
        }
    }
}

但是,这样代码的可读性和美观性并不强,随着代码的扩展还会出现代码荣冗余问题,if-else还没完全消除,这里我们的工厂模式就可以登场了。

工厂模式详细可见:https://www.runoob.com/design-pattern/factory-pattern.html

  • 介绍:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

  • 主要解决:主要解决接口选择的问题。

  • 何时使用:我们明确地计划不同条件下创建不同实例时。

  • 如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

  • 应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。

  • 优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。

  • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

  • 使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

  • 注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

引入一个工厂,将我们需要用到的各种场景都装配到springboot里来管理。


import org.springframework.util.StringUtils;
import java.util.HashMap;
/**
 * 工厂设计模式
 */
public class MyFactory {
    /**
     * HashMap,第一次参数代码对象,第二个参数代表对象对应的策略方法
     */
    private static HashMap<String,Handler> strategyMap = new HashMap();

    /**
     * 通过对象获取对应的策略类
     * @param name
     * @return
     */
    public static Handler getInvokeStrategy(String name) {
        return strategyMap.get(name);
    }

    /**
     * 将策略类put进HashMap
     * @param name
     * @param handler
     */
    public static void register(String name,Handler handler) {
        if (!StringUtils.hasText(name)||handler==null){
            return;
        }
        strategyMap.put(name,handler);
    }
}

那么各个策略实现类是如何注册到HashMap的呢,上面说到Handler继承了spring的一个InitializingBean,前面还没用到,该方法我们主要用于在spring加载的时候,实现一些初始化的操作,学过vue的小伙伴,可以把它当作vue里面的created钩子函数。

InitializingBean用法介绍
当一个类实现这个接口之后,Spring启动后,初始化Bean时,若该Bean实现InitialzingBean接口,会自动调用afterPropertiesSet()方法,完成一些用户自定义的初始化操作。

看下具体代码实现:

import org.springframework.stereotype.Component;

@Component
public class ZhangsanHandler implements Handler {
    @Override
    public void doSomething(String name) {
        System.out.println("张三 doSomething");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        MyFactory.register("张三",this);
    }
}

一定要注意添加@Component注解,表示将代码交给springboot托管,否则代码不会生效,这样在springboot在启动完成之后,我们的Handler就被装配进了spring容器了。

然后再来看下简化后的代码实现:

    public void useDesign(){
        String name = "张三";
        Handler invokeStrategy = MyFactory.getInvokeStrategy(name);
        invokeStrategy.doSomething(name);
    }

完美~

3、小结

上述代码,就可以简化成为三行了,刚学设计模式,可能会有这样的疑问,上面写了这么多,没觉得代码减少了多少,反而觉得代码变的更加繁琐了,而且也只是把代码分散开了而已。

但是,代码的冗余,并不仅仅是用代码量的多少来评判的,还需要考虑到代码的可读性和可扩展性。

改造成上述框架后,如果后续还会再添加其他实现方式,只需要将实现类注册到工厂即可,无需改动暴露在外面的现有代码,实现了我们程序的解耦,符合我们代码的开闭原则~

以上,与君共勉~

posted @ 2022-06-14 22:18  进阶的蜗牛  阅读(769)  评论(0编辑  收藏  举报