抽象工厂模式(Abstract Factory Pattern)提供了一种创建一组相关或依赖对象的接口,而无需指定它们的具体类。抽象工厂模式允许创建对象家族,而不必指定每个对象的具体类,从而实现了对象的抽象和解耦。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产同一等级的产品,而抽象工厂模式可以生产多个等级的产品。
以下是抽象工厂模式的主要组成部分:
抽象工厂(Abstract Factory):定义了一组创建相关对象的抽象接口,通常包括多个工厂方法,每个工厂方法负责创建一个具体对象。抽象工厂通常是一个接口或抽象类。
具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建一组相关的具体对象。每个具体工厂都可以创建一整套产品对象,这些产品对象通常有一定的关联性。
抽象产品(Abstract Product):定义了一组相关的产品对象的抽象接口。每个抽象产品对应于工厂方法的一种类型。
具体产品(Concrete Product):实现了抽象产品接口,是由具体工厂创建的实际对象。每个具体产品都属于某个产品家族。
抽象工厂模式的关键点在于,客户端不直接实例化具体产品,而是通过抽象工厂来创建产品。这使得客户端代码更加灵活,因为它不依赖于具体产品的类名,而只依赖于抽象工厂和抽象产品的接口。
以下是抽象工厂模式的一些示例应用场景:
多主题界面:假设此时正在开发一个应用程序,需要支持多个不同的用户界面主题(例如,浅色和深色主题)。可以使用抽象工厂来创建每个主题下的按钮、文本框和其他界面元素,而不必在代码中硬编码特定主题的元素。
跨平台应用程序:如果正在构建一个需要同时支持多个操作系统或平台的应用程序,可以使用抽象工厂来创建适用于每个平台的界面元素和操作系统特定的对象。
数据库访问层:在数据库访问层中,抽象工厂可以用来创建数据库连接、命令、事务等对象,以便在不同的数据库系统中切换时能够轻松适应。
仍然以上述手机为例,只不过此时因顾客需求,该工厂不仅要生产手机,还需要生产电脑(包括Windows、Mac两类电脑)。
所以在上面代码的基础上,根据抽象工厂模式的结构,需要定义一个数码抽象工厂
interface DigitalFactory {
/**
* 生产手机的功能
*/
Phone createPhone();
/**
* 生产电脑的功能
*/
Computer createComputer();
}
然后再定义手机制造工厂和电脑制造工厂(因为这里的手机和电脑不为同一个品牌,所以只能分开定义,实际开发中一般都会为同一个族定义一个工厂)
class AppleFactory implements DigitalFactory{
@Override
public Phone createPhone() {
return new Apple();
}
@Override
public Computer createComputer() {
return new Mac();
}
}
class HarmonyFactory implements DigitalFactory{
@Override
public Phone createPhone() {
return new HUAWEI();
}
@Override
public Computer createComputer() { // 暂时没有,则返回null
return null;
}
}
class WindowsFactory implements DigitalFactory{
@Override
public Phone createPhone() { // 暂时没有,则返回null
return null;
}
@Override
public Computer createComputer() {
return new Windows();
}
}
最后,定义一个顾客测试一下工厂
class Customer {
public static void main(String[] args) {
// 创建苹果工厂
AppleFactory factory = new AppleFactory();
// 获取苹果手机
Phone apple = factory.createPhone();
// 获取苹果电脑
Computer mac = factory.createComputer();
// 分别展示其信息
apple.show();
mac.show();
}
}
运行结果为:
总之,抽象工厂模式有助于将对象的创建与客户端解耦,提供了一种灵活的方式来创建一组相关对象,同时保持了高层代码对底层实现的独立性。
抽象工厂方法有以下几个优点:
除了上述三种工厂模式的单独使用能降低代码耦合以外,还有一种简单工厂+配置文件
的方式也可以降低代码耦合,并且在开发使用场景也很常见,例如熟悉的Spring框架就是基于这种方式实现的。
使用方法是,在工厂类中加载配置文件中的全类名,并通过反射的方式创建对象进行存储,客户端如果需要对象,则直接进行获取即可。
下面,还是使用是上面的手机工厂为例进行演示(这里采用的maven模版创建的工程模块,即包含java和resource包的实际开发所使用的模版)
第一步:定义配置文件
在resource包下创建一个bean.properties的配置文件
apple=com.onism.pattern.factory.Apple
huawei=com.onism.pattern.factory.HUAWEI
第二步:改进工厂类
class PhoneFactory {
/**
* 定义容器存储手机对象
*/
private static HashMap<String,Phone> map = new HashMap<>();
// 加载配置文件,只需要加载一次
static {
// 创建properties对象
Properties properties = new Properties();
// 调用该对象中的load方法进行配置文件的配置。
InputStream inputStream = PhoneFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
properties.load(inputStream);
// 从properties集合中获取全类名并创建对象
for (Object key : properties.keySet()) {
String className = properties.getProperty((String) key);
// 通过反射技术创建对象
Class<?> clazz = Class.forName(className);
Phone phone = (Phone) clazz.newInstance();
// 将名称和对象存储到容器中
map.put((String) key, phone);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Phone createPhone(String name){
return map.get(name);
}
}
下面创建一个顾客类测试一下
class Customer {
public static void main(String[] args) {
Phone phone = PhoneFactory.createPhone("apple");
phone.show();
}
静态成员变量用来存储创建对象(键存储名称,值存储对应对象),而读取配置文件以及创建对象与静态代码块中,目的就是只需要执行一次。
在JDK中,有一些类和接口使用了工厂模式或类似的创建对象的设计模式。例如,最熟悉的Collection。
public class Demo{
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("123");
list.add("456");
list.add("789");
// 获取迭代器对象
Iterator<String> it = list.iterator();
//使用迭代器遍历
while(it.hasNext()){
System.out.println(it.next());
}
}
}
上面的代码是很经典的List集合使用方法,其中的获取iterator迭代器方法就使用到了工厂方法模式,通过上面代码结构分析其类图结构
其中的Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中的iterator()方法创建具体的商品类的对象。
此外,除了Collection,还有Calendar类等
java.util.Calendar:Calendar
类是一个抽象类,用于处理日期和时间。它提供了一种用于创建Calendar
实例的工厂方法(工厂方法模式),通常使用getInstance()
方法获取一个Calendar
对象。具体的Calendar
实现类由JVM的Locale
和时区信息决定。客户端代码无需关心具体的Calendar
实现,只需通过工厂方法获取实例即可。