👉文章示例代码👈

定义

一个工厂方法,根据传入的不同参数,生成对应的具体实例化对象。

简单工厂属于创建性模式,但是其不属于GOF23种设计模式

场景示例

假设笔者拥有很多品牌的手机(虽然笔者没有),这里以笔者选择品牌手机打电话作为示例。

创建基类(可以是接口或者是抽象类)

创建基类,基类可以是接口或者是抽象类。这里以创建手机接口为例。

/**
 * @author zhh
 * @description 手机接口
 * @date 2020-02-10 11:04
 */
public interface Phone {

    /**
     * 拨打电话
     */
    void call();
}

创建实现类

创建实现类实现手机接口。

/**
 * @author zhh
 * @description 苹果手机
 * @date 2020-02-10 11:07
 */
public class IPhone implements Phone {

    public void call() {
        System.out.println("使用苹果手机拨打电话");
    }
}

/**
 * @author zhh
 * @description 小米手机
 * @date 2020-02-10 11:08
 */
public class MiPhone implements Phone {

    public void call() {
        System.out.println("使用小米手机拨打电话");
    }
}

创建工厂类

创建手机工厂类,实现获取手机的方法,根据方法的入参生成对应实现类的对象。

使得应用层不依赖对应的具体实现类。

/**
 * @author zhh
 * @description 手机工厂类
 * @date 2020-02-10 11:09
 */
public class PhoneFactory {

    /**
     * 获取手机
     * @param type 手机品牌类型
     * @return
     */
    public static Phone getPhone(String type) {
        if ("iphone".equalsIgnoreCase(type)) {
            return new IPhone();
        } else if ("miphone".equalsIgnoreCase(type)) {
            return new MiPhone();
        }
        return null;
    }
}

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2020-02-10 11:11
 */
public class Test {

    public static void main(String[] args) {
        Phone phone = PhoneFactory.getPhone("iphone");
        if (phone != null) {
            phone.call();
        } else {
            System.out.println("无此手机品牌类型");
        }
    }
}

测试类的输出结果如下:

使用苹果手机拨打电话

类结构图

以上示例类的结构图如下所示

image.png

存在的问题

上述模式的实例其实还是可以继续扩展的,例如我们需要额外增加华为手机类型:

我们只需创建一个新的实现类 HuaweiPhone ,同时实现 Phone 接口,然后再在工厂类获取手机方法中增加一个关于创建 HuaweiPhone 实例对象的判断分支。

代码如下:

else if ("huaweiphone".equalsIgnoreCase(type)) {     return new HuaweiPhone(); }

使用简单工厂模式,我们会发现一个问题: 增加如上的判断分支修改了我们原有的工厂类方法。

即不满足于开闭原则中所规定的:

软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的

这个问题在工厂方法模式中得到了很好的解决。

方案改进

除了工厂方法外,我们还可以利用反射来弥补简单工厂的扩展性。

工厂类改进

/**
 * @author zhh
 * @description 手机工厂类
 * @date 2020-02-10 11:09
 */
public class PhoneFactory {

    /**
     * 获取手机(方案改进,利用反射)
     * @param clazz 类对象
     * @return
     */
    public static Phone getPhone(Class clazz) {
        try {
            return (Phone) Class.forName(clazz.getName()).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试类改进

/**
 * @author zhh
 * @description 测试类
 * @date 2020-02-10 11:11
 */
public class Test {

    public static void main(String[] args) {
        Phone phone = PhoneFactory.getPhone(IPhone.class);
        if (phone != null) {
            phone.call();
        } else {
            System.out.println("无此手机品牌类型");
        }
    }
}

这时候再去新增华为手机类型 HuaweiPhone ,我们只需要传递 HuaweiPhone 这个类对象进去即可,而工厂类是不需要变动的,这种改进从一定程度上满足了开闭原则。

源码中的用例

  • java.util.Calendar 类( createCalendar() 方法)
  • Class.forName("**") 加载数据库驱动
  • Logback (以具体实现 ch.qos.logback.classic.LoggerContext.getLogger() 方法为例)

总结

适用场景

  • 工厂类负责创建的对象比较少
  • 客户端只关注传入工厂类的参数,不关心实体类对象创建的具体过程

优点

  • 客户端不用去关心实体类对象创建的具体过程,从而达到了解耦的效果
  • 工厂类可以根据客户端输入的条件动态地去实例化对应的类

缺点

工厂类的职责相对过重,新增产品需要修改工厂类的判断逻辑,违背了开闭原则

参考

  • 《Head First 设计模式》
  • 《大话设计模式》