我们常说的 23中设计模式 ,大致可以分为3类:
- 创建者模式
- 结构型模式
- 行为型模式
而工厂设计模式
所属于创建者模式。
创建者模式
顾名思义,关注点是怎么创建对象,特点是将对象的创建与使用相分离。降低系统耦合度,使用者不关注细节。
工厂设计模式
在JAVA中,万物皆对象,这些对象都需要创建,如果在业务代码中创建的时候直接 new 对象,就会导致该对象严重耦合。举个例子:
现在有两个手机类 华为手机(PhoneHuawei)
和 苹果手机(PhoneApple)
,有一个商店类 Store
,商店中提供一个方法,根据传入的参数,售卖不同的手机。如果我们不使用 工厂设计模式,则初期的代码是这个样子。
- 定义
手机抽象类(Phone)
类
/**
* 手机抽象类,描述产品的主要功能
*/
public abstract class Phone {
// 手机名称
String name ;
// 获取手机信息
public abstract String getName() ;
// 公共方法
public void call() {
System.out.println("我是Phone,我可以打电话");
}
}
- 定义
华为手机(PhoneHuawei)
类,继承 Phone
/**
* 华为手机类
*/
public class PhoneHuawei extends Phone{
public PhoneHuawei() {
this.name = "华为手机" ;
}
@Override
public String getName() {
return this.name ;
}
}
- 定义
苹果手机(PhoneApple)
类,继承 Phone
/**
* 苹果手机
*/
public class PhoneApple extends Phone{
public PhoneApple() {
this.name = "苹果手机" ;
}
@Override
public String getName() {
return this.name ;
}
}
- 定义
商店(Store)
类
/**
* 商店
*/
public class Store {
public static Phone getPhone(String type) {
if ("apple".equals(type)) {
return new PhoneApple() ;
} else if ("huawei".equals(type)) {
return new PhoneHuawei() ;
} else {
throw new RuntimeException("本地没有改类型的手机~~~~") ;
}
}
}
- 测试类
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
Phone phone = Store.getPhone("apple");
System.out.println("买了手机:" + phone.getName());
phone.call();
}
}
输出结果:
买了手机:苹果手机
我是Phone,我可以打电话
通过以上代码可以发现,创建对象的操作,都是在 Store类
中完成的。现在业务变更,要求传入 xiaomi 的时候,再创建一个小米手机,根据上面的案例,我们只能在 Store 中再添加一个 if 判断。如果有多个Store 类,我们需要添加很多不必要的代码,而且还违反了软件设计的开闭原则
。
开闭原则: 对扩展开放,对修改关闭。意思是升级业务时,要对原有代码进行扩展,而非修改。
针对业务中大量 new 对象的耦合性,我们可以把对象的创建提取出来,创建一个专门用来生产对象的类,这种类就叫做 工厂类
。根据上方案例,我们可以创建一个 手机工厂类 PhoneFactory
来专门生产手机:
/**
* 手机工厂类
*/
public class PhoneFactory {
public static Phone createPhone(String type) {
if ("apple".equals(type)) {
return new PhoneApple() ;
} else if ("huawei".equals(type)) {
return new PhoneHuawei() ;
} else {
throw new RuntimeException("本地没有改类型的手机~~~~") ;
}
}
}
改造一下商店的代码,从工厂中获取创建的对象:
/**
* 商店
*/
public class Store {
public static Phone getPhone(String type) {
// 调用工厂方法,获取手机
return PhoneFactory.createPhone(type) ;
}
}
通过这种方式,可以把业务方法与对象创建相分离,减少代码的耦合性,即使后期增加手机类型,我们只需要更改工厂代码即可。
优点:
- 封装了创建对象的过程,C端可以通过参数直接获取对象。把对象的创建和业务逻辑相分离,降低了C端代码修改的可能性,更加容易扩展。
缺点:
- 增加新手机时,还是需要修改工厂代码,仍然违背了
开闭原则
小结一下上面的案例,每增加一种机型,就需要多加一条 if 判断,总是会修改原有代码。那有没有一种方式,可以避免修改代码,或者说,有没有一种方式,可以不通过 if 判断来生成对象。
作为C端的消费者,我们能够清晰的知道自己想要什么。所以 我们需要给 Store 传达一个信息,Store 根据这个信息,不需要做出任何判断,直接能返回我们想要的对象。所以我们传达的这个信息,不能再是字符串,而是需要和对象相关的,也就是接下来要提到的:手机生产商(手机工厂),所以就有了 工厂方法模式
。
工厂方法模式
之前我们是定义了一个 手机工厂 Phonefactory
类,然后在此类中通过if 来生产不同的对象。而 工厂方法模式
也是需要一个 PhoneFactory
工厂,但是此工厂我们定义成 抽象的,也提供生产手机的方法,但是 具体生产什么品牌的手机,需要由具体的子工厂来生产。先上代码:
- 抽象手机工厂接口:
public interface PhoneFactory {
/**
* 生产手机,但是生产什么牌子的,由子工厂写实现方法
* 有可能是华为,有可能是苹果,所以这个地方的返回值,应该是 所有手机的父类
* @return
*/
Phone createPhone() ;
}
- 具体华为手机工厂类
/**
* 华为手机工厂
*/
public class PhoneHuaweiFactory implements PhoneFactory {
// 华为工厂,生产华为的手机
@Override
public Phone createPhone() {
return new PhoneHuawei() ;
}
}
- 具体苹果手机工厂类
/**
* 苹果手机工厂
*/
public class PhoneAppleFactory implements PhoneFactory{
// 苹果工厂,生产苹果手机
@Override
public Phone createPhone() {
return new PhoneApple();
}
}
而商店Store
都会有自己的生产商(factory),生产商是啥,商店就生产啥,所以商店里,应该有一个 生产商对象,也就是所谓的 手机工厂
- Store 代码:
public class Store {
// 定义商店的生产商对象,有可能是华为,有可能是苹果,所以用他们的父类
private PhoneFactory factory ;
// 顾客是上帝,顾客扔什么进来,这个factory 就生产什么
public void setFactory(PhoneFactory factory) {
this.factory = factory ;
}
public Phone getPhone() {
return factory == null ? null : factory.createPhone() ;
}
}
接下来就很明了了,C端在明确知道自己想要什么的情况下,直接给商店里扔 具体的 生产商对象 就可以了。
- 消费者端:
public class Client {
public static void main(String[] args) {
// 1、获取商店对象
Store store = new Store() ;
// 2、给商店设置手机类型的生产商,这里是想要获取苹果手机
store.setFactory(new PhoneAppleFactory());
// 3、获取手机
Phone phone = store.getPhone();
// 4、打印手机信息
System.out.println("获取了新手机:" + phone.getName());
phone.call();
}
}
查看结果:
获取了新手机:苹果手机
我是Phone,我可以打电话
如果后期业务再加其他品牌的手机,只需要再加一个 实体类和对应的工厂类就可以了。避免了修改原有的代码,符合了 开闭原则
。
优点:
- C端只需要知道具体的工厂名称,就可以创建对应的产品,无须知道产品的具体创建过程。
- 符合了开闭原则,无须对原有工厂进行任务修改。
缺点:
- 每增加一个产品,都需要增加一个实体类 和 对应的工厂类,增加了系统的复杂度。
抽象工厂模式
随着手机店业务的升级,不止卖手机了,为了保护手机及美观,店里还要卖手机套套 TT类
。
手机套肯定也会分 华为手机套套 TTHuawei
和 苹果手机套套TTApple
,我们需要定义一个 TT 抽象类,描述TT的特征,然后创建两个实体类,来继承这个 TT 抽象类。话不多说,上代码。
- TT抽象类
/**
* 手机套套
*/
public abstract class TT {
// 获取手机套的名称
public abstract void getName() ;
// 通用的功能
public void function() {
System.out.println("保护手机~~");
}
}
- 华为TT类
public class TTHuawei extends TT{
@Override
public void getName() {
System.out.println("华为手机套套");
}
}
- 苹果TT类
public class TTApple extends TT{
@Override
public void getName() {
System.out.println("苹果手机套套");
}
}
按照 工厂方法模式,我们还需要定义一个 抽象TT生产工厂interface TTFactory
,再分别创建 具体华为TT工厂class HuaweiTTFactory implements TTFactory
和 具体苹果TT工厂class AppleTTFactory implements TTFactory
,然后再去 商店类Store
中声明一个TT工厂对象,并提供 setter 方法。 在C端调用时,我们除了给 store 对象 setPhoneFactory 之外,还需要额外setTTFactory,部分关键代码如下:
- 商店代码
public class Store {
// 定义手机的生产商对象,有可能是华为工厂,有可能是苹果工厂,所以用他们的抽象
private PhoneFactory phoneFactory ;
// 定义手机套的生产对象
private TTFactory ttFactory ;
// 顾客是上帝,顾客扔什么进来,这个factory 就生产什么
public void setPhoneFactory (PhoneFactory factory) {
this.phoneFactory = factory ;
}
public void setTTfactory (TTFactory factory) {
this.ttFactory = factory ;
}
public Phone getPhone() {
return phoneFactory == null ? null : phoneFactory.createPhone() ;
}
public TT getTT() {
return ttFactory == null ? null : ttFactory.getTT() ;
}
}
- 消费者代码:
public class Client {
public static void main(String[] args) {
// 1、获取商店对象
Store store = new Store() ;
// 2、给商店设置手机类型的生产商,这里是想要获取苹果手机
store.setPhoneFactory(new PhoneAppleFactory());
// 3、获取手机
Phone phone = store.getPhone();
// 4、给商店设置手机套套生产商,获取套套
store.setTTfactory(new HuiweTTFactory());
// 5、获取手机套
TT tt = store.getTT() ;
}
}
新加的业务,虽说以上这种方式也可以实现需求,但是有一个很明显的问题:买了苹果手机,但是手机套套却不小心买了华为的。虽然主观上是可以的,但是客观上 是不允许这种情况发生的。
所以在程序设计的时候,我们需要想办法去改变这种情况。首先需要了解两个概念: 产品等级 和 产品族。
- 产品等级:指具有相同特征的一类产品。华为手机和苹果手机,他们都是同一类产品,我们把这种具有相同特征的产品称作产品等级。
- 产品族:具有多种 产品等级、且多个产品等级之间互相有某种联系的,叫做产品族。比如 华为手机 和 华为手机套,这两个产品等级 都属于
华为
,所以华为
就可以叫做产品族。
听起来很抽象,多举几个例子:
- 商务外套、休闲外套属于一个产品等级,商务裤子、休闲裤子属于一个产品等级,商务风 就属于一个产品族,休闲风 也属于一个产品族。
- 猫和狗属于一个产品等级,鸡和鸭属于一个产品等级。所以 动物 属于一个产品族,家禽属于一个产品族。
个人的理解,产品族 是更抽象的,根据实际的业务去抽取出来的。一个产品族 可以包含很多 有某种关联的产品等级。
上方的例子中,华为手机、苹果手机属于一个产品等级,华为手机套、苹果手机套属于一个产品等级,华为 属于一个产品族,苹果同理,也属于一个产品族。
所以根据实际业务,我们去商店,只希望说出一个 产品族时,就希望等获得 当前产品族下的,所有的产品等级。比如 去 Store ,说 华为,就会得到 华为手机 和 华为手机套。
我们在设计这类业务的代码时,一般会把 产品族的描述作为最底层的抽象接口,每个具体的产品族工厂类去实现该接口。这句话的意思就是,华为 和 苹果这两个产品族,都可以 制造手机和手机套,所以我们就把 制造手机 和 制造手机套 的功能,抽取出来设计成一个抽象的工厂。然后 创建 华为工厂
和 苹果工厂
去实现这个接口。
- 抽象工厂
/**
* 生产手机和套套的抽象
*/
public interface PhoneAndTTFactory {
/**
* 生产手机
* @return
*/
Phone createPhone() ;
/**
* 生产套套
* @return
*/
TT createTT() ;
}
- 华为工厂
/**
* 华为工厂
* 生产 华为手机 和 华为手机套
*/
public class HuaweiFactory implements PhoneAndTTFactory{
@Override
public Phone createPhone() {
return new PhoneHuawei() ;
}
@Override
public TT createTT() {
return new TTHuawei() ;
}
}
- 苹果工厂
/**
* 苹果工厂
* 生产苹果手机 和 苹果套套
*/
public class AppleFactory implements PhoneAndTTFactory{
@Override
public Phone createPhone() {
return new PhoneApple() ;
}
@Override
public TT createTT() {
return new TTApple() ;
}
}
- 商店类
/**
* 商店
*/
public class Store {
private PhoneAndTTFactory factory ;
/**
* 因为 华为工厂 和 苹果工厂都实现了 PhoneAndTTFactory 接口
* 所以 这里可以接受 任何一个工厂
* 所以返回的对象,也就是各个工厂生产的对象
* @param factory
*/
public void setFactory(PhoneAndTTFactory factory) {
this.factory = factory ;
}
public Phone getPhone() {
return factory.createPhone() ;
}
public TT getTT() {
return factory.createTT() ;
}
}
- 消费者
public class Client {
public static void main(String[] args) {
Store store = new Store() ;
// 传递产品族工厂即可
store.setFactory(new AppleFactory());
Phone phone = store.getPhone();
TT tt = store.getTT();
System.out.println("手机:" + phone.getName());
tt.getName();
}
}
结果:
手机:苹果手机
苹果手机套套
优点
当一个产品族中,多个对象被设计成在一起工作时,它可以保证客户端始终只使用同一个产品族中的对象。
缺点
当产品族中需要添加一个产品的时候,所以的工厂类都需要进行修改。比如 再增加 华为电脑 和 苹果电脑,需要同时修改 华为工厂 和 苹果工厂的代码。
使用场景
当需要创建的对象是一系列相关产品或相互依赖的产品族时,比如 输入法(更换输入法,背景图、颜色、字体等全部发生变化),游戏皮肤(更改皮肤后,台词、特效等都会发生变化)。
工厂模式扩展(重要)
简单工厂 + 配置文件接触耦合
可以通过工厂模式+配置文件的方式接触工厂对象和产品的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果使用对象,直接进行获取即可。
第一步:定义配置文件
在maven项目的 resources下,创建bean.properties
文件
huawei: cn.lzc.demo5.entity.PhoneHuawei
apple: cn.lzc.demo5.entity.PhoneApple
第二步:改进工厂类
public class CustomFactory {
// 存放对象
private static Map<String, Phone> phoneMap = new HashMap<String, Phone>(0) ;
static {
// 获取配置文件输入流
Properties properties = new Properties() ;
InputStream inputStream = CustomFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
properties.load(inputStream);
// 获取定义的key 的集合
Set<Object> keys = properties.keySet();
// 遍历keys,获取对象
for (Object key : keys) {
// 获取全类名
String className = properties.getProperty((String) key) ;
Class clazz = Class.forName(className) ;
Phone phone = (Phone) clazz.newInstance() ;
phoneMap.put((String) key, phone) ;
}
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取对象
* @param name
* @return
*/
public static Phone createPhone (String name) {
return phoneMap.get(name) ;
}
}
第三步:消费者代码
public class Client {
public static void main(String[] args) {
// 根据配置文件中的key,来获取对象
Phone phone = CustomFactory.createPhone("apple") ;
System.out.println(phone.getName());
phone.call();
}
}
打印
苹果手机
我是Phone,我可以打电话
此方法也符合开闭原则,如果需要添加一种咖啡,则只需要 在配置文件中 添加一个操作即可。