1、简介
工厂设计模式,可能是我们开发过程中无形之中使用的最多的设计模式。工厂设计模式包括简单工厂(Simple Factory)、方法工厂(Method Factory)、抽象工厂(Abstract Factory),其中简单工厂设计模式并包含在 GOF23 种设计模式之中,但是其使用也十分广泛;这三种设计模式之间存在一定的关系,层层递进;但是三种工厂设计模式各自有各自适用的场景,在实际开发中选择设计模式应该深思熟虑。三种工厂设计模式之间的关系类比图如下:
2、大纲
本文围绕简单工厂(Simple Factory)、方法工厂(Method Factory)、抽象工厂(Abstract Factory)三种设计模式开展,与大家共同学习。大纲图如下:
3、简单工厂
3.1 说明
简单工厂模式(Simple Factory)是一个可以生成不同产品的类,我认为可以类比为一个工具类,用于将多个产品的创建聚合到一起。本文的开展介绍将围绕我华为、小米、苹果三个系列的产品开展。(李子捌支持国产,但也兼容并包,我们时刻保持学习,努力追赶超越,国产加油!)
简单工厂 UML 图示
3.2 使用场景
简单工厂主要适合产品类较少、设计上能固定个数的时候,我们通过简单工厂来获取对象,屏蔽对象生成的具体细节。简单工厂代码耦合,不符合开闭原则,不适合过多产品(实现类)的场景下使用。
3.3 使用举例
如下例子创建了如下几个接口与类的层级关系
移动手机接口 IMobilePhone
华为手机 HuaweiPhone、小米手机 XiaomiPhone、苹果手机 IPhone 分别实现了 IMobilePhone 接口,并重写其中的方法抽象
简单工厂 MobilePhoneFactory 用于根据客户端(client)的调用传参创建并返回指定的手机实例对象
PhoneTypeEnum 是一个枚举类,用于客户端传参,可以字符串等常量代替均可
3.3.1 移动手机接口 IMobilePhone 代码示例
package com.liziba.pattern.factory;
/**
* <p>
* 移动手机接口
* </p>
*
* @Author: Liziba
*/
public interface IMobilePhone {
/**
* 发消息
*/
void sendShortMessage();
/**
* 打电话
*/
void call();
}
复制代码
3.3.2 接口三个实现类代码示例
1、华为手机实现类
package com.liziba.pattern.factory;
/**
* <p>
* 华为手机
* </p>
*
* @Author: Liziba
*/
public class HuaweiPhone implements IMobilePhone {
@Override
public void sendShortMessage() {
System.out.println("Huawei phone send short message...");
}
@Override
public void call() {
System.out.println("Huawei phone call...");
}
}
复制代码
2、小米手机实现类
package com.liziba.pattern.factory;
/**
* <p>
* 小米手机
* </p>
*
* @Author: Liziba
*/
public class XiaomiPhone implements IMobilePhone {
@Override
public void sendShortMessage() {
System.out.println("Xiaomi phone send short message...");
}
@Override
public void call() {
System.out.println("Xiaomi phone call...");
}
}
复制代码
3、苹果手机实现类
package com.liziba.pattern.factory;
import com.liziba.pattern.factory.IMobilePhone;
/**
* <p>
* 苹果手机
* </p>
*
* @Author: Liziba
*/
public class IPhone implements IMobilePhone {
@Override
public void sendShortMessage() {
System.out.println("IPhone phone send short message...");
}
@Override
public void call() {
System.out.println("IPhone phone call...");
}
}
复制代码
3.3.3 简单工厂代码示例
客户端在调用简单工厂生产所需产品实例时,需要入参用于区分实例化哪个具体的产品。如下展示的枚举方式:
package com.liziba.pattern.factory.simpleFactory;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.HuaweiPhone;
import com.liziba.pattern.factory.IPhone;
import com.liziba.pattern.factory.XiaomiPhone;
/**
* <p>
* 手机简单工厂
* </p>
*
* @Author: Liziba
* @Date: 2021/6/28 21:14
*/
public class MobilePhoneFactory {
public static IMobilePhone getMobilePhone(PhoneTypeEnum phoneType) {
IMobilePhone phone = null;
switch (phoneType) {
case HUAWEI:
phone = new HuaweiPhone();
break;
case XIAOMI:
phone = new XiaomiPhone();
break;
case IPHONE:
phone = new IPhone();
break;
default:
break;
}
return phone;
}
/**
* 枚举类用于区分手机类型,本应写出去,为了减少类的示例个数内置于简单工厂中
* 也可以用字符串或者其他常量代替均可(但是我不推荐这种)
*/
enum PhoneTypeEnum {
HUAWEI("华为", "A"),
XIAOMI("小米", "B"),
IPHONE("苹果", "C");
private String name;
private String value;
PhoneTypeEnum(String name, String value) {
this.name = name;
this.value = value;
}
}
}
复制代码
如果觉得枚举导致类过多,也建议使用泛型来约束可入参的范围,同时使得简单工厂正确的实例化指定的产品。这种代码的写法非常简洁,其示例代码如下:
package com.liziba.pattern.factory.simpleFactory;
import com.liziba.pattern.factory.IMobilePhone;
/**
* <p>
* 反射简单工厂示例代码
* </p>
*
* @Author: Liziba
*/
public class MobilePhoneFactory {
public static IMobilePhone getMobilePhone(Class<? extends IMobilePhone> clz) {
IMobilePhone phone = null;
if (null != clz) {
try {
phone = clz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
return phone;
}
}
复制代码
3.3.4 客户端调用示例
package com.liziba.pattern.factory.simpleFactory;
import com.liziba.pattern.factory.IMobilePhone;
/**
* <p>
* 简单工厂测试
* </p>
*
* @Author: Liziba
*/
public class Test {
public static void main(String[] args) {
// 华为手机
IMobilePhone phone = MobilePhoneFactory.getMobilePhone(MobilePhoneFactory.PhoneTypeEnum.HUAWEI);
phone.sendShortMessage();
phone.call();
// 小米手机
phone = MobilePhoneFactory.getMobilePhone(MobilePhoneFactory.PhoneTypeEnum.XIAOMI);
phone.sendShortMessage();
phone.call();
// 苹果手机
phone = MobilePhoneFactory.getMobilePhone(MobilePhoneFactory.PhoneTypeEnum.IPHONE);
phone.sendShortMessage();
phone.call();
}
}
复制代码
查看测试输出:
3.4 源码中的使用
简单工厂在源码中的使用十分广泛,例如我们常用的日历类:java.util.Calendar,Calender 根据入参 Locale 时区来获取 UnicodeLocaleType 从而实例化对应的 Calendar 返回给客户端。
public static Calendar getInstance(TimeZone zone, Locale aLocale){
return createCalendar(zone, aLocale);
}
private static Calendar createCalendar(TimeZone zone,Locale aLocale) {
// prev ...
Calendar cal = null;
// 简单工厂模式使用场景
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
// post ...
return cal;
}
复制代码
3.5 优缺点总结
简单单例模式优点
代码编码简单
调用方清晰明了
在一定程度上区分了产品和生产产品工厂之间的职责
简单单例模式缺点
工厂职责不单一,能创建各种产品,理论上不符合单一职责原则(当然这个单一职责的职责区分界限视情况而定)
工厂代码耦合,新增产品会导致代码的修改,理论上不符合开闭原则。新增产品需要修改简单工厂类。这点符合前面说的,简单工厂模式在产品少或者产品个数能确定的场景使用最佳。
4、工厂方法
4.1 说明
工厂方法设计模式(Factory Method)也为虚拟构造函数(Virtual Constructor),我觉得老外这个 Virtual Constructor 称呼还挺有那个意思的;工厂通过实现一个统一个工厂接口,来约定生成何种产品。工厂方法将具体产品的生成推迟到了子类中,本身只约束不生产。这种方式解决了简单工厂不符合开闭原则的缺点。工厂方法(Factory Method)其原文定义如下:
Define an interface for creating an object, but let subclassed decide which class to instantiate. Factory Method lets a class defer instantiation to subclassed.
工厂方法 UML 图示
4.2 使用场景
工厂方法由于其符合,开闭原则,在产品(实现类)个数不确定的情况下,使用该场景代码的可用性更强,其主要使用场景如下:
产品(类)无法预测其具体实现或实现的个数(类的个数多)
具体实现需要交给子类处理,父类只提供约束规范(实现很多,后期可能会一直加,或者当前不能全部穷举)
客户端的调用对于产品的创建(对象的实例化)可以透明,无需知道具体细节
4.3 使用举例
在简单工厂中移动手机接口 IMobilePhone 及其实现类华为手机 HuaweiPhone、小米手机 XiaomiPhone、苹果手机 IPhone 沿用,下面代码将不在重复演示,其创建了如下几个接口与类的层级关系:
移动手机接口 IMobilePhone
华为手机 HuaweiPhone、小米手机 XiaomiPhone、苹果手机 IPhone 分别实现了 IMobilePhone 接口,并重写其中的方法抽象
工厂方法接口 IMakePhoneFactory,提供创建手机的抽象方法
华为手机工厂 HuaweiPhoneFactory、小米手机工厂 XiaomiPhoneFactory、苹果手机工厂 IPhonePhoneFactory 分别实现了 IMakePhoneFactory 接口,并重新了其中的抽象方法,分别创建各自的手机实例。
4.3.1 IMobilePhone、HuaweiPhone、XiaomiPhone、IPhone 代码示例
4.3.2 工厂方法接口 IMakePhoneFactory 代码示例
IMakePhoneFactory 只提供了一个 makePhone()方法,用于子工厂实现并重写,子类中决定具体实例化的对象。
package com.liziba.pattern.factory.factoryMethod;
import com.liziba.pattern.factory.IMobilePhone;
/**
* <p>
* 手机生产工厂接口
* </p>
*
* @Author: Liziba
*/
public interface IMakePhoneFactory {
/**
* 生产手机的方法定义
* @return
*/
IMobilePhone makePhone();
}
复制代码
4.3.3 工厂方法的三个实现子类代码示例
1、华为手机工厂实现类代码示例
package com.liziba.pattern.factory.factoryMethod;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.HuaweiPhone;
/**
* <p>
* 华为手机生产工厂
* </p>
*
* @Author: Liziba
*/
public class HuaweiPhoneFactory implements IMakePhoneFactory{
@Override
public IMobilePhone makePhone() {
return new HuaweiPhone();
}
}
复制代码
2、小米手机工厂实现类代码示例
package com.liziba.pattern.factory.factoryMethod;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.XiaomiPhone;
/**
* <p>
* 小米手机生产工厂
* </p>
*
* @Author: Liziba
*/
public class XiaomiPhoneFactory implements IMakePhoneFactory{
@Override
public IMobilePhone makePhone() {
return new XiaomiPhone();
}
}
复制代码
3、苹果手机工厂实现类代码示例
package com.liziba.pattern.factory.factoryMethod;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.IPhone;
/**
* <p>
* 苹果手机生产工厂
* </p>
*
* @Author: Liziba
*/
public class IPhonePhoneFactory implements IMakePhoneFactory{
@Override
public IMobilePhone makePhone() {
return new IPhone();
}
}
复制代码
4.3.4 客户端调用示例
客户端在获取指定产品时,只需要通过指定的产品的工厂获取即可,输出结果不再查看。
public class Test {
public static void main(String[] args) {
// 华为手机工厂,生产华为手机
IMobilePhone phone = new HuaweiPhoneFactory().makePhone();
phone.call();
// 小米手机工厂,生产小米手机
phone = new XiaomiPhoneFactory().makePhone();
phone.call();
}
}
复制代码
4.4 源码中的使用
我们先引入 maven 依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
复制代码
在源码 org.slf4j.ILoggerFactory 可以查看其两个实现类,类关系图和部分相关代码在下面展示:
/**
* ILoggerFactory
*/
public interface ILoggerFactory {
Logger getLogger(String var1);
}
/**
* NOPLoggerFactory
*/
public class NOPLoggerFactory implements ILoggerFactory {
public NOPLoggerFactory() {
}
public Logger getLogger(String name) {
return NOPLogger.NOP_LOGGER;
}
}
/**
* SubstituteLoggerFactory
*/
public class SubstituteLoggerFactory implements ILoggerFactory {
// ...
public synchronized Logger getLogger(String name) {
SubstituteLogger logger = (SubstituteLogger)this.loggers.get(name);
if (logger == null) {
logger = new SubstituteLogger(name, this.eventQueue, this.postInitialization);
this.loggers.put(name, logger);
}
return logger;
}
// ...
}
复制代码
4.5 优缺点总结
工厂方法单例模式优点
一个产品对应一个工厂,符合单一职责原则
易于扩展,新增产品只需新增一个工厂和相关产品即可,无需改动以前代码,符合开闭原则
屏蔽对象创建细节,客户端调用清晰
工厂方法单例模式缺点
产品数目过多时,会导致类的个数成倍增长
抽象程度高,难以理解,对开发开发要求较高
工厂方法在某些需要一个工厂生产多种产品的情况下显得乏力
5、抽象工厂
5.1 说明
前面有说道,简单工厂就像民间个体作坊,工厂方法就像小型加工厂,而抽象工厂就像大型代工厂。抽象工厂能解决工厂方法中一个工厂无法生产多个产品的问题。抽象工厂(Abstract Factory),简单来说就是一个工厂的工厂,它将单个相关/依赖的工厂组合在一起而不指定它们的具体类的工厂。抽象工厂(Abstract Factory)其原文定义如下:
Provide an interface for creating familiesof related or dependent objects without specifying their concrete classes.
抽象工厂 UML 图示
5.2 使用场景
在介绍抽象工厂的使用场景之前,我们先来介绍一个概念,产品族与产品等级结构。我们知道华为、小米、苹果公司都不仅仅只生产手机;其也生产电脑、平板等各种产品。在上述描述的各种产品中,华为手机、小米手机与苹果手机就是一个产品等级,而华为手机、华为电脑、华为平板就构成了一个产品族。我们通过华为、小米和苹果的各种产品以一张图来示例这二者的关系:
在这张图中,五边形代表手机、圆形代表平板、三角形代表电脑;横坐标手机、平板、电脑分别代表三个不同的产品等级(例如橙色部分包含的三个三角形);纵坐标华为、小米、苹果各自的三种产品组合在一起称为产品族(例如绿色部分包含的一组五边形、原型和三角形)。清晰了这个概念我们就能大致的理解抽象工厂的使用场景也为后续举例加深映像做了铺垫。其使用场景如下:
系统需要配置多个系列的产品,并且它们约定一起使用(产品族)
只提供给客户端调用接口,不暴露具体实现
产品和产品族之间具有一定的一致性约束,通过接口规范来实现
5.3 使用举例
在简单工厂中移动手机接口 IMobilePhone 及其实现类华为手机 HuaweiPhone、小米手机 XiaomiPhone、苹果手机 IPhone 沿用,下面代码将不在重复演示,手机在这里是一个产品等级。此外新增电脑产品等级,其代码实现如下。
5.3.1 手机产品等级代码示例
5.3.2 电脑产品等级代码示例
电脑接口 ILaptop 代码示例:
package com.liziba.pattern.factory;
/**
* <p>
* 笔记本电脑接口
* </p>
*
* @Author: Liziba
*/
public interface ILaptop {
/**
* 敲代码,大家都爱编程
*/
void coding();
/**
* 打游戏,大家都爱打游戏
*/
void playGame();
}
复制代码
ILaptop 的三个实现类:
package com.liziba.pattern.factory;
/**
* <p>
* 华为电脑
* </p>
*
* @Author: Liziba
*/
public class MagicBook implements ILaptop {
@Override
public void coding() {
System.out.println("coding on MacBook...");
}
@Override
public void playGame() {
System.out.println("play game on MacBook...");
}
}
复制代码
package com.liziba.pattern.factory;
/**
* <p>
* 小米电脑
* </p>
*
* @Author: Liziba
*/
public class XiaoMiPC implements ILaptop {
@Override
public void coding() {
System.out.println("coding on MacBook...");
}
@Override
public void playGame() {
System.out.println("play game on MacBook...");
}
}
复制代码
package com.liziba.pattern.factory;
/**
* <p>
* 苹果电脑
* </p>
*
* @Author: Liziba
*/
public class MacBook implements ILaptop {
@Override
public void coding() {
System.out.println("coding on MacBook...");
}
@Override
public void playGame() {
System.out.println("play game on MacBook...");
}
}
复制代码
5.3.3 抽象工厂接口 AbstractFactory 示例代码
package com.liziba.pattern.factory.abstractFactory;
import com.liziba.pattern.factory.ILaptop;
import com.liziba.pattern.factory.IMobilePhone;
/**
* <p>
* 抽象工厂
* </p>
*
* @Author: Liziba
*/
public interface AbstractFactory {
/**
* 生产笔记本电脑
* @return
*/
ILaptop makeLaptop();
/**
* 生产手机
* @return
*/
IMobilePhone makeMobilePhone();
}
复制代码
AbstractFactory 的三个实现类,分别为 HuaweiFactory、XiaomiFactory、AppleFactory 三个产品族,其实现类代码:
package com.liziba.pattern.factory.abstractFactory;
import com.liziba.pattern.factory.ILaptop;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.HuaweiPhone;
import com.liziba.pattern.factory.MagicBook;
/**
* <p>
* 华为工厂 -- 产品族
* </p>
*
* @Author: Liziba
*/
public class HuaweiFactory implements AbstractFactory{
@Override
public ILaptop makeLaptop() {
return new MagicBook();
}
@Override
public IMobilePhone makeMobilePhone() {
return new HuaweiPhone();
}
}
复制代码
package com.liziba.pattern.factory.abstractFactory;
import com.liziba.pattern.factory.ILaptop;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.XiaoMiPC;
import com.liziba.pattern.factory.XiaomiPhone;
/**
* <p>
* 小米工厂
* </p>
*
* @Author: Liziba
*/
public class XiaomiFactory implements AbstractFactory{
@Override
public ILaptop makeLaptop() {
return new XiaoMiPC();
}
@Override
public IMobilePhone makeMobilePhone() {
return new XiaomiPhone();
}
}
复制代码
package com.liziba.pattern.factory.abstractFactory;
import com.liziba.pattern.factory.ILaptop;
import com.liziba.pattern.factory.IMobilePhone;
import com.liziba.pattern.factory.IPhone;
import com.liziba.pattern.factory.MacBook;
/**
* <p>
* 苹果工厂
* </p>
*
* @Author: Liziba
*/
public class AppleFactory implements AbstractFactory{
@Override
public ILaptop makeLaptop() {
return new MacBook();
}
@Override
public IMobilePhone makeMobilePhone() {
return new IPhone();
}
}
复制代码
5.3.4 使用示例
public class Test {
public static void main(String[] args) {
// 构建华为工厂,通过HuaweiFactory生成华为的产品
AbstractFactory factory = new HuaweiFactory();
ILaptop laptop = factory.makeLaptop();
laptop.coding();
IMobilePhone phone = factory.makeMobilePhone();
phone.call();
}
}
复制代码
5.4 优缺点总结
抽象工厂优点
抽象工厂能够很好的适配产品族的使用场景,给客户端的调用带来了极大的便利,解决了工厂方法的短板
抽象工厂代码设计符合整体上开闭原则(但是其实不符合这个界限也很微妙,看各位大神么自己去抉择)
抽象工厂缺点
接口与类的设计较为复杂
产品族中新增产品需要修改上层接口(这种情况也是有的),所以他也不是很符合开闭原则。
如果您真的阅读到了这一行,我深深的对您表达我的致意。文章中出现的错误请多多指教,李子捌一定即使修正。如您不嫌弃,高抬贵手给个三连,先行谢过。
评论