JAVA设计模式之工厂模式

前言

        本文将对工厂模式进行详细的分析与讲解,主要脉络是由浅入深,从简单工厂到抽象工厂模式,逐渐地递进分析并体现优化过程,阐述简单工厂、工厂方法、抽象工厂之间的关系,认清每种模式的定位以及如何理解。本文章是笔者查阅网上资料并结合自己的理解总结而来,若有不同见解的网友欢迎参与讨论共同进步。

提问:工厂模式设计的初衷是什么?为什么会产生工厂模式?
在计算机领域发展的历史长河中,有一个一直存在并且一直不断地被优化的问题就是如何解除代码的耦合性;这个问题也是领域内专家们一直讨论的问题,于是才有了设计模式相关的书籍。恰巧工厂模式也是其中之一,而工厂模式针对的主要目标是解决创建对象和应用对象代码耦合,其次是提高创建对象代码的复用性。

工厂模式的种类:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式

一、简单工厂模式

工厂的作用:生产出统计标准的产品;但是在java中通常被用于生产出统一标准的对象。

1、初级阶段
举一个生活中的实例:假如一个小型的雪糕厂刚刚成立目前产品单一,只能生产出一种口味的雪糕,那么该工厂用java代码表示就是一个最简单的简单工厂模式的实例;代码如下:
 

雪糕A类型:

public class IceA {
    public final String name = "雪糕A";
}

刚成立的雪糕厂:

/**
 * 刚成立的小型雪糕厂
 * 产品范围:只能生产一种产品,即IceA
 */
public class SmallIceFactory {
    public static IceA make(){
        return new IceA(); // 新创建一个IceA
    }

    // 测试
    public static void main(String[] args) {
        IceA product = SmallIceFactory.make();
        System.out.println("product = " + product.name);
    }

    // 程序输出:product = 雪糕A
}

可以看出一个简单工厂的实现非常简单,只需要创建一个工厂类并且定义一个静态方法,在方法中new一个IceA并进行return即可!
以上的实现可以进行一点改进就是将工厂类和产品类合并到一起,如下:

与工厂合并后的IceA

public class IceA {
    public final String name = "雪糕A";

    public static IceA getInstance(){  // 提过本类实例的静态方法
        return new IceA();
    }
    // 测试
    public static void main(String[] args) {
        IceA instance = IceA.getInstance();
        System.out.println("instance.name = " + instance.name);
    }
    // 程序输出:instance.name = 雪糕A
}

简单粗暴的工厂模式在JDK源码中也有很多应用,例如Integer类型:

JDK源码中的应用

// Integer 类中部分代码截取
public static Integer valueOf(int i) {  // 该静态方法作用:new一个实例并返回
    if (i >= IntegerCache.low && i <= IntegerCache.high) // 小数据池缓存
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);  // 新建Integer对象
}

但是若仔细思考下,会发现许多隐藏的问题,比如将来出品第二种口味的雪糕该怎么办?
有些初学者可能想到进行如下扩展:

添加新口味雪糕

// 新口味雪糕类IceB
public class IceB {
    public final String name = "雪糕B";
}

/**
 * 工厂类做了如下修改
 */
public class SmallIceFactory {
    public static Object make(String type){
        if ("B".equals(type)){
            return new IceB();
        }
        return new IceA(); // type为null,默认创建IceA
    }

    public static void main(String[] args) {
        IceB b = (IceB) SmallIceFactory.make("B");
        System.out.println("b.name = " + b.name);
        IceA a = (IceA) SmallIceFactory.make("A");
        System.out.println("a.name = " + a.name);
    }
    // 程序输出:
    // b.name = 雪糕B 
    // a.name = 雪糕A
}

像上面这样为了实现兼容新口味,直接在工厂类中进行修改,着实有点不妥!于是抽象类和接口便有了用武之地。(这里用的是接口)

2、优化:将雪糕定义为抽象接口

雪糕抽象接口

public interface Ice {
    /**
     * 将雪糕抽象成冰冻的奶油
     * 方法细节由具体实现类定义
     */
    void frozenCream();
}

所有口味的雪糕都实现该接口

// IceA类型雪糕
public class IceA implements Ice{
    public final String name = "雪糕A";

    // 实现接口方法
    @Override
    public void frozenCream() {
        System.out.println("name = " + name);
    }
}

// IceB类型雪糕
public class IceB implements Ice{
    public final String name = "雪糕B";

    @Override
    public void frozenCream() {
        System.out.println("name = " + name);
    }
}

将雪糕产品进行抽象后,在构建工厂类时就可以将返回值类型声明为抽象类型,代表的就是雪糕类型,但是工厂生产的实例实际上是抽象类的子类或实现完成的。那么如何获取期望类型的实例呢?这里可以利用反射机制来实现。

