本篇Blog继续学习创建型模式,创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离,这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。本篇学习的是抽象工厂模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:
- 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景等
- 模式结构:包含模式的结构,包含的角色定义及调用关系
- 模式实现:包含模式的实现方式代码举例或者生活中简单问题映射代码举例
- 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
- 模式对比:如果模式相似或模式有额外的替换方法,有必要体现其相似点及不同点,区分使用,说明哪些场景下使用哪种模式比较好
- 模式扩展:如果模式有与标准结构定义不同的变体形式,一并体现出其变体结构;对模式的思考需要进行发散等。
接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,如果某一条目没有则无需体现,但条目顺序遵循此结构,本文的模式实践案例大多来自极客时间。
模式档案
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族
模式定义:抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象
模式特点:抽象工厂模式的目的是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式,可以把抽象工厂比作一个工厂,而工厂方法就是这个抽象工厂中的一条条产品线,负责生产不同维度的产品。
解决什么问题:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。例如对于宝马车来说,宝马5系和宝马3系都需要:座椅、轮胎。但是宝马5系的配置是:真皮座椅、米其林轮胎;而宝马3系的配置是:革皮座椅、普通轮胎。那么创建5系的工厂和创建3系的工厂就要注意了,它们需要生产成套且有约束的产品族,不能把真皮座椅和普通轮胎组合起来,5系就要5系的产品族配置,3系就要3系的产品族配置。
优点: 除了工厂方法具备的解耦性优点外,它还额外具备产品族的约束作用:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:产品族难扩展,如果产品族中增加了一个创建产品的工厂方法(产品族扩展一个新产品),那么所有具体的工厂类都需要实现这个方法
使用场景: 当要创建的内容为一组相互依赖相互约束的产品族的产品时,使用抽象工厂模式可以方便的实现这个需求,拿上一篇工厂方法模式的形状例子扩展,如果我们想要在抽象工厂中制造红方旗【方形布料+红色染料】就需要布料产品线产出方形布料+染料产品线产出红色染料。
模式结构
我们接着按照工厂方法模式中的角色来聊聊抽象工厂中该有哪些工厂以及创建过程,相对于工厂方法模式,它的特点就是:有多个抽象产品类并且每个产品抽象类有多个具体的产品类,有一个抽象工厂类和多个具体的工厂类,每个具体工厂类能创建多个具体的产品类实例。
- 抽象工厂角色: 它是具体工厂角色必须实现的接口或者必须继承的父类,在Java中它由抽象类或接口来实现。它包含创建多个抽象产品的抽象方法。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的一组具体产品对象。
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在Java中一般由抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在Java中由具体的类来实现。
在以下的例子中我们的抽象工厂是用来生产旗帜的,拥有多条产品线,包括生产布料产品线和生产染料产品线。
模式实现
以下是各个参与其中的角色
抽象产品角色
布料形状抽象产品角色
package com.example.designpattern.factory; /** * The interface Shape. */ public interface Shape { /** * Draw. */ void draw(); }
染料颜色抽象产品角色
public interface Color { void fill(); }
具体产品角色
布料形状具体产品角色
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } } public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } }
布料形状具体产品角色
public class Red implements Color { @Override public void fill() { System.out.println("Inside Red::fill() method."); } } public class Yellow implements Color { @Override public void fill() { System.out.println("Inside Yellow ::fill() method."); } } public class Blue implements Color { @Override public void fill() { System.out.println("Inside Blue::fill() method."); } }
抽象工厂角色
抽象工厂角色
public interface AbstractFactory { //制造布料 public Shape getShape(); //制造染料 public Color getColor(); }
具体工厂角色
具体工厂角色
public class RedSquareFactory implements AbstractFactory { @Override public Shape getShape() { return new Square (); } @Override public Color getColor() { return new Red(); } } public class CircleBlueFactory implements AbstractFactory { @Override public Shape getShape() { return new Circle(); } @Override public Color getColor() { return new Blue(); } } public class YellowRectangleFactory implements AbstractFactory { @Override public Shape getShape() { return new Rectangle(); } @Override public Color getColor() { return new Yellow(); } }
客户端调用代码知道它所需要的是哪种旗子,通过具体工厂获取产品并打印结果如下:
public class FactoryPatternDemo { public static void main(String[] args) { //获取 RedSquareFactory工厂并创造产品族实例 AbstractFactory redSquareFactory = new RedSquareFactory(); redSquareFactory .getShape(); redSquareFactory .getColor(); //获取 CircleBlueFactory工厂并创造产品族实例 AbstractFactory circleBlueFactory = new CircleBlueFactory(); circleBlueFactory .getShape(); circleBlueFactory .getColor(); //获取 YellowRectangleFactory工厂并创造产品族实例 AbstractFactory yellowRectangleFactory = new YellowRectangleFactory(); yellowRectangleFactory .getShape(); yellowRectangleFactory .getColor(); } }
模式实践
接着上篇Blog的文本文件解析,我们再给例子增加一个维度。在简单工厂和工厂方法中,类只有一种分类方式。比如,在规则配置解析那个例子中,解析器类只会根据配置文件格式(Json、Xml、Yaml……)
来分类。
设计一个多维可扩展文件解析工厂
如果解析维度更多,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象(Rule 规则配置还是 System 系统配置)来分类,那就会对应下面这 8 个 parser 类
针对规则配置的解析器:基于接口IRuleConfigParser JsonRuleConfigParser XmlRuleConfigParser YamlRuleConfigParser PropertiesRuleConfigParser 针对系统配置的解析器:基于接口ISystemConfigParser JsonSystemConfigParser XmlSystemConfigParser YamlSystemConfigParser PropertiesSystemConfigParser
针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们要针对每个 parser 都编写一个工厂类,也就是要编写 8 个工厂类。如果我们未来还需要增加针对业务配置的解析器(比如 IBizConfigParser),那就要再对应地增加 4 个工厂类。而我们知道,过多的类也会让系统难维护。
抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示
抽象产品角色
package com.example.designpattern.factory; public interface IRuleConfigParser{ void parse(); } public interface ISystemConfigParser{ void parse(); }
具体产品角色
IRuleConfigParser
的具体产品
public class JsonRuleConfigParser implements IRuleConfigParser { @Override public void parse() { System.out.println("IRuleConfigParser JsonRuleConfigParser parse "); } } public class XmlRuleConfigParser implements IRuleConfigParser { @Override public void parse() { System.out.println("IRuleConfigParser XmlRuleConfigParser parse"); } } public class YamlRuleConfigParser implements IRuleConfigParser { @Override public void parse() { System.out.println("IRuleConfigParser YamlRuleConfigParser parse"); } } public class PropertiesRuleConfigParser implements IRuleConfigParser { @Override public void parse() { System.out.println("IRuleConfigParser PropertiesRuleConfigParser parse"); } }
ISystemConfigParser
的具体产品
public class JsonSystemConfigParser implements ISystemConfigParser { @Override public void parse() { System.out.println("ISystemConfigParser JsonRuleConfigParser parse "); } } public class XmlSystemConfigParser implements ISystemConfigParser { @Override public void parse() { System.out.println("ISystemConfigParser XmlRuleConfigParser parse"); } } public class YamlSystemConfigParser implements ISystemConfigParser { @Override public void parse() { System.out.println("ISystemConfigParser YamlRuleConfigParser parse"); } } public class PropertiesSystemConfigParser implements ISystemConfigParser { @Override public void parse() { System.out.println("ISystemConfigParser PropertiesRuleConfigParser parse"); } }
抽象工厂角色
public interface IConfigParserFactory { IRuleConfigParser createRuleParser(); ISystemConfigParser createSystemParser(); //此处可以扩展新的parser类型,比如IBizConfigParser }
具体工厂角色
public class JsonConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new JsonRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new JsonSystemConfigParser(); } } public class XmlConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new XmlRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser() { return new XmlSystemConfigParser(); } } public class YamlConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new YamlRuleConfigParser (); } @Override public ISystemConfigParser createSystemParser() { return new YamlSystemConfigParser (); } } public class PropertiesConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser() { return new PropertiesRuleConfigParser (); } @Override public ISystemConfigParser createSystemParser() { return new PropertiesSystemConfigParser (); } }
客户端调用同工厂方法
模式对比
这里简单对比下工厂方法模式和抽象工厂模式,并整体回顾一下工厂模式的演变。通俗的说这个区别在于产品,如果产品单一,最合适用工厂模式,但是如果有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
再通俗深化理解下:工厂模式针对的是一个产品等级结构 ,抽象工厂模式针对的是面向多个产品等级结构。抽象工厂在只有一条产品线的时候退化为工厂方法模式。再看一个工厂模式的演进路线:
1 简单工厂模式
简单工厂模式不是 23 种里的一种,简而言之,就是有一个专门生产某个产品的类。比如下图中的鼠标工厂,专业生产鼠标,给参数 0,生产戴尔鼠标,给参数 1,生产惠普鼠标
2 工厂方法模式
工厂方法也就是鼠标工厂是个父类,有生产鼠标这个接口。戴尔鼠标工厂,惠普鼠标工厂继承它,可以分别生产戴尔鼠标,惠普鼠标。生产哪种鼠标不再由参数决定,而是创建鼠标工厂时,由戴尔鼠标工厂创建。后续直接调用鼠标工厂.生产鼠标()
即可
3 抽象工厂模式
抽象工厂模式也就是不仅生产鼠标,同时生产键盘。也就是 PC 厂商是个父类,有生产鼠标,生产键盘两个接口。戴尔工厂,惠普工厂继承它,可以分别生产戴尔鼠标+戴尔键盘,和惠普鼠标+惠普键盘。创建工厂时,由戴尔工厂创建。后续工厂.生产鼠标()则生产戴尔鼠标,工厂.生产键盘()则生产戴尔键盘
。
在抽象工厂模式中,假设我们需要增加一个工厂,假设我们增加华硕工厂并且华硕也有自己的鼠标和键盘,则我们需要增加华硕工厂,和戴尔工厂一样,继承 PC 厂商,之后创建华硕鼠标,继承鼠标类。创建华硕键盘,继承键盘类即可。
在抽象工厂模式中,假设我们需要增加一个产品,假设我们增加耳麦这个产品,则首先我们需要增加耳麦这个父类,再加上戴尔耳麦,惠普耳麦这两个子类。之后在PC厂商这个父类中,增加生产耳麦的接口。最后在戴尔工厂,惠普工厂这两个类中,分别实现生产戴尔耳麦,惠普耳麦的功能。
总结一下
抽象工厂模式其实是工厂方法模式的升级版,其优点其实和工厂方法类似,都是将对象的创建解耦出来,不过抽象工厂模式的工厂能创建的不只是一个产品而是一组产品,这一组产品都有相同的产品类别,同组不同类的产品相互依赖和约束,例如一个工厂要生产红方旗,那么布料类产品必须是方形布料、染料类产品必须为红色染料,方形布料和红色染料在该工厂中就是绑定关系,该工厂也就约束了其生产的具体产品品类。