设计模式-创建型模式
Posted ML李嘉图
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式-创建型模式相关的知识,希望对你有一定的参考价值。
阅读推荐:
项目地址:https://gitee.com/zwtgit/gof23
学习网站推荐:
设计模式是针对软件设计中常见问题的工具箱, 其中的工具就是各种经过实践验证的解决方案。 即使你从未遇到过这些问题, 了解模式仍然非常有用, 因为它能指导你如何使用面向对象的设计原则来解决各种问题。
算法更像是菜谱: 提供达成目标的明确步骤。 而模式更像是蓝图: 你可以看到最终的结果和模式的功能, 但需要自己确定实现步骤。
GoF 23(分类)
- 创建型模式:提供创建对象的机制, 增加已有代码的灵活性和可复用性。
- 结构型模式:介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
- 行为模式:负责对象间的高效沟通和职责委派。
不同设计模式的复杂程度、 细节层次以及在整个系统中的应用范围等方面各不相同。
我喜欢将其类比于道路的建造:
如果你希望让十字路口更加安全, 那么可以安装一些交通信号灯, 或者修建包含行人地下通道在内的多层互通式立交桥。
最基础的、 底层的模式通常被称为惯用技巧。 这类模式一般只能在一种编程语言中使用。
最通用的、 高层的模式是构架模式。 开发者可以在任何编程语言中使用这类模式。
与其他模式不同, 它们可用于整个应用程序的架构设计。
- 创建型模式:
- 单例模式,工厂模式, 抽象工厂模式, 建造者模式, 原型模式
- 结构型模式:
- 适配器模式, 桥接模式, 装饰模式, 组合模式, 外观模式, 享元模式, 代理模式
- 行为型模式:
- 模板方法模式, 命令模式, 迭代器模式, 观察者模式, 中介者模式, 备忘录模式, 解释器模式, 状态模式, 策略模式, 职责链模式, 访问者模式
OOP 七大原则
面向对象程序设计(Object Oriented Programming,OOP)。
1、开闭原则
对扩展开放, 对修改关闭。
2、单一职责原则
每个类应该实现单一的职责,不要存在多于一个导致类变更的原因,否则就应该把类拆分。该原则是实现高内聚、低耦合的指导方针。
3、里氏替换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏替换原则是对开闭原则的补充。实现开闭原则的关键就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
4、依赖倒转原则(Dependence Inversion Principle)
面向接口编程,依赖于抽象而不依赖于具体。用到具体类时,不与具体类交互,而与具体类的上层接口交互。
5、接口隔离原则(Interface Segregation Principle)
每个接口中不存在子类用不到却必须实现的方法,否则就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口中的方法聚合到一个的接口)要好。
6、迪米特法则(最少知道原则)(Demeter Principle)
一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过 public 方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
7、合成复用原则(Composite Reuse Principle)
软件复用时,要先尽量使用组合或者聚合等关联关系实现,其次才考虑使用继承。即在一个新对象里通过关联的方式使用已有对象的一些方法和功能。
Creational Pattems
Singleton
背景
保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
适用场景
- 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式。
- 如果你需要更加严格地控制全局变量,可以使用单例模式 。
实例-实现
Java 核心程序库中仍有相当多的单例示例:
基础单例(单线程)
package com.zwt.BasicSingleton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public final class Singleton {
private static Singleton instance;
public String value;
private Singleton(String value) {
// The following code emulates slow initialization.
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
package com.zwt.BasicSingleton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class DemoSingleThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\\n\\n" +
"RESULT:" + "\\n");
Singleton anotherSingleton = Singleton.getInstance("wb");
Singleton singleton = Singleton.getInstance("zwt");
System.out.println(singleton.value);
System.out.println(anotherSingleton.value);
}
}
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)
RESULT:
wb
wb
基础单例(多线程)
相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。
package com.zwt.BasicSingleton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class DemoMultiThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\\n\\n" +
"RESULT:" + "\\n");
Thread threadFoo = new Thread(new Threadwb());
Thread threadBar = new Thread(new Threadzwt());
threadFoo.start();
threadBar.start();
}
static class Threadwb implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("wb");
System.out.println(singleton.value);
}
}
static class Threadzwt implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("zwt");
System.out.println(singleton.value);
}
}
}
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)
RESULT:
wb
zwt
采用延迟加载的线程安全单例
为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。
package com.zwt.Singleton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public final class Singleton {
private static volatile Singleton instance;
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
Singleton result = instance;
if (result != null) {
return result;
}
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
}
package com.zwt.Singleton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class DemoMultiThread {
public static void main(String[] args) {
System.out.println("If you see the same value, then singleton was reused (yay!)" + "\\n" +
"If you see different values, then 2 singletons were created (booo!!)" + "\\n\\n" +
"RESULT:" + "\\n");
Thread threadFoo = new Thread(new ThreadFoo());
Thread threadBar = new Thread(new ThreadBar());
threadFoo.start();
threadBar.start();
}
static class ThreadFoo implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("FOO");
System.out.println(singleton.value);
}
}
static class ThreadBar implements Runnable {
@Override
public void run() {
Singleton singleton = Singleton.getInstance("BAR");
System.out.println(singleton.value);
}
}
}
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)
RESULT:
FOO
FOO
理论-实现
Eager initialization
在预先初始化中,单例类的实例是在类加载时创建的,这是创建单例类的最简单方法,但它有一个缺点,即即使客户端应用程序可能不会使用它,也会创建实例。
如果您的单例类没有使用大量资源,则可以使用这种方法。但是在大多数情况下,Singleton 类是为文件系统、数据库连接等资源创建的。除非客户端调用该getInstance
方法,否则我们应该避免实例化。此外,此方法不提供任何异常处理选项。
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
Static block initialization
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
Lazy Initialization
在单线程环境下工作正常,但是当涉及到多线程系统时,如果多个线程同时处于 if 条件内,则可能会导致问题。它将破坏单例模式,两个线程将获得单例类的不同实例。
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}
Thread Safe Singleton
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
但由于与同步方法相关的成本,它降低了性能,尽管我们只需要它用于可能创建单独实例的前几个线程(阅读:Java 同步)。为了每次都避免这种额外的开销,使用了双重检查锁定原则。在这种方法中,同步块在 if 条件中使用,并进行额外检查以确保仅创建单例类的一个实例。
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
Bill Pugh Singleton Implementation
在 Java 5 之前,java 内存模型有很多问题,并且在某些场景中,如果太多线程试图同时获取 Singleton 类的实例,上述方法会失败。
请注意包含单例类实例的私有内部静态类。当加载单例类时,SingletonHelper
类不会加载到内存中,只有当有人调用getInstance方法时,才会加载这个类并创建单例类实例。
这是 Singleton 类最广泛使用的方法,因为它不需要同步。我在我的许多项目中都使用了这种方法,而且它也很容易理解和实现。
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
Using Reflection to destroy Singleton Pattern
反射可用于破坏上述所有单例实现方法。让我们用一个示例类来看看这个。
您运行上面的测试类时,您会注意到两个实例的 hashCode 不相同,这破坏了单例模式。反射非常强大,并在很多框架中使用,如 Spring 和 Hibernate。
import java.lang.reflect.Constructor;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
EagerInitializedSingleton instanceThree = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
//关闭了检查
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
instanceThree = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
System.out.println(instanceThree.hashCode());
}
}
Enum Singleton
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
Serialization and Singleton
有时在分布式系统中,我们需要在 Singleton 类中实现 Serializable 接口,以便我们可以将其状态存储在文件系统中并在以后的某个时间点检索它。这是一个也实现了 Serializable 接口的小型单例类。
package com.zwt.SerializableSingleton;
import java.io.Serializable;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class SerializedSingleton implements Serializable {
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton() {
}
private static class SingletonHelper {
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance() {
return SingletonHelper.instance;
}
//这样当JVM从内存中反序列化地"组装"一个新对象时
//就会自动调用这个 readResolve方法来返回我们指定好的对象了,
protected Object readResolve() {
return getInstance();
}
}
序列化单例类的问题在于,无论何时反序列化它,它都会创建该类的一个新实例。让我们用一个简单的程序来看看它。
package com.zwt.SerializableSingleton;
import java.io.*;
/**
* @author ML李嘉图
* @version createtime: 2021-11-09
* Blog: https://www.cnblogs.com/zwtblog/
*/
public class SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
//deserailize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode=" + instanceOne.hashCode());
System.out.println("instanceTwo hashCode=" + instanceTwo.hashCode());
System.out.println("===============");
}
}
所以它破坏了单例模式,为了克服这种情况,我们需要做的就是提供readResolve()
方法的实现。
protected Object readResolve() {
return getInstance();
}
与其他模式关系
- 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
- 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
- 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
- 单例对象可以是可变的。 享元对象是不可变的。
- 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
Factory
背景
工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
假设你正在开发一款物流管理应用。 最初版本只能处理卡车运输, 因此大部分代码都在位于名为 卡车的类中。
一段时间后, 这款应用变得极受欢迎。 你每天都能收到十几次来自海运公司的请求, 希望应用能够支持海上物流功能。
如果代码其余部分与现有类已经存在耦合关系, 那么向程序中添加新类其实并没有那么容易。
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new运算符)。
不用担心, 对象仍将通过 new运算符创建, 只是该运算符改在工厂方法中调用罢了。 工厂方法返回的对象通常被称作 “产品”。
乍看之下, 这种更改可能毫无意义:
我们只是改变了程序中调用构造函数的位置而已。 但是, 仔细想一下, 现在你可以在子类中重写工厂方法, 从而改变其创建产品的类型。
但有一点需要注意:仅当这些产品具有共同的基类或者接口时, 子类才能返回不同类型的产品, 同时基类中的工厂方法还应将其返回类型声明为这一共有接口。
调用工厂方法的代码 (通常被称为客户端代码) 无需了解不同子类返回实际对象之间的差别。 客户端将所有产品视为抽象的 运输
。 客户端知道所有运输对象都提供 交付
方法, 但是并不关心其具体实现方式。
适用场景
- 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
- 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
- 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。
实例-实现
工厂方法模式在 Java 代码中得到了广泛使用。 当你需要在代码中提供高层次的灵活性时, 该模式会非常实用。
- java.util.Calendar、ResourceBundle 和 NumberFormat
getInstance()
方法使用工厂模式。 valueOf()
Boolean、Integer 等包装类中的方法。
核心 Java 程序库中有该模式的应用:
java.util.Calendar#getInstance()
java.util.ResourceBundle#getBundle()
java.text.NumberFormat#getInstance()
java.nio.charset.Charset#forName()
java.net.URLStreamHandlerFactory#createURLStreamHandler(String)
(根据协议返回不同的单例对象)java.util.EnumSet#of()
javax.xml.bind.JAXBContext#createMarshaller()
及其他类似的方法。
识别方法: 工厂方法可通过构建方法来识别, 它会创建具体类的对象, 但以抽象类型或接口的形式返回这些对象。
生成跨平台的 GUI 元素
buttons
buttons/Button.java: 通用产品接口
package com.zwt.buttons;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Common interface for all buttons.
*/
public interface Button {
void render();
void onClick();
}
buttons/HtmlButton.java: 具体产品
package com.zwt.buttons;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* HTML button implementation.
*/
public class HtmlButton implements Button {
public void render() {
System.out.println("<button>Test Button</button>");
onClick();
}
public void onClick() {
System.out.println("Click! Button says - \'Hello World!\'");
}
}
buttons/WindowsButton.java: 另一个具体产品
package com.zwt.buttons;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Windows button implementation.
*/
public class WindowsButton implements Button {
JPanel panel = new JPanel();
JFrame frame = new JFrame();
JButton button;
public void render() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello World!");
label.setOpaque(true);
label.setBackground(new Color(235, 233, 126));
label.setFont(new Font("Dialog", Font.BOLD, 44));
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
frame.getContentPane().add(panel);
panel.add(label);
onClick();
panel.add(button);
frame.setSize(320, 200);
frame.setVisible(true);
onClick();
}
public void onClick() {
button = new JButton("Exit");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
System.exit(0);
}
});
}
}
factory
factory/Dialog.java: 基础创建者
package com.zwt.factory;
import com.zwt.buttons.Button;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Base factory class. Note that "factory" is merely a role for the class. It
* should have some core business logic which needs different products to be
* created.
*/
public abstract class Dialog {
public void renderWindow() {
// ... other code ...
Button okButton = createButton();
okButton.render();
}
/**
* Subclasses will override this method in order to create specific button
* objects.
*/
public abstract Button createButton();
}
factory/HtmlDialog.java: 具体创建者
package com.zwt.factory;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
import com.zwt.buttons.Button;
import com.zwt.buttons.HtmlButton;
/**
* HTML Dialog will produce HTML buttons.
*/
public class HtmlDialog extends Dialog {
@Override
public Button createButton() {
return new HtmlButton();
}
}
factory/WindowsDialog.java: 另一个具体创建者
package com.zwt.factory;
import com.zwt.buttons.Button;
import com.zwt.buttons.WindowsButton;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Windows Dialog will produce Windows buttons.
*/
public class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
Demo.java: 客户端代码
package com.zwt;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
import com.zwt.factory.Dialog;
import com.zwt.factory.HtmlDialog;
import com.zwt.factory.WindowsDialog;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
configure();
runBusinessLogic();
}
/**
* The concrete factory is usually chosen depending on configuration or
* environment options.
*/
static void configure() {
if (System.getProperty("os.name").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HtmlDialog();
}
}
/**
* All of the client code should work with factories and products through
* abstract interfaces. This way it does not care which factory it works
* with and what kind of product it returns.
*/
static void runBusinessLogic() {
dialog.renderWindow();
}
}
OutputDemo.txt: 执行结果 (HtmlDialog)
<button>Test Button</button>
Click! Button says - \'Hello World!\'
OutputDemo.png: 执行结果 (WindowsDialog)
与其他模式的关系
- 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
- 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
- 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
- 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
- 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。
Abstract Factory
背景
抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
假设你正在开发一款家具商店模拟器。 你的代码中包括一些类, 用于表示:
一系列相关产品, 例如 椅子Chair 、 沙发Sofa 和 咖啡桌Coffee Table 。
系列产品的不同变体。 例如, 你可以使用 现代Modern 、 维多利亚Victorian 、 装饰风艺术ArtDeco等风格生成 椅子 、 沙发 和 咖啡桌 。
适用场景
- 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。
- 如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。
实例-实现
使用示例: 抽象工厂模式在 Java 代码中很常见。 许多框架和程序库会将它作为扩展和自定义其标准组件的一种方式。
以下是来自核心 Java 程序库的一些示例:
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()
识别方法: 我们可以通过方法来识别该模式——其会返回一个工厂对象。 接下来, 工厂将被用于创建特定的子组件。
跨平台 GUI 组件系列及其创建方式
在本例中, 按钮和复选框将被作为产品。 它们有两个变体: macOS 版和 Windows 版。
抽象工厂定义了用于创建按钮和复选框的接口。 而两个具体工厂都会返回同一变体的两个产品。
客户端代码使用抽象接口与工厂和产品进行交互。 同样的代码能与依赖于不同工厂对象类型的多种产品变体进行交互。
buttons: 第一个产品层次结构
buttons/Button.java
package com.zwt.buttons;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Abstract Factory assumes that you have several families of products,
* structured into separate class hierarchies (Button/Checkbox). All products of
* the same family have the common interface.
* <p>
* This is the common interface for buttons family.
*/
public interface Button {
void paint();
}
buttons/MacOSButton.java
package com.zwt.buttons;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* All products families have the same varieties (MacOS/Windows).
* <p>
* This is a MacOS variant of a button.
*/
public class MacOSButton implements Button {
@Override
public void paint() {
System.out.println("You have created MacOSButton.");
}
}
buttons/WindowsButton.java
package refactoring_guru.abstract_factory.example.buttons;
package com.zwt.buttons;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* All products families have the same varieties (MacOS/Windows).
* <p>
* This is another variant of a button.
*/
public class WindowsButton implements Button {
@Override
public void paint() {
System.out.println("You have created WindowsButton.");
}
}
checkboxes: 第二个产品层次结构
checkboxes/Checkbox.java
package com.zwt.checkboxes;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
/**
* Checkboxes is the second product family. It has the same variants as buttons.
*/
public interface Checkbox {
void paint();
}
checkboxes/MacOSCheckbox.java
/**
* All products families have the same varieties (MacOS/Windows).
*
* This is a variant of a checkbox.
*/
public class MacOSCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("You have created MacOSCheckbox.");
}
}
checkboxes/WindowsCheckbox.java
/**
* All products families have the same varieties (MacOS/Windows).
*
* This is another variant of a checkbox.
*/
public class WindowsCheckbox implements Checkbox {
@Override
public void paint() {
System.out.println("You have created WindowsCheckbox.");
}
}
factories
factories/GUIFactory.java: 抽象工厂
package com.zwt.factories;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
import com.zwt.buttons.Button;
import com.zwt.checkboxes.Checkbox;
/**
* Abstract factory knows about all (abstract) product types.
*/
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
factories/MacOSFactory.java: 具体工厂 ( macOS)
package com.zwt.factories;
/**
* @author ML李嘉图
* @version createtime: 2021-11-10
* Blog: https://www.cnblogs.com/zwtblog/
*/
import com.zwt.buttons.Button;
import com.zwt.buttons.MacOSButton;
import com.zwt.checkboxes.Checkbox;
import com.zwt.checkboxes.MacOSCheckbox;
/**
* Each concrete factory extends basic factory and responsible for creating
* products of a single variety.
*/
public class MacOSFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacOSButton();
}
@Override
public Checkbox createCheckbox() {
return new MacOSCheckbox();
}
}
factories/WindowsFactory.java: 具体工厂 (Windows)
package com.zwt.factories;
import com.zwt.buttons.Button;
import com.zwt.buttons.WindowsButton;
import com.zwt.checkboxes.Checkbox;
import com.zwt.checkboxes.WindowsCheckbox;
/**
* Each concrete factory extends basic factory and responsible for creating
* products of a single variety.
*/
public class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
app
app/Application.java: 客户端代码
package com.zwt.app;
import com.zwt.buttons.Button;
import com.zwt.checkboxes.Checkbox;
import com.zwt.factories.GUIFactory;
/**
* Factory users don\'t care which concrete factory they use since they work with
* factories and products through abstract interfaces.
*/
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void paint() {
button.paint();
checkbox.paint();
}
}
Demo.java: 程序配置
package com.zwt;
import com.zwt.app.Application;
import com.zwt.factories.GUIFactory;
import com.zwt.factories.MacOSFactory;
import com.zwt.factories.WindowsFactory;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
/**
* Application picks the factory type and creates it in run time (usually at
* initialization stage), depending on the configuration or environment
* variables.
*/
private static Application configureApplication() {
Application app;
GUIFactory factory;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("mac")) {
factory = new MacOSFactory();
app = new Application(factory);
} else {
factory = new WindowsFactory();
app = new Application(factory);
}
return app;
}
public static void main(String[] args) {
Application app = configureApplication();
app.paint();
}
}
与其他模式的关系
- 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
- 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。
- 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。
- 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。
- 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
- 抽象工厂、 生成器和原型都可以用单例模式来实现。
Builder
亦称:建造者模式、Builder
背景
假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。
这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;
甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。
生成器模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中。
适用场景
- 使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
- 当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。
- 使用生成器构造组合树或其他复杂对象。
实例-实现
生成器在 Java 核心程序库中得到了广泛的应用:
java.lang.StringBuilder#append()
(非同步
)java.lang.StringBuffer#append()
(同步
)java.nio.ByteBuffer#put()
(还有CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
和DoubleBuffer
)javax.swing.GroupLayout.Group#addComponent()
java.lang.Appendable
的所有实现
识别方法: 生成器模式可以通过类来识别, 它拥有一个构建方法和多个配置结果对象的方法。 生成器方法通常支持链式编程 (例如 someBuilder->setValueA(1)->setValueB(2)->create()
)。
分步制造汽车
在本例中, 生成器模式允许你分步骤地制造不同型号的汽车。
示例还展示了生成器如何使用相同的生产过程制造不同类型的产品 (汽车手册)。
主管控制着构造顺序。 它知道制造各种汽车型号需要调用的生产步骤。 它仅与汽车的通用接口进行交互。 这样就能将不同类型的生成器传递给主管了。
最终结果将从生成器对象中获得, 因为主管不知道最终产品的类型。 只有生成器对象知道自己生成的产品是什么。
builders
builders/Builder.java: 通用生成器接口
/**
* Builder interface defines all possible ways to configure a product.
*/
public interface Builder {
void setCarType(CarType type);
void setSeats(int seats);
void setEngine(Engine engine);
void setTransmission(Transmission transmission);
void setTripComputer(TripComputer tripComputer);
void setGPSNavigator(GPSNavigator gpsNavigator);
}
builders/CarBuilder.java: 汽车生成器
/**
* Concrete builders implement steps defined in the common interface.
*/
public class CarBuilder implements Builder {
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;
public void setCarType(CarType type) {
this.type = type;
}
@Override
public void setSeats(int seats) {
this.seats = seats;
}
@Override
public void setEngine(Engine engine) {
this.engine = engine;
}
@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}
@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}
@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}
public Car getResult() {
return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}
builders/CarManualBuilder.java: 汽车手册生成器
/**
* Unlike other creational patterns, Builder can construct unrelated products,
* which don\'t have the common interface.
*
* In this case we build a user manual for a car, using the same steps as we
* built a car. This allows to produce manuals for specific car models,
* configured with different features.
*/
public class CarManualBuilder implements Builder{
private CarType type;
private int seats;
private Engine engine;
private Transmission transmission;
private TripComputer tripComputer;
private GPSNavigator gpsNavigator;
@Override
public void setCarType(CarType type) {
this.type = type;
}
@Override
public void setSeats(int seats) {
this.seats = seats;
}
@Override
public void setEngine(Engine engine) {
this.engine = engine;
}
@Override
public void setTransmission(Transmission transmission) {
this.transmission = transmission;
}
@Override
public void setTripComputer(TripComputer tripComputer) {
this.tripComputer = tripComputer;
}
@Override
public void setGPSNavigator(GPSNavigator gpsNavigator) {
this.gpsNavigator = gpsNavigator;
}
public Manual getResult() {
return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
}
}
cars
cars/Car.java: 汽车产品
/**
* Car is a product class.
*/
public class Car {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;
private double fuel = 0;
public Car(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
if (this.tripComputer != null) {
this.tripComputer.setCar(this);
}
this.gpsNavigator = gpsNavigator;
}
public CarType getCarType() {
return carType;
}
public double getFuel() {
return fuel;
}
public void setFuel(double fuel) {
this.fuel = fuel;
}
public int getSeats() {
return seats;
}
public Engine getEngine() {
return engine;
}
public Transmission getTransmission() {
return transmission;
}
public TripComputer getTripComputer() {
return tripComputer;
}
public GPSNavigator getGpsNavigator() {
return gpsNavigator;
}
}
cars/Manual.java: 手册产品
/**
* Car manual is another product. Note that it does not have the same ancestor
* as a Car. They are not related.
*/
public class Manual {
private final CarType carType;
private final int seats;
private final Engine engine;
private final Transmission transmission;
private final TripComputer tripComputer;
private final GPSNavigator gpsNavigator;
public Manual(CarType carType, int seats, Engine engine, Transmission transmission,
TripComputer tripComputer, GPSNavigator gpsNavigator) {
this.carType = carType;
this.seats = seats;
this.engine = engine;
this.transmission = transmission;
this.tripComputer = tripComputer;
this.gpsNavigator = gpsNavigator;
}
public String print() {
String info = "";
info += "Type of car: " + carType + "\\n";
info += "Count of seats: " + seats + "\\n";
info += "Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\\n";
info += "Transmission: " + transmission + "\\n";
if (this.tripComputer != null) {
info += "Trip Computer: Functional" + "\\n";
} else {
info += "Trip Computer: N/A" + "\\n";
}
if (this.gpsNavigator != null) {
info += "GPS Navigator: Functional" + "\\n";
} else {
info += "GPS Navigator: N/A" + "\\n";
}
return info;
}
}
cars/CarType.java
public enum CarType {
CITY_CAR, SPORTS_CAR, SUV
}
components
components/Engine.java: 产品特征 1
/**
* Just another feature of a car.
*/
public class Engine {
private final double volume;
private double mileage;
private boolean started;
public Engine(double volume, double mileage) {
this.volume = volume;
this.mileage = mileage;
}
public void on() {
started = true;
}
public void off() {
started = false;
}
public boolean isStarted() {
return started;
}
public void go(double mileage) {
if (started) {
this.mileage += mileage;
} else {
System.err.println("Cannot go(), you must start engine first!");
}
}
public double getVolume() {
return volume;
}
public double getMileage() {
return mileage;
}
}
components/GPSNavigator.java: 产品特征 2
/**
* Just another feature of a car.
*/
public class GPSNavigator {
private String route;
public GPSNavigator() {
this.route = "221b, Baker Street, London to Scotland Yard, 8-10 Broadway, London";
}
public GPSNavigator(String manualRoute) {
this.route = manualRoute;
}
public String getRoute() {
return route;
}
}
components/Transmission.java: 产品特征 3
/**
* Just another feature of a car.
*/
public enum Transmission {
SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC
}
components/TripComputer.java: 产品特征 4
/**
* Just another feature of a car.
*/
public class TripComputer {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void showFuelLevel() {
System.out.println("Fuel level: " + car.getFuel());
}
public void showStatus() {
if (this.car.getEngine().isStarted()) {
System.out.println("Car is started");
} else {
System.out.println("Car isn\'t started");
}
}
}
director
director/Director.java: 主管控制生成器
/**
* Director defines the order of building steps. It works with a builder object
* through common Builder interface. Therefore it may not know what product is
* being built.
*/
public class Director {
public void constructSportsCar(Builder builder) {
builder.setCarType(CarType.SPORTS_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(3.0, 0));
builder.setTransmission(Transmission.SEMI_AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}
public void constructCityCar(Builder builder) {
builder.setCarType(CarType.CITY_CAR);
builder.setSeats(2);
builder.setEngine(new Engine(1.2, 0));
builder.setTransmission(Transmission.AUTOMATIC);
builder.setTripComputer(new TripComputer());
builder.setGPSNavigator(new GPSNavigator());
}
public void constructSUV(Builder builder) {
builder.setCarType(CarType.SUV);
builder.setSeats(4);
builder.setEngine(new Engine(2.5, 0));
builder.setTransmission(Transmission.MANUAL);
builder.setGPSNavigator(new GPSNavigator());
}
}
Demo.java: 客户端代码
/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) {
Director director = new Director();
// Director gets the concrete builder object from the client
// (application code). That\'s because application knows better which
// builder to use to get a specific product.
CarBuilder builder = new CarBuilder();
director.constructSportsCar(builder);
// The final product is often retrieved from a builder object, since
// Director is not aware and not dependent on concrete builders and
// products.
Car car = builder.getResult();
System.out.println("Car built:\\n" + car.getCarType());
CarManualBuilder manualBuilder = new CarManualBuilder();
// Director may know several building recipes.
director.constructSportsCar(manualBuilder);
Manual carManual = manualBuilder.getResult();
System.out.println("\\nCar manual built:\\n" + carManual.print());
}
}
OutputDemo.txt: 执行结果
Car built:
SPORTS_CAR
Car manual built:
Type of car: SPORTS_CAR
Count of seats: 2
Engine: volume - 3.0; mileage - 0.0
Transmission: SEMI_AUTOMATIC
Trip Computer: Functional
GPS Navigator: Functional