抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
譬如 你正在开发一款家具组装系统。代码中包括一些产品:椅子(chair),沙发(sofa)和 咖啡桌(CoffeeTable) 然后基于这些产品形成一些列组合套餐 装饰风艺术(Art Deco)、维多利亚(Victorian)和现代(Mdern)。你需要设法单独生成每件家具对象, 这样才能确保其风格一致。 如果顾客收到的家具风格不一样, 他们可不会开心。此外, 你也不希望在添加新产品或新风格时修改已有代码。 家具供应商对于产品目录的更新非常频繁, 你不会想在每次更新时都去修改核心代码的。
如何解决?
首先, 抽象工厂模式建议为系列中的每件产品明确声明接口 (例如椅子、 沙发或咖啡桌)。 然后, 确保所有产品变体都继承这些接口。 例如, 所有风格的椅子都实现 椅子
接口; 所有风格的咖啡桌都实现 咖啡桌
接口, 以此类推。
接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。 例如 createChair
创建椅子 、 createSofa
创建沙发和 createCoffeeTable
创建咖啡桌 。 这些方法必须返回抽象产品类型, 即我们之前抽取的那些接口: 椅子
, 沙发
和 咖啡桌
等等。
那么该如何处理产品变体呢? 对于系列产品的每个变体, 我们都将基于 抽象工厂
接口创建不同的工厂类。 每个工厂类都只能返回特定类别的产品, 例如, 现代家具工厂
ModernFurnitureFactory只能创建 现代椅子
ModernChair 、 现代沙发
ModernSofa和 现代咖啡桌
ModernCoffeeTable对象。
客户端代码可以通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。
假设客户端想要工厂创建一把椅子。 客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。 无论是现代风格, 还是维多利亚风格的椅子, 对于客户端来说没有分别, 它只需调用抽象 椅子
接口就可以了。 这样一来, 客户端只需知道椅子以某种方式实现了 sitOn
坐下方法就足够了。 此外, 无论工厂返回的是何种椅子变体, 它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。
最后一点说明: 如果客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。 而在此之前, 应用程序必须根据配置文件或环境设定选择工厂类别。
使用示例: 抽象工厂模式在 Java 代码中很常见。 许多框架和程序库会将它作为扩展和自定义其标准组件的一种方式。
以下是来自核心 Java 程序库的一些示例:
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()
识别方法: 我们可以通过方法来识别该模式——其会返回一个工厂对象。 接下来, 工厂将被用于创建特定的子组件。
上代码:
public interface FurnitureFactory {
Chair createChair();
CoffeeTable createCoffeeTable();
Sofa createSofa();
}
public class ArtDecoFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new ArtDecoChar();
}
@Override
public CoffeeTable createCoffeeTable() {
return new ArtDecoCoffeeTable();
}
@Override
public Sofa createSofa() {
return new ArtDecoSofa();
}
}
产品:
public abstract class Chair {
public abstract String hasLegs();
public abstract String sitOn();
}
public abstract class CoffeeTable {
public abstract int legsLength();
public abstract String tableShape();
}
public abstract class Sofa {
public abstract String hasLegs();
public abstract String sitOn();
}
实现产品:
public class ArtDecoChar extends Chair {
@Override
public String hasLegs() {
// TODO Auto-generated method stub
return "两条腿";
}
@Override
public String sitOn() {
// TODO Auto-generated method stub
return "能坐沙发";
}
}
public class ArtDecoSofa extends Sofa {
@Override
public String hasLegs() {
// TODO Auto-generated method stub
return "2条腿";
}
@Override
public String sitOn() {
// TODO Auto-generated method stub
return "长沙发,能座";
}
}
public class ArtDecoCoffeeTable extends CoffeeTable {
@Override
public int legsLength() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String tableShape() {
// TODO Auto-generated method stub
return "立体咖啡桌";
}
}
客户端:
public class Client {
private FurnitureFactory factory;
public Client(FurnitureFactory factory) {
this.factory = factory;
}
public void someOperation() {
Chair chair = factory.createChair();
CoffeeTable coffeeTable =factory.createCoffeeTable();
Sofa sofa = factory.createSofa();
String result = String.format("套餐类型 %s , 椅子:%s - %s。沙发:%s-%s.咖啡桌: %d条腿- 桌面形状: %s ",factory.getClass().getName(),chair.hasLegs(),chair.sitOn(),
sofa.hasLegs(),sofa.sitOn(),coffeeTable.legsLength(),coffeeTable.tableShape());
System.out.println(result);
}
}
测试:
Client client = new Client(new ArtDecoFurnitureFactory());
client.someOperation();
client = new Client(new ModernFurnitureFactory());
client.someOperation();
client = new Client(new VictorianFurnitureFactory());
client.someOperation();
输出结果:
套餐类型 com.sloth.designpatter.abstractfactory.impl.ArtDecoFurnitureFactory , 椅子:两条腿 - 能坐沙发。沙发:2条腿-长沙发,能座.咖啡桌: 0条腿- 桌面形状: 立体咖啡桌
套餐类型 com.sloth.designpatter.abstractfactory.impl.ModernFurnitureFactory , 椅子:没有腿 - 能座。沙发:无腿-可以座.咖啡桌: 4条腿- 桌面形状: 椭圆咖啡桌
套餐类型 com.sloth.designpatter.abstractfactory.impl.VictorianFurnitureFactory , 椅子:4条腿 - 座上去很舒服。沙发:4条腿-维多利亚 能座.咖啡桌: 4条腿- 桌面形状: 长方体+圆边
抽象工厂模式适合应用场景
如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。
如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。
在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。
在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。
抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。
你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
抽象工厂、 生成器和原型都可以用单例模式来实现。