设计模式实践笔记第一节:工厂方法模式

Posted 滕哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式实践笔记第一节:工厂方法模式相关的知识,希望对你有一定的参考价值。

前言

工作有段时间了,一直没有把自己所知所想沉淀下来,想趁着重新学习设计模式的机会,记录一些自己的心得体会。

大学里也有学过设计模式,那会没有实际的项目经验,感触不是很深,工作以后跟人讨论设计模式,有人觉得是圣经,是解决开发问题的银弹,也有人觉得设计模式让系统复杂又难懂,弃之如糟粕,经历不同对事物的理解也不一样。

我的看法是设计模式很重要,但不是所有工程都必须要遵守的,有些项目只有寥寥几个 CRUD ,再怎么折腾,维护的复杂程度和工作量也不会很大;对于复杂软件的设计与维护来说,设计模式则可以大大减轻维护难度,还是十分必要的。而我们随着自身的成长,所面对的业务和系统也会越来越复杂,所以学习设计模式并在合适的时候用它来让我们的系统变得更好,是十分必要的。

基于此,我们先了解一下设计模式的基本概念和背景。

设计模式是什么?

设计模式(Design pattern)是在软件开发过程常见问题的解决方案,由众多软件开发人员的设计和实践经验总结而来,并且被反复使用来提高代码的可扩展性和可维护性。

设计模式是谁发明的?

1994年埃里希·伽玛(Erich Gamma)、理查德·赫尔姆(Richard Helm)、拉尔夫·约翰逊(Raplh Johnson)和约翰·弗利赛德斯(Jonhn Vlissides) 四个人合写了Design Patterns一书,在该书中提到了设计模式。这四人也被称为四人帮(GoF)。

为什么要用设计模式?

设计模式不是必须要遵循的,有时可能是累赘,但是软件开发要做到可维护、可拓展,那必须要尽量复用代码,降低代码的耦合度,而设计模式提供了一种比较好的解决方案。

工厂方法模式

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

工厂方法模式又叫工厂模式,是一种非常常见的设计模式,我们熟知的日志记录器、数据库连接实现通常都使用了这种模式来进行「创建」和「使用」解耦,增强代码扩展性的同时也对复杂、具体的实现逻辑进行屏蔽。

相比较面条型的代码,工厂方法模式的优点是让调用者只需要关心产品接口,但这也让类的个数成倍增加,如何权衡利弊还是要放到具体的项目中去探究。

汽车工厂

下面通过一个汽车工厂的例子来说明,在这个例子中我们的抽象「产品」就是汽车,具体的「产品」包括小轿车和SUV,我们先给汽车一个基础的「行驶」能力。

1.定义一个产品接口。

public interface Car 
    /**
     * 行驶
     */
    void drive();

2.给出具体的产品实现。

public class SportCar implements Car 
    /**
     * 行驶
     */
    @Override
    public void drive() 
        System.out.println("小轿车行驶在路上...");
    


public class SuvCar implements Car 
    /**
     * 行驶
     */
    @Override
    public void drive() 
        System.out.println("SUV汽车行驶在路上...");
    

假设我们的汽车交给某斯拉来生产,只要我们对工厂输入生产指令,就会从流水线生产出对应型号的汽车。

3.创建一个工厂,基于给定车型生产对应的汽车。

public class TeslaCarFactory 
    /**
     * 生产汽车
     */
    public Car create(String type) 
        if ("sport".equals(type)) 
            return new SportCar();
         else if ("suv".equals(type)) 
            return new SuvCar();
        
        throw new RuntimeException("生产指令错误");
    

4.使用工厂来生产产品。

public class FactoryDemo 
    public static void main(String[] args) 
        // 实例化一个工厂
        TeslaCarFactory factory = new TeslaCarFactory();

        // 生产一台跑车
        Car sportCar = factory.create("sport");
        sportCar.drive();

        // 生产一台跑车
        Car suvCar = factory.create("suv");
        suvCar.drive();
    

输出:

小轿车行驶在路上...
SUV汽车行驶在路上...

至此我们就完成了一个普普通通的工厂模式学习。