通过反射机制实现开闭原则

public class IceFactory {
    public static Ice make(String name){
        if (!StringUtils.isBlank(name)) {  // 若字符串不为空
            try {
                return (Ice) Class.forName(name).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
	// 测试
    public static void main(String[] args) {
        Objects.requireNonNull(IceFactory.make("com.cars.ict.core.testForStd.product.IceA")).frozenCream();
        Objects.requireNonNull(IceFactory.make("com.cars.ict.core.testForStd.product.IceB")).frozenCream();
    }
    // 程序输出:
    // name = 雪糕A
	// name = 雪糕B
}

上边的反射是以name的方式获取class对象,还有另一种实现,直接将make方法的入参类型从String改成Class类型,代码如下:

make方法以Class对象为入参

public class IceFactory {
    public static Ice make(Class aClass){ // 入参直接是Class对象
        if (aClass != null) {
            try {
                return aClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    // 测试
    public static void main(String[] args) {
        IceFactory.make(IceA.class).frozenCream();
        IceFactory.make(IceB.class).frozenCream();
    }
    // 程序输出:
    // name = 雪糕A
    // name = 雪糕B
}

以上是展示了两种反射机制的实现方式,两种各有各的特点:

  1. 第一种形式下make方法的入参类型是String,这样的好处就是参数的内容是动态的,我们可以将其写到配置文件中,通过读取配置文件来动态指定类型;但是缺点就是有可能找不到类,会抛出异常。
  2. 第二种形式下make方法的如参类型就是Class类型,这样的好处在于方便编程,编程体验较好,有一丢丢硬编码的感觉,但是不会出现找不到类的异常,因为该异常在编译期就已经确认了。

二、工厂方法模式

工厂方法模式又称多态性工厂模式,顾名思义,一个工厂(工厂指的是抽象)多种形态,具体的形态由其子类体现;具体创建实例的方式就由子类来决定,这样即使有产品扩展也不必修改原有代码,只需要重新创建一个子类即可,提高了产品扩展力,符合开闭原则。

1、Common工厂方法模式
工厂方法模式也用到了简单工厂模式中的产品抽象。

工厂抽象

/**
 * 抽象工厂接口
 */
public interface IFactory {
    Ice make();  // 此方法由子类实现,集体操作细节将体现在子类中。
}

工厂抽象的实现类

/**
 * 生产雪糕A的工厂
 */
public class IceAFactory implements IFactory{
    @Override
    public Ice make() {
        return new IceA(); // 创建IceA对象
    }
}

/**
 * 生产雪糕B的工厂
 */
public class IceBFactory implements IFactory{
    @Override
    public Ice make() {
        return new IceB(); // 创建IceB对象
    }
}
// ----------------------------test----------------------------
public class TestFactory {
    public static void main(String[] args) {
        IFactory iceAFactory = new IceAFactory();
        IFactory iceBFactory = new IceBFactory();
        Ice A = iceAFactory.make(); // 生产雪糕A
        Ice B = iceBFactory.make(); // 生产雪糕B
        A.frozenCream();
        B.frozenCream();

        // 程序输出:
        // name = 雪糕A
		// name = 雪糕B
    }
}

上述就是标准的工厂方法模式,这种模式的优点正如上边体现出来的,可以通过新建IFactory的实现类来进行产品的横向扩展。

2、工厂方法优化
场景设想:假如这个新创建的小雪糕工厂由于市场效应太火,想进一步做大做强,于是就拓展了生产蛋糕业务;那么问题来了,原来工厂是只做雪糕的啊,现在又要做蛋糕了,原来的IFactory不好用了,这该怎麽办?
于是就有了如下的改进。

工厂方法模式优化

/**
 * 抽象工厂接口
 */
public interface IFactory {
    T make(Class cls);  // 此方法由子类实现,集体操作细节将体现在子类中。
}

在原先的基础上将上了泛型,泛型的作用是让该抽象适用更多系列的产品,比如雪糕和蛋糕等,另外make方法也增加了Class对象为入参。

蛋糕产品

/**
 * 蛋糕抽象
 */
public interface Cake {

    /**
     * 将蛋糕抽象成加了牛奶的食品
     */
    void withMilk();
}

/**
 * 蛋糕A
 */
public class CakeA implements Cake{
    public final String name = "蛋糕A";
    @Override
    public void withMilk() {
        System.out.println("name = " + name);
    }
}

/**
 * 蛋糕B
 */
public class CakeB implements Cake{
    public final String name = "蛋糕B";
    @Override
    public void withMilk() {
        System.out.println("name = " + name);
    }
}

相关的子实现工厂

/**
 * 综合性雪糕工厂:可以生产雪糕A和雪糕B等等
 */
public class IceCompFactory implements IFactory{
    @Override
    public Ice make(Class cls) {
        if (cls != null) {
            try {
                // 此处可以自定义细节
                return cls.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

/**
 * 综合性蛋糕工厂:可以生产蛋糕A和蛋糕B等等
 */
public class CakeCompFactory implements IFactory{
    @Override
    public Cake make(Class cls) {
        if (cls != null) {
            try {
                // 此处可以自定义细节
                return cls.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

上边两种子实现类的主要改进是应用了泛型和反射。

测试

public class TestFactory {
    public static void main(String[] args) {
        IFactory iceCompFactory = new IceCompFactory();
        Ice A = iceCompFactory.make(IceA.class);
        Ice B = iceCompFactory.make(IceB.class);
        System.out.println("--------------雪糕实例--------------");
        A.frozenCream();
        B.frozenCream();

        IFactory cakeCompFactory = new CakeCompFactory();
        System.out.println("--------------蛋糕实例--------------");
        cakeCompFactory.make(CakeA.class).withMilk();
        cakeCompFactory.make(CakeB.class).withMilk();
    }
    // 程序输出:
    // --------------雪糕实例--------------
    // name = 雪糕A
    // name = 雪糕B
    // --------------蛋糕实例--------------
    // name = 蛋糕A
    // name = 蛋糕B
}

优化后的工厂方法模式可以进行跨系列产品的扩展。

工厂方法模式在源码中的应用

在LogBack中的应用。

public interface ILoggerFactory {
    Logger getLogger(String var1);
}
public class NOPLoggerFactory implements ILoggerFactory {
    public NOPLoggerFactory() {
    }

    public Logger getLogger(String name) {
        return NOPLogger.NOP_LOGGER;
    }
}
.....

三、抽象工厂模式

概念:抽象工厂模式适用于生产产品族的情景。一个子类工厂会生产出多种产品,且这些产品要么在一个产品族中,要么有互相依存的关系;例如电子产品的系列,在一个系列中可能会存在手机、电脑、手表等,它们都处于同一产品族;或者是存在某些依存的关系,主板、键盘、cpu等等。


产品族和依存关系:族和依存关系的概念有些微妙,它没有一个固定的模式,例如站在单个商品的角度,可能是商品的各个部件之间就存在依存关系;再例如站在大厂商的角度,这个族的概念就是产品系列。

工厂抽象

// A与B相互依存或属于同族
public interface IAbstractFactory {
    IProductA makeProductA();

    IProductB makeProductB();
}

这个工厂抽象接口中有多个创建实例的抽象方法。

产品抽象

public interface IProductA {
    void doA();
}
public interface IProductB {
    void doB();
}

A和B两种产品接口都有多个子实现类

A和B的产品实现

public class ProductA1 implements IProductA {
    @Override
    public void doA() {
        System.out.println("A1 属于 A");
    }
}
public class ProductA2 implements IProductA{
    @Override
    public void doA() {
        System.out.println("A2 属于 A");
    }
}
public class ProductB1 implements IProductB{
    @Override
    public void doB() {
        System.out.println("B1 属于 B");
    }
}
public class ProductB2 implements IProductB{
    @Override
    public void doB() {
        System.out.println("B2 属于 B");
    }
}

在A和B抽象下分别实现了两个子类A1、A2和B1、B2。

两个工厂实现类

public class Factory1 implements IAbstractFactory{
    @Override
    public IProductA makeProductA() {
        return new ProductA1();
    }

    @Override
    public IProductB makeProductB() {
        return new ProductB1();
    }
}
public class Factory2 implements IAbstractFactory{
    @Override
    public IProductA makeProductA() {
        return new ProductA2();
    }

    @Override
    public IProductB makeProductB() {
        return new ProductB2();
    }
}

上边的两个工厂可以看成是两个族,每个族下都有自己的A产品和B产品,其实就是对族的展开和A、B商品的聚合。

抽象方法模式在源码中的应用

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

     T getBean(String var1, Class var2) throws BeansException;

     T getBean(Class var1) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

     T getBean(Class var1, Object... var2) throws BeansException;

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class var2) throws NoSuchBeanDefinitionException;

    Class getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}


抽象工厂在Spring中的使用(BeanFactory)

抽象工厂的优缺点
优点:

  • 当需要扩展产品族时,只需要增加新的具体工厂实现类,不需要对已有的代码进行修改,符合开闭原则。
  • 对于客户端,只需要调用同一个产品的产品族。

缺点:

  • 产品族中需要扩展新的产品时较为困难,需要修改抽象接口。
  • 继承和实现架构与前两种相比较为复杂。

参考文献:https://www.cnblogs.com/amazing-yml/p/15947321.html

原文地址:java工厂模式详解-CSDN博客

你可能感兴趣的:(JAVA设计模式,设计模式,开发语言,java)