设计模式 - 工厂模式

概述

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

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

简单工厂模式

定义

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

场景

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

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

优缺点

优势

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

缺点

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

示例

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

假设某手机公司最近发布了型号为 A、B 的手机,其中生产任务交给代工厂去生产;咱们都知道无论什么类型的手机都属于手机,因此咱们先建立一个手机类Phone,并在其中声明一个公共的手机型号方法type编码

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

而后定义具体的手机类型:spa

型号 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. 屏蔽了产品类。(产品类的实现如何变化,调用者不须要关心)

缺点:

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

示例

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

相关文章
相关标签/搜索