通过这个例子不难发现工厂方法模式只跟产品定义、产品实现和工厂这三个要素有关系,而我们开发过程中最常见到的更是使用静态工厂方法来实现创建产品的逻辑,比如 Integer.valueOf()LoggerFactory.getLogger(clazz) 等。

当然了,仅仅了解工厂模式不是我们的目的,还要在具体的实践中使用模式来完成编码,让我们的系统可扩展性更强,可维护性更高。

现实中我们常常会遇到一堆老系统需要我们维护和开发,在经年累月的沉积之后,各种糟粕会让我们繁琐不堪,现在我们模拟从最简单的登录功能进行切入,将其进行彻底改造!

使用工厂模式重构登录!

在我们的系统中支持用户名+密码手机号+验证码的登录方式,以后还会支持邮箱+密码手机号+密码的登录,如果按照现在的逻辑继续添加功能,会导致 controller 代码十分臃肿难以维护,而且也不符合软件开发的职责单一原则和开闭原则。

下面是原始的登录功能代码结构:

登录功能的主要逻辑代码:

public class LoginController 

    /**
     * 登录
     */
    public String login(LoginRequest request) 
        if (request.getLoginType() == 1) 
            UsernameLoginService loginService = new UsernameLoginService();
            return loginService.login(request.getUuid(), request.getPassword());
         else if (request.getLoginType() == 2) 
            PhoneLoginService loginService = new PhoneLoginService();
            return loginService.login(request.getUuid(), request.getPassword());
         else 
            throw new RuntimeException("登录出错啦!");
        
    

主要的逻辑是根据不同的登录类型,完成登录校验和生成 token 。继续添加新功能只能在这里继续写 else if

通过分析我们看到不同的登录类型对应了不同的登录服务,决策的逻辑都写在 controller 里,导致代码混乱,职责不清。按照面对抽象编程的思想,我们首先想到的是让 controller 只对登录服务接口有依赖。

抽象出登录服务:

public interface LoginService 
    String login(String uuid, String password);

两个具体的登录服务实现:

public class PhoneLoginService implements LoginService 

    @Override
    public String login(String phoneNumber, String code) 
        return "token";
    


public class UsernameLoginService implements LoginService 
    @Override
    public String login(String username, String password) 
        return "token";
    

登录服务工厂:

public class LoginServiceFactory 
    public static LoginService get(Integer loginType) 
        if (loginType == 1) 
            return new UsernameLoginService();
         else if (loginType == 2) 
            return new PhoneLoginService();
         else 
            throw new RuntimeException("登录出错啦!");
        
    

这里使用静态方法来简化客户端的调用。所以重构以后的 controller 代码如下:

public class LoginController 

    /**
     * 登录
     */
    public String login(LoginRequest request) 
        LoginService loginService = LoginServiceFactory.get(request.getLoginType());
        return loginService.login(request.getUuid(), request.getPassword());
    

可以看到经过重构以后,登录 controller 只需要关心出入参和登录流程编排,而不需要再跟具体的登录逻辑耦合,那么后续再新增登录实现只需要增加登录服务的实现类即可。

小结

工厂模式三要素:产品定义产品实现工厂;调用方只跟产品定义和工厂进行交互,对外屏蔽实现细节,创建产品被延迟到子类实现。

通过以上学习和实践,我们已经对工厂模式有所了解,不过知易行难,趁现在赶紧行动起来吧,实践出真知!

版权声明:本文为公众号「Java实践笔记」的原创文章,转载请联系作者,附上原文出处链接及本声明。
原文链接:https://mp.weixin.qq.com/s/33YfZdDnIFJfDw5wDaa0bA

以上是关于设计模式实践笔记第一节:工厂方法模式的主要内容,如果未能解决你的问题,请参考以下文章

Java描述设计模式(04):抽象工厂模式

设计模式实践笔记第二节:抽象工厂模式

设计模式实践笔记第二节:抽象工厂模式

设计模式实践笔记第二节:抽象工厂模式

23种设计模式之抽象工厂

oop设计模式抽象总结