抽象工厂模式又称工具箱模式。其实抽象工厂模式可以简单的理解为一个工厂生成一个产品族的产品。
抽象工厂模式可以向客户端提供一个接口,使得客户端在不指定产品的具体类型的情况下,创建多个产品族中的产品对象。这也是抽象工厂模式的用意。
抽象工厂模式面对的是一个产品等级结构的系统设计。
抽象工厂模式和工厂模式最大的区别就是:工厂模式针对的是一个产品等级结构,而抽象工厂针对的是多个产品等级结构。
产品族:
产品族是指位于不同产品等级结构中,功能相关联的产品组成的家族。
抽象工厂模式:
抽象工厂模式最早的应用是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构件,在Unix和Windows操作系统的视窗环境中,这两个构件有不同的本地实现,它们的细节也有所不同。
其产品如下:
可以发现在上面图中有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是Unix产品族和Windows产品族。Unix产品族由UnixButton和UnixText构成,Windows产品族由WinButton和WinText构成,相图描述如下:
系统对产品对象的创建需求由一个工厂的等级满足,其中有两个具体工厂角色,UnixFactory和WinFactory。UnixFactory对象创建Unix产品族的产品,WinFactory负责Windows产品族的产品。这就是抽象工厂模式的应用。对应图如下:
用工厂模式解决上面问题:
试想如果我们用工厂模式,就需要写多个具体的工厂(UnixButtonFactory、UnixTextFactory、WinButtonFactory、WinTextFactory,如果将来产品等级增多就需要更多的具体工厂),每个工厂生产对应的产品,如果需要切换产品族需要切换整个产品族对应的工厂。比如原来生产的是Unix产品,使用的是UnixButtonFactory、UnixTextFactory,如果现在想切换Windows系列的产品就需要改成WinButtonFactory、WinTextFactory(如果产品等级增加需要切换更多的工厂)。而且增加新的产品族需要编写更多的工厂。
抽象工厂模式涉及到以下角色:
抽象工厂(Abstract Factory)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统的商业逻辑无关的。通常使用Java接口或者类来实现,所有的具体工厂必须实现这个接口或继承这个抽象类。
具体工厂(Concrete Factory)类:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适产品的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。
抽象产品(Abstract Product):担任这个角色的类是工厂方法模式所创建的对象的父类或它们共同拥有的接口。
具体产品(Concrete Product):由具体工厂创建的具体产品。这是客户端需要的东西,与与应用系统的商业逻辑紧密相关的。
UML类图如下:
源码如下:
package cn.qlq.absfactory; public interface Button { void clickedCutton(); }
package cn.qlq.absfactory; public class UnixButton implements Button { @Override public void clickedCutton() { System.out.println("点击 UnixButton "); } }
package cn.qlq.absfactory; public class WinButton implements Button { @Override public void clickedCutton() { System.out.println("点击 WinButton "); } }
package cn.qlq.absfactory; public interface Text { void clickedText(); }
package cn.qlq.absfactory; public class UnixText implements Text { @Override public void clickedText() { System.out.println("点击 UnixText "); } }
package cn.qlq.absfactory; public class WinText implements Text { @Override public void clickedText() { System.out.println("点击 WinText "); } }
package cn.qlq.absfactory; public interface AbstractFactory { Button createButton(); Text createText(); }
package cn.qlq.absfactory; public class UnixFactory implements AbstractFactory { @Override public Button createButton() { return new UnixButton(); } @Override public Text createText() { return new UnixText(); } }
package cn.qlq.absfactory; public class WinFactory implements AbstractFactory { @Override public Button createButton() { return new WinButton(); } @Override public Text createText() { return new WinText(); } }
测试代码:
package cn.qlq.absfactory; public class MainClass { public static void main(String[] args) { AbstractFactory unixFactory = new WinFactory(); Button button = unixFactory.createButton(); Text text = unixFactory.createText(); button.clickedCutton(); text.clickedText(); } }
补充:如果有必要可以增加一个组合器进行组装生产工厂生产的产品,如下:
package cn.qlq.absfactory; public class Combiner { public Combiner(AbstractFactory factory) { // 生产产品 factory.createButton(); factory.createText(); // 用产品做后续处理 } }
适用场景:
1.一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
2.这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
3.同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
4.系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。
优缺点:
优点:
分离接口和实现:
客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。
使切换产品族和增加产品组变得容易:
因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从windows系列产品到unix系列产品只需要切换一下具体工厂。增加一系列产品也容易,比如增加一个mac系列产品则需要增加对应的mac产品与对应的macfactory即可。
缺点:
增加和减少产品等级结构不容易:
如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。比如所有的产品都需要增加一个label,则需要增加一个label产品等级并修改工厂接口与修改所有的具体工厂。这也违反了开闭原则。
综合起来,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供便利,而不能为产品等级结构的增加提供便利。
利用简单工厂改造上面的抽象工厂:
我们尝试利用简单工厂改造上面的抽象工厂。UML类图如下:
产品类代码同上,工厂类代码如下:
package cn.qlq.simplefactory; public class ProductFactory { private String productName; public ProductFactory(String productName) { this.productName = productName; } public Button createButton() { Button button = null; switch (productName) { case "Win": button = new WinButton(); break; case "Unix": button = new UnixButton(); break; default: break; } return button; } public Text createText() { Text text = null; switch (productName) { case "Win": text = new WinText(); break; case "Unix": text = new UnixText(); break; default: break; } return text; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } }
测试代码:
package cn.qlq.simplefactory; public class MainClass { public static void main(String[] args) { String productName = "Win"; ProductFactory productFactory = new ProductFactory(productName); Button button = productFactory.createButton(); Text text = productFactory.createText(); button.clickedCutton(); text.clickedText(); } }
上面有个缺点就是增加产品族的时候需要修改工厂
利用反射+简单工厂改造:
修改工厂类采用反射创建对象:
这里需要遵循一个约定:产品族的名称 + 抽象产品类名称构成具体类名称,比如产品族为Win,抽象产品为Button,则具体类为WinButton。
package cn.qlq.simplefactory; public class ProductFactory { private String productName; public ProductFactory(String productName) { this.productName = productName; } public Button createButton() { String packageName = "cn.qlq.simplefactory"; Button button = null; try { Class clazz = Class.forName(packageName + "." + productName + "Button"); button = (Button) clazz.newInstance(); } catch (Exception e) { // 记录日志 throw new RuntimeException("非法参数异常"); } return button; } public Text createText() { String packageName = "cn.qlq.simplefactory"; Text text = null; try { Class clazz = Class.forName(packageName + "." + productName + "Text"); text = (Text) clazz.newInstance(); } catch (Exception e) { // 记录日志 throw new RuntimeException("非法参数异常"); } return text; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } }
这种方式就比较灵活了,将来扩展新的产品族的时候也比较方便,无需工厂类。
一般最常用的就是反射 + 简单工厂。