工厂模式
工厂模式分为三种,都是属于 创建型设计模式
将对象的创建过程封装起来,根据所要的需求返回对应的实例;
就像工厂一样,并不知内部具体的生产细节,只需要向工厂提交请求,就能拿到想要的内容;
在应用开发和源码中都会经常看到:
ViewModelFactory 用于创建 ViewModel 实例;
ThreadPoolService 传入 threadFactory 参数用于创建线程;
简单工厂模式 Simple Factroy
也称作为 静态工厂方法模式
提供一个工厂类,根据传入的参数返回不同的实现;
将对象的创建和获取对象的代码进行分离;
使用场景
适用于:
工厂类负责创建的对象较少,方法业务逻辑不会过于复杂;
调用方只需要传入参数,而不关心对象的创建细节;
一般来说,这种模式都有以下特点:
多个类继承了共同父类或者共同接口:通过传入名称或者 Class 来获取对应的实例;
同一类有多种参数进行配置:一个类包含多种配置参数,只需要向工厂提供相关的参数,工厂对于参数进行检验,并返回对应的实例;
比如 Java 中的
DateFormat中的getXxxInstance()方法
优缺点
优点:
调用方无需知道创建对象的细节,只需要提供相关的参数即可;
让调用方免除直接创建对象的责任,而只是获取对象;
符合 单一职责原则:专门用于创建对象,并与对象的使用分离;
缺点:
工厂类集中了对象的创建逻辑(中心化),如果工厂类不能工作(单点故障),整个系统都会受到影响;
不符合 开闭原则:系统扩展困难;
若需要添加某种类,需要修改工厂类的代码,容易出现问题
若返回的对象较多,代码较难维护;
简单工厂是静态模式,会导致工厂角色无法形成基于继承的层次结构;
实现
根据实际情况决定是否需要缓存:
如果是全局使用或不可修改的,可以考虑使用缓存;
对于不同类型的对象,可以
设置名称和 Class 的映射关系,就能返回对应具体类型的对象,而不是只有其父类或者接口;
例子
存在多个系统服务,都继承了 SystemService ,通过 简单工厂模式,将这些可能多处使用的代码,抽离为一个静态方法,将创建对象代码统一维护在该方法内;
abstract class SystemService { }
class SoundSystemService extends SystemService { }
class BluetoothSystemService extends SystemService { }
class WifiSystemService extends SystemService { }
class SystemServiceFactory {
private SystemServiceFactory() { }
static SystemService getService(String serviceName) {
return switch (serviceName) {
case "SoundSystemService" -> new SoundSystemService();
case "BluetoothSystemService" -> new BluetoothSystemService();
case "WifiSystemService" -> new WifiSystemService();
default -> throw new IllegalArgumentException("not exist " + serviceName + " !");
};
}
}
public class Main {
public static void main(String[] args) {
SystemService service = SystemServiceFactory.getService("SoundSystemService");
}
}工厂方法模式 Factory Method
对于简单工厂模式,仅提供了一个工厂类,就存在问题:
若添加新的对象,就需要修改工厂类,违背了 开闭原则
所有对象的创建都是由该工厂创建,工厂类职责范围过大,具体对象的创建和工厂类类之间的耦合度高,影响系统的灵活性和扩展性;
因此提出 工厂方法模式;
工厂方法模式也成作为 虚拟构造器(Virtual Constructor)或者 多态工厂(Polymorphoic Factory)
工厂父类负责定义创建对象的公共接口,工厂子类负责创建具体的对象实例
将对象的实例化操作延迟到工厂子类完成;
换句话讲,就是由工厂子类决定需要实例化哪一个具体对象;
将工厂也进行抽象,提供统一的 工厂方法(通过接口实现)
实现
定义对象的 抽象公共接口
定义负责创建对象的 抽象工厂类
实现不同的对象;
针对不同的对象实现对应的 具体工厂类
例子
定义共同接口 SystemService ,并有不同的实现类:
interface SystemService {
void stop();
}
class SoundSystemService implements SystemService {
@Override
public void stop() {
System.out.println("SoundSystemService stopped");
}
}
class BluetoothSystemService implements SystemService {
@Override
public void stop() {
System.out.println("BluetoothSystemService stopped");
}
}
class WifiSystemService implements SystemService {
@Override
public void stop() {
System.out.println("WifiSystemService stopped");
}
}定义负责创建 SystenService 的抽象工厂类:
abstract class SystemServiceFactory {
abstract SystemService getService();
}针对不同的对象实现对应的具体工厂类:
class SoundSystemServiceFactory extends SystemServiceFactory {
@Override
SystemService getService() {
return new SoundSystemService();
}
}
class BluetoothSystemServiceFactory extends SystemServiceFactory {
@Override
SystemService getService() {
return new BluetoothSystemService();
}
}
class WifiSystemServiceFactory extends SystemServiceFactory {
@Override
SystemService getService() {
return new WifiSystemService();
}
}使用:
public class Main {
public static void main(String[] args) {
SystemServiceFactory sound = new BluetoothSystemServiceFactory();
SystemService service = sound.getService();
service.stop();
}
}使用场景
希望复用现有对象来节省资源,而不是每次都重新创建对象时,可以使用工厂方法模式(设置缓存);
一个类无需知道所需要的具体类,只需要知道所对应的工厂类即可,具体的产品对象由对应的具体工厂类创建;
工厂类通过其具体子类来指定创建哪个对象:抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时子类对象将覆盖父类对象,从而使得系统更容易扩展。
比如:
Java 中的集合类:
Collection实现Iterable接口,类似抽象工厂类,其iterator方法就是获取迭代器对象的工厂方法;其子类
ArrayList和LinkedList继承了Collection,类似具体工厂类,分别实现了对应迭代器的创建;
Collection<Integer> linkedList = new LinkedList<>();
Collection<Integer> arrayList = new ArrayList<>();
Iterator<Integer> iterator = linkedList.iterator();优缺点
优点:
工厂方法用于创建客户端所需要的对象,同时隐藏对象实例化的细节;
基于 面向对象的多态性,能使工厂能够自主确定创建对象的类型,创建对象的细节为完全封装在具体工厂内部,遵循 “单一职责原则”;
系统引进新对象时,无需修改抽象工厂和抽象对象提供的抽象方法,无需修改其他具体工厂和具体对象,更加符合 “开闭原则”;
缺点:
引进新对象时,需要创建新的具体对象类,还需要创建与之对应的具体工厂类,在一定程度上增加了系统的复杂度,给系统带来额外开销;
引入抽象层会增加系统的抽象性和理解难度。
抽象工厂模式 Abstract Factory
也称为 Kit模式
是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
与工厂方法模式最大的区别在于:
工厂方法模式针对的是一个产品等级结构;
抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建。
当一个工厂等级结构需要创建出分属于不同产品等级结构的一个产品族中的所有对象时,使用抽象工厂模式比工厂方法模式更为简单、高效。
相较于工厂方法模式仅抽离出一个接口,抽象工厂模式将所有不同等级的工厂也抽象出来,形成一个具有层次(对应产品等级结构)的结构;
例子
当创建的对象存在等级结构时,可以使用;
比如同一个控件在 Linux 和 Windows 具有不同的实现,但是在编写代码时无需知道具体的类;
因此先规定不同平台的抽象工厂类,负责创建不同的平台实现控件,提供创建控件的方法:
abstract class GUIFactory {
abstract Button createButton();
abstract TextView createTextView();
}对于不同的控件不同平台有不同的具体实现:
规定了产品的抽象类
Button和TextView不同平台有对应的实现:
WinButton、WinTextView、LinuxButton、LinuxTextView
abstract class Button {
abstract void click();
}
class WinButton extends Button {
@Override
void click() {
System.out.println("Windows Button Click");
}
}
class LinuxButton extends Button {
@Override
void click() {
System.out.println("Linux Button Click");
}
}
abstract class TextView {
abstract void showText();
}
class WinTextView extends TextView {
@Override
void showText() {
System.out.println("Windows TextView");
}
}
class LinuxTextView extends TextView {
@Override
void showText() {
System.out.println("Linux TextView");
}
}不同平台的工厂类负责创建对应平台的控件:
class WinFactory extends GUIFactory {
@Override
Button createButton() {
return new WinButton();
}
@Override
TextView createTextView() {
return new WinTextView();
}
}
class LinuxFactory extends GUIFactory {
@Override
Button createButton() {
return new LinuxButton();
}
@Override
TextView createTextView() {
return new LinuxTextView();
}
}具体使用时,无需关心创建对象的具体类:
public class Main {
public static void main(String[] args) {
// windows 上面使用
GUIFactory factory = new WinFactory();
TextView textView = factory.createTextView();
textView.showText();
}
}使用场景
在以下情况推荐使用抽象工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
系统中存在多于一个的产品族,而每次只使用其中某一产品族。
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
优缺点
优点
隔离了具体类的生成;同时所有具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变某具体工厂的实例,就可以在某种程度上改变整个软件系统的行为,实现了 “高内聚、低耦合“ 的设计目的,符合 “单一职责原则“;
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象;
增加 新的具体工厂和产品族 很方便,无须修改已有系统,符合 “开闭原则”。
缺点
在添加新的产品对象时,难以扩展抽象工厂来兼容新对象;
若要延伸产品等级结构,这需要对抽象工厂进行扩展,那所有子类都需要进行修改,不便于维护;