• 设计模式


    概述

    我们都知道Java中共有 23 种设计模式,其中工厂模式分为三种,即:简单工厂模式(不在 23 种设计模式之列)、工厂方法模式和抽象工厂模式;我们平时说的工厂模式,其实大都指工厂方法模式,这种模式是我们平时编码中用的频率最高的一种,在Spring源码中就有很多工厂模式的应用,比如 BeanFactory

    下面依次按照简单工厂模式、工厂方法模式、抽象工厂模式的顺序,依次由浅入深说说这三种模式;文章分别从定义、场景、优缺点也示例进行讲解。

    简单工厂模式

    定义

    简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例,简单来说就是,
    定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

    场景

    简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。总结一下就是:

    1. 需要创建的对象较少;
    2. 客户端不关心对象的创建过程;

    优缺点

    优点

    实现了对责任的分割,提供了专门的工厂类用于创建对象

    缺点

    工厂类的职责相对过重,不易于扩展过于复杂的产品结构,不符合开闭原则(可解决)

    示例

    接下来我们构造一个场景来看看简单工厂模式的应用:现在手机更新换代的比较快,手机厂商每年基本都会在不同时间或者在同一时间发布生产不同型号和配置的手机。

    假设某手机公司最近发布了型号为 A、B 的手机,其中生产任务交给代工厂去生产;我们都知道不管什么类型的手机都属于手机,所以我们先创建一个手机类Phone,并在其中声明一个公共的手机型号方法type

    /**
     * @author eamon.zhang
     * @date 2019-09-27 上午10:55
     */
    public interface Phone {
        void type();
    }
    

    然后定义具体的手机类型:

    型号 A:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 上午11:02
     */
    public class PhoneA implements Phone {
        @Override
        public void type() {
            System.out.println("型号为A的手机!");
        }
    }
    

    型号 B:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 上午11:03
     */
    public class PhoneB implements Phone {
        @Override
        public void type() {
            System.out.println("型号为B的手机!");
        }
    }
    

    创建手机代工厂 PhoneFactory 类:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 上午10:54
     */
    public class PhoneFactory {
        public Phone product(String type) {
            switch (type) {
                case "A":
                    return new PhoneA();
                case "B":
                    return new PhoneB();
                default:
                    return null;
            }
        }
    }
    

    测试:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 上午11:09
     */
    public class PhoneFactoryTest {
    
        @Test
        public void product() {
            PhoneFactory phoneFactory = new PhoneFactory();
            phoneFactory.product("A").type();
    
            phoneFactory.product("B").type();
        }
    }
    

    输出:

    型号为A的手机!
    型号为B的手机!
    

    当然,为了方便调用,PhoneFactory 中的product()也可以写成静态的。

    类图:

    拓展

    解决不符合开闭原则问题

    上面的示例中,客户端调用是简单了,但如果我们业务继续扩展,增加一个型号 C,那么上面的工厂方法中的product() 方法就得再次修改逻辑。不符合开闭原则;因此我们客户考虑对其进行进一步优化,利用反射技术修改product()方法:

     public Phone product(String className) {
        try {
            if (!(null == className || "".equals(className))) {
                return (Phone) Class.forName(className).newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    修改客户端调用代码:

    public void product() {
        PhoneFactory phoneFactory = new PhoneFactory();
        phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneA").type();
    
        phoneFactory.product("com.eamon.javadesignpatterns.factory.PhoneB").type();
    }
    

    经过优化之后,今后再增加型号,就不用去修改工厂方法了;但是又有一个问题,方法参数是很长的字符串,可控性有待提升,而且还需要强制转型,不方便阅读和维护,所以进一步改造:

    public Phone product(Class<? extends Phone> clazz) {
        try {
            if (null != clazz) {
                return clazz.newInstance();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

    优化客户端调用代码:

    @Test
    public void product() {
        PhoneFactory phoneFactory = new PhoneFactory();
        phoneFactory.product(PhoneA.class).type();
    
        phoneFactory.product(PhoneB.class).type();
    }
    

    再来看一下类图:


    其他

    简单工厂模式在 JDK 源码中也无处不足,比如常用的 Calendar类中Calendar.getInstance()方法,跟进源码到createCalendar(TimeZone zone,Locale aLocale)就可以看出。

    还有就是 常用的logback,我们可以看到 LoggerFactory 中有多个重载的方法 getLogger():

     public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
    
    public final Logger getLogger(final Class<?> clazz) {
        return getLogger(clazz.getName());
    }
    

    工厂方法模式

    定义

    工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这个 接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。

    在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符 合开闭原则。

    工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个手机的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,并不便于维护。根据单一职责原则我们将职能继续拆分,专人干专事。

    场景

    工厂方法适用于以下场景:

    1. 创建对象需要大量重复的代码。
    2. 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。
    3. 一个类通过其子类来指定创建哪个对象。

    优缺点

    优点

    1. 具有良好的封装性,代码结构清晰,井底了模块间的耦合。
    2. 拓展性非常优秀。(在增加产品类的情况下,只要修改具体的工厂类或扩展一个工厂类)
    3. 屏蔽了产品类。(产品类的实现如何变化,调用者不需要关心)

    缺点:

    1、类的个数容易过多,增加复杂度。
    2、增加了系统的抽象性和理解难度。

    示例

    A 型号手机由PhoneA工厂创建,B 型号手机由PhoneB工厂创建,对工厂本身也做一个抽象。来看代码,先创建 PhoneFactory 接口:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午1:45
     */
    public interface PhoneFactory {
       Phone product();
    }
    

    分别创建子工厂 PhoneAFactory

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午1:50
     */
    public class PhoneAFactory implements PhoneFactory {
        @Override
        public Phone product() {
            return new PhoneA();
        }
    }
    

    PhoneBFactory 类:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午1:50
     */
    public class PhoneBFactory implements PhoneFactory {
        @Override
        public Phone product() {
            return new PhoneB();
        }
    }
    

    看测试代码:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午1:54
     */
    public class PhoneFactoryTest {
    
        @Test
        public void product() {
            PhoneFactory factory = new PhoneAFactory();
            factory.product().type();
    
            factory = new PhoneBFactory();
            factory.product().type();
    
        }
    }
    

    测试结果:

    型号为A的手机!
    型号为B的手机!
    

    再看一下类图:

    拓展

    再来看看 logback 中工厂方法模式的应用,看看类图就 OK 了:


    抽象工厂模式

    定义

    抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类。

    客户端(应用层)不依赖于产品类实例如何被创建、实现等细节。强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

    理解

    为了便于大家理解抽象工厂,我们先了解两个概念产品等级结构和产品族,看下面的图:

    从上图中看出有正方形,圆形和三角形三种图形,相同颜色深浅的就代表同一个产品族,相同形状的代表同一个产品等级结构。同样可以从生活中来举例,比如,美的电器生产多种家用电器。那么上图中,颜色最深的正方形就代表美的洗衣机、颜色最深的圆形代表美的空调、颜色最深的三角形代表美的热水器,颜色最深的一排都属于美的品牌,都是美的电器这个产品族。再看最右侧的三角形,颜色最深的我们指定了代表美的热水器,那么第二排颜色稍微浅一点的三角形,代表海信的热水器。同理,同一产品结构下还有格力热水器,格力空调,格力洗衣机。

    再看下面这张图,最左侧的箭头代表具体的工厂,有美的工厂、海信工厂、格力工厂。每个品牌的工厂都生产洗衣机、热水器、空调。

    通过上面两张图的对比理解,相信大家对抽象工厂有了非常形象的理解。

    场景

    一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。简单来说:

    1. 和工厂方法一样客户端不需要知道它所创建的对象的类。
    2. 需要一组对象共同完成某种功能时。并且可能存在多组对象完成不同功能的情况。
    3. 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)

    优缺点

    优点

    • 封装性,每个产品的实现类不是高层模块要关心的,它要关心的是接口,不关心对象是如何创建的,只要知道工厂类是谁,就能创建出一个需要的对象,省时省力。
    • 产品族内的约束为非公开状态。

    缺点

    • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
    • 增加了系统的抽象性和理解难度

    示例

    比如现在有一个应用,假如是某视频软件,需要在三个不同的平台(Windows、IOS、Android)上运行,该应用针对每套系统都设计了一套上传控制器(UploadController)、播放控制(DisplayController),下面通过抽象工厂模式来设计该软件。

    视频软件里边的各个平台的UploadControllerDisplayController应该是我们最终生产的具体产品。所以新建两个抽象产品接口。

    UploadController 接口:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午2:59
     */
    public interface UploadController {
        void upload();
    }
    

    DisplayController 接口:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午2:59
     */
    public interface DisplayController {
        void display();
    }
    

    定义抽象工厂VideoPlayerFactory类,它能够创建UploadControllerDisplayController

    /**
     * 抽象工厂是主入口,在Spring中应用的最广泛的一种设计模式,易于扩展
     *
     * @author eamon.zhang
     * @date 2019-09-27 下午3:04
     */
    public interface VideoPlayerFactory {
        DisplayController createDisplayController();
    
        UploadController createUploadController();
    }
    

    然后在各个平台创建具体的 UploadControllerDisplayController

    创建适用于WindowsUploadControllerDisplayController

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:09
     */
    public class WindowsUploadController implements UploadController {
        @Override
        public void upload() {
            System.out.println("Windows 上传控制器!");
        }
    }
    
    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:09
     */
    public class WindowsDisplayController implements DisplayController {
    
        @Override
        public void display() {
            System.out.println("Windows 上的播放器!");
        }
    }
    

    创建适用于IOSUploadControllerDisplayController

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:10
     */
    public class IosUploaderController implements UploadController {
        @Override
        public void upload() {
            System.out.println("IOS 上传控制器!");
        }
    }
    
    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:09
     */
    public class IosDisplayController implements DisplayController {
    
        @Override
        public void display() {
            System.out.println("IOS 上的播放器!");
        }
    }
    

    创建适用于AndroidUploadControllerDisplayController

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:10
     */
    public class AndroidUploaderController implements UploadController {
        @Override
        public void upload() {
            System.out.println("Android 上传控制器!");
        }
    }
    
    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:09
     */
    public class AndroidDisplayController implements DisplayController {
    
        @Override
        public void display() {
            System.out.println("Android 上的播放器!");
        }
    }
    

    在各平台具体的工厂类中完成上传控制器和播放控制器的创建过程:

    创建WindowsFactory类:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:15
     */
    public class WindowsFactory implements VideoPlayerFactory {
        @Override
        public DisplayController createDisplayController() {
            return new WindowsDisplayController();
        }
    
        @Override
        public UploadController createUploadController() {
            return new WindowsUploadController();
        }
    }
    

    创建IosFactory类:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:17
     */
    public class IosFactory implements VideoPlayerFactory {
        @Override
        public DisplayController createDisplayController() {
            return new IosDisplayController();
        }
    
        @Override
        public UploadController createUploadController() {
            return new IosUploaderController();
        }
    }
    

    创建AndroidFactory类:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:18
     */
    public class AndroidFactory implements VideoPlayerFactory {
        @Override
        public DisplayController createDisplayController() {
            return new AndroidDisplayController();
        }
    
        @Override
        public UploadController createUploadController() {
            return new AndroidUploaderController();
        }
    }
    

    来看客户端调用:

    /**
     * @author eamon.zhang
     * @date 2019-09-27 下午3:20
     */
    public class VideoPlayerFactoryTest {
    
        @Test
        public void VideoPlayer() {
            VideoPlayerFactory factory = new WindowsFactory();
    
            // IOS
    //        factory = new IosFactory();
    //        // Android
    //        factory = new AndroidFactory();
    
            UploadController uploadController = factory.createUploadController();
            DisplayController displayController = factory.createDisplayController();
    
            uploadController.upload();
            displayController.display();
    
        }
    }
    

    以调用 Windows 为例,结果:

    Windows 上传控制器!
    Windows 上的播放器!
    

    上面就是针对不同平台只通过创建对应的工厂对象就完成了上传控制器和播放控制器的创建。抽象工厂非常完美清晰地描述这样一层复杂的关系。但是,不知道大家有没有发现,如果我们再继续扩展功能,将下载器也加入到产品中,那么我们的代码从抽象工厂,到具体工厂要全部调整,很显然不符合开闭原则。因此就有了上面优缺点中所说的缺点。


    总结

    在实际应用中,我们千万不能犯强迫症甚至有洁癖。在实际需求中产品等级结构升级是非常正常的一件事情。我们可以根据实际情况,只要不是频繁升级,可以不遵循开闭原则。代码每半年升级一次或者每年升级一次又有何不可呢?

    源码:github.com

  • 相关阅读:
    OCP 071【中文】考试题库(cuug整理)第39题
    OCP 071【中文】考试题库(cuug整理)第38题
    OCP 071【中文】考试题库(cuug整理)第37题
    OCP 071【中文】考试题库(cuug整理)第36题
    OCP 071【中文】考试题库(cuug整理)第35题
    OCP 071【中文】考试题库(cuug整理)第34题
    POST 数据的丢失 +号
    Windows下使用 sqlmap 测试注入
    多部电梯的测试用例
    如何测试购物车?
  • 原文地址:https://www.cnblogs.com/eamonzzz/p/11610897.html
Copyright © 2020-2023  润新知