设计模式(1)创建型模式

    科技2025-02-04  18

    创建型模式

    创建型模式:这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。

    参考链接:https://refactoringguru.cn/design-patterns/creational-patterns

    创建型模式:

    工厂模式 :返回具体对象的方法。比如说:java.lang.Proxy#newProxyInstance();抽象工厂模式:抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的类型。简单地说,一个创建新对象的方法,返回的却是接口或者抽象类的,比如说:java.util.Calendar#getInstance();单例模式 : 用来确保类只有一个实例。比如说:java.lang.Runtime#getRuntime();建造者模式:定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建。建造模式通常也使用方法链接来实现。比如说:java.lang.StringBuilder#append();原型模式:使得类的实例能够生成自身的拷贝。如果创建一个对象的实例非常复杂且耗时,就可以使用这种模式,而不重新创建一个新的实例,你可以拷贝一个对象并直接修改它。比如说:java.lang.Object#clone()

    工厂方法模式

    意图

    其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

    问题

    假设你正在开发一款物流管理应用。 最初版本只能处理卡车运输, 因此大部分代码都在位于名为卡车的类中。

    一段时间后, 这款应用变得极受欢迎。 你每天都能收到十几次来自海运公司的请求, 希望应用能够支持海上物流功能。

    如果代码其余部分与现有类已经存在耦合关系, 那么向程序中添加新类其实并没有那么容易。

    这可是个好消息。但是代码问题该如何处理呢?目前,大部分代码都与卡车类相关。在程序中添加轮船类需要修改全部代码。更糟糕的是,如果你以后需要在程序中支持另外一种运输方式,很可能需要再次对这些代码进行大幅修改。

    最后,你将不得不编写繁复的代码,根据不同的运输对象类,在应用中进行不同的处理。

    解决方案

    工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new 运算符)。不用担心,对象仍将通过 new 运算符创建,只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作 “产品”。

    子类可以修改工厂方法返回的对象类型。

    乍看之下,这种更改可能毫无意义:我们只是改变了程序中调用构造函数的位置而已。但是,仔细想一下,现在你可以在子类中重写工厂方法,从而改变其创建产品的类型。

    但有一点需要注意:仅当这些产品具有共同的基类或者接口时,子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型声明为这一共有接口。

    举例来说, 卡车 Truck 和轮船 Ship 类都必须实现运输 Transport 接口, 该接口声明了一个名为 deliver交付的方法。 每个类都将以不同的方式实现该方法:卡车走陆路交付货物,轮船走海路交付货物。 陆路运输 Road­Logistics 类中的工厂方法返回卡车对象,而 海路运输 Sea­Logistics 类则返回轮船对象。

    只要产品类实现一个共同的接口, 你就可以将其对象传递给客户代码, 而无需提供额外数据。

    调用工厂方法的代码(通常被称为客户端代码) 无需了解不同子类返回实际对象之间的差别。客户端将所有产品视为抽象的运输 。客户端知道所有运输对象都提供交付方法,但是并不关心其具体实现方式。

    模式结构

    产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象, 这些接口都是通用的。

    具体产品 (Concrete Products) 是产品接口的不同实现。

    创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。

    你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。

    注意, 尽管它的名字是创建者, 但他最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码, 而非生产程序员。

    具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。

    注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。

    通用代码实现

    public class FactoryMethodPatternDemo { public static void main(String[] args) { Product product1 = Product1Factory.get().createProduct(); Product product2 = Product2Factory.get().createProduct(); Product product3 = Product3Factory.get().createProduct(); product1.execute(); product2.execute(); product3.execute(); } public interface Product { void execute(); } public static class Product1 implements Product { public void execute() { System.out.println("产品1的功能逻辑"); } } public static class Product2 implements Product { public void execute() { System.out.println("产品2的功能逻辑"); } } public static class Product3 implements Product { public void execute() { System.out.println("产品3的功能逻辑"); } } public static abstract class AbstractProductFactory { public Product createProduct() { commonCreate(); return specificCreate(); } private void commonCreate() { System.out.println("生产产品的通用逻辑,修改"); } protected abstract Product specificCreate(); } public static class Product1Factory extends AbstractProductFactory { private static final Product1Factory instance = new Product1Factory(); private Product1Factory() { } public static Product1Factory get() { return instance; } public Product specificCreate() { System.out.println("生产产品1的特殊逻辑"); return new Product1(); } } public static class Product2Factory extends AbstractProductFactory { private static final Product2Factory instance = new Product2Factory(); private Product2Factory() { } public static Product2Factory get() { return instance; } public Product specificCreate() { System.out.println("生产产品2的特殊逻辑"); return new Product2(); } } public static class Product3Factory extends AbstractProductFactory { private static final Product3Factory instance = new Product3Factory(); private Product3Factory() { } public static Product3Factory get() { return instance; } public Product specificCreate() { System.out.println("生产产品3的特殊逻辑"); return new Product3(); } } }

    生成跨平台的 GUI 元素

    使用示例: 工厂方法模式在 Java 代码中得到了广泛使用。 当你需要在代码中提供高层次的灵活性时, 该模式会非常实用。

    在本例中, 按钮担任产品的角色, 对话框担任创建者的角色。

    不同类型的对话框需要其各自类型的元素。 因此我们可为每个对话框类型创建子类并重写其工厂方法。

    现在, 每种对话框类型都将对合适的按钮类进行初始化。 对话框基类使用其通用接口与对象进行交互, 因此代码更改后仍能正常工作。

    通用产品接口

    /** * Common interface for all buttons. */ public interface Button { void render(); void onClick(); }

    具体产品

    /** * 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!'"); } }

    另一个具体产品

    /** * 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); } }); } }

    基础创建者

    /** * 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(); }

    具体创建者

    /** * HTML Dialog will produce HTML buttons. */ public class HtmlDialog extends Dialog { @Override public Button createButton() { return new HtmlButton(); } }

    另一个具体创建者

    /** * Windows Dialog will produce Windows buttons. */ public class WindowsDialog extends Dialog { @Override public Button createButton() { return new WindowsButton(); } }

    客户端代码

    /** * 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(); } }

    模式的应用场景

    1)当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。

    工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。

    例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。

    2)如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。

    继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类?

    解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。

    3)如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

    在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 你会经常碰到这种资源需求。

    JDK 中的工厂设计模式示例

    java.util.Calendar,ResourceBundle 和 NumberFormat getInstance() 方法使用 Factory 模式。valueOf() 包装器类(例如 Boolean,Integer 等)中的方法。

    与其他模式的关系

    在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或建造者模式 (更灵活但更加复杂)。

    抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

    你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。

    原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

    工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

    抽象工厂模式

    意图

    它能创建一系列相关的对象, 而无需指定其具体类。

    抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。

    问题

    假设你正在开发一款家具商店模拟器。你的代码中包括一些类, 用于表示:

    一系列相关产品,例如椅子 Chair 、沙发 Sofa 和咖啡桌 Coffee­Table 。

    系列产品的不同变体。例如,你可以使用现代 Modern 、维多利亚 Victorian 、装饰风艺术 Art­Deco 等风格生成椅子 、沙发和咖啡桌 。

    你需要设法单独生成每件家具对象, 这样才能确保其风格一致。 如果顾客收到的家具风格不一样, 他们可不会开心。

    此外, 你也不希望在添加新产品或新风格时修改已有代码。 家具供应商对于产品目录的更新非常频繁, 你不会想在每次更新时都去修改核心代码的。

    解决方案

    首先,抽象工厂模式建议为系列中的每件产品明确声明接口(例如椅子、沙发或咖啡桌)。然后,确保所有产品变体都继承这些接口。例如,所有风格的椅子都实现椅子接口; 所有风格的咖啡桌都实现咖啡桌接口,以此类推。

    接下来,我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。例如 create­Chair 创建椅子 、create­Sofa 创建沙发和 create­Coffee­Table 创建咖啡桌。这些方法必须返回抽象产品类型,即我们之前抽取的那些接口: 椅子,沙发和咖啡桌等等。

    那么该如何处理产品变体呢? 对于系列产品的每个变体,我们都将基于抽象工厂接口创建不同的工厂类。每个工厂类都只能返回特定类别的产品,例如, 现代家具工厂 Modern­Furniture­Factory 只能创建现代椅子Modern­Chair 、现代沙发 Modern­Sofa 和现代咖啡桌 Modern­Coffee­Table 对象。

    客户端代码可以通过相应的抽象接口调用工厂和产品类。你无需修改实际客户端代码,就能更改传递给客户端的工厂类,也能更改客户端代码接收的产品变体。

    假设客户端想要工厂创建一把椅子。客户端无需了解工厂类,也不用管工厂类创建出的椅子类型。无论是现代风格,还是维多利亚风格的椅子,对于客户端来说没有分别,它只需调用抽象椅子接口就可以了。这样一来,客户端只需知道椅子以某种方式实现了 sit­On 坐下方法就足够了。此外,无论工厂返回的是何种椅子变体,它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。

    最后一点说明: 如果客户端仅接触抽象接口,那么谁来创建实际的工厂对象呢? 一般情况下,应用程序会在初始化阶段创建具体工厂对象。而在此之前,应用程序必须根据配置文件或环境设定选择工厂类别。

    模式结构

    抽象产品(Abstract Product) 为构成系列产品的一组不同但相关的产品声明接口。

    具体产品(Concrete Product) 是抽象产品的多种不同类型实现。所有变体 (维多利亚/现代)都必须实现相应的抽象产品(椅子/沙发)。

    抽象工厂(Abstract Factory) 接口声明了一组创建各种抽象产品的方法。

    具体工厂(Concrete Factory) 实现抽象工厂的构建方法。每个具体工厂都对应特定产品变体,且仅创建此种产品变体。

    尽管具体工厂会对具体产品进行初始化,其构建方法签名必须返回相应的抽象产品。这样,使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端(Client)只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。

    通用代码实现

    public class AbstractFactoryPatternDemo { public static void main(String[] args) { // 产品A1+产品B1 -> 产品A1+产品B3 ProductA firstProductA = Factory1.get().createProductA(); ProductB firstProductB = Factory1.get().createProductB(); firstProductA.execute(); firstProductB.execute(); // 产品A2+产品B2 ProductA secondProductA = Factory2.get().createProductA(); ProductB secondProductB = Factory2.get().createProductB(); secondProductA.execute(); secondProductB.execute(); // 产品A3+产品B3 ProductA thirdProductA = Factory3.get().createProductA(); ProductB thirdProductB = Factory3.get().createProductB(); thirdProductA.execute(); thirdProductB.execute(); } public interface ProductA { void execute(); } public static class ProductA1 implements ProductA { public void execute() { System.out.println("产品A1的功能逻辑"); } } public static class ProductA2 implements ProductA { public void execute() { System.out.println("产品A2的功能逻辑"); } } public static class ProductA3 implements ProductA { public void execute() { System.out.println("产品A3的功能逻辑"); } } public interface ProductB { void execute(); } public static class ProductB1 implements ProductB { public void execute() { System.out.println("产品B1的功能逻辑"); } } public static class ProductB2 implements ProductB { public void execute() { System.out.println("产品B2的功能逻辑"); } } public static class ProductB3 implements ProductB { public void execute() { System.out.println("产品B3的功能逻辑"); } } public interface Factory { ProductA createProductA(); ProductB createProductB(); } public static class Factory1 implements Factory { private static final Factory1 instance = new Factory1(); private Factory1() { } public static Factory get() { return instance; } public ProductA createProductA() { return new ProductA1(); } public ProductB createProductB() { return new ProductB3(); } } public static class Factory2 implements Factory { private static final Factory2 instance = new Factory2(); private Factory2() { } public static Factory get() { return instance; } public ProductA createProductA() { return new ProductA2(); } public ProductB createProductB() { return new ProductB2(); } } public static class Factory3 implements Factory { private static final Factory3 instance = new Factory3(); private Factory3() { } public static Factory get() { return instance; } public ProductA createProductA() { return new ProductA3(); } public ProductB createProductB() { return new ProductB3(); } } }

    跨平台 GUI 组件系列及其创建方式

    使用示例: 抽象工厂模式在 Java 代码中很常见。 许多框架和程序库会将它作为扩展和自定义其标准组件的一种方式。

    在本例中, 按钮和复选框将被作为产品。 它们有两个变体: macOS 版和 Windows 版。

    抽象工厂定义了用于创建按钮和复选框的接口。 而两个具体工厂都会返回同一变体的两个产品。

    客户端代码使用抽象接口与工厂和产品进行交互。 同样的代码能与依赖于不同工厂对象类型的多种产品变体进行交互。

    第一个产品层次结构

    /** * 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. * * This is the common interface for buttons family. */ public interface Button { void paint(); } /** * All products families have the same varieties (MacOS/Windows). * * This is a MacOS variant of a button. */ public class MacOSButton implements Button { @Override public void paint() { System.out.println("You have created MacOSButton."); } } /** * All products families have the same varieties (MacOS/Windows). * * This is another variant of a button. */ public class WindowsButton implements Button { @Override public void paint() { System.out.println("You have created WindowsButton."); } }

    第二个产品层次结构

    /** * Checkboxes is the second product family. It has the same variants as buttons. */ public interface Checkbox { void paint(); } /** * 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."); } } /** * 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."); } }

    抽象工厂

    /** * Abstract factory knows about all (abstract) product types. */ public interface GUIFactory { Button createButton(); Checkbox createCheckbox(); }

    具体工厂 ( mac­OS)

    /** * 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(); } }

    具体工厂 (Windows)

    /** * 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(); } }

    客户端代码

    /** * 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 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(); } }

    模式的应用场景

    1、如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。

    抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

    2、如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

    在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

    JDK 中的抽象工厂设计模式示例

    javax.xml.parsers.DocumentBuilderFactory#newInstance()javax.xml.transform.TransformerFactory#newInstance()javax.xml.xpath.XPathFactory#newInstance()

    与其他模式的关系

    在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或建造者模式 (更灵活但更加复杂)。当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。

    建造者重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 建造者则允许你在获取产品前执行一些额外构造步骤。

    抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

    当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。

    你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

    抽象工厂、 建造者和原型都可以用单例模式来实现。

    建造者模式

    意图

    使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。

    问题

    假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况,那就是这些代码散落在客户端代码的多个位置。

    如果为每种可能的对象都创建一个子类, 这可能会导致程序变得过于复杂。

    例如,我们来思考如何创建一个 房屋 House 对象。 建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?

    最简单的方法是扩展 房屋基类, 然后创建一系列涵盖所有参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂。

    另一种方法则无需生成子类。 你可以在 房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题。

    拥有大量输入参数的构造函数也有缺陷: 这些参数也不是每次都要全部用上的。

    通常情况下, 绝大部分的参数都没有使用, 这使得对于构造函数的调用十分不简洁。 例如, 只有很少的房子有游泳池, 因此与游泳池相关的参数十之八九是毫无用处的。

    解决方案

    建造者模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为建造者的独立对象中。

    建造者模式让你能够分步骤创建复杂对象。 建造者不允许其他对象访问正在创建中的产品。

    该模式会将对象构造过程划分为一组步骤, 比如 build­Walls 创建墙壁和 build­Door 创建房门创建房门等。 每次创建对象时, 你都需要通过建造者对象执行一系列步骤。 重点在于你无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。

    当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 木屋的房门可能需要使用木头制造, 而城堡的房门则必须使用石头制造。

    在这种情况下, 你可以创建多个不同的建造者, 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些建造者 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。

    不同建造者以不同方式执行相同的任务。

    例如, 假设第一个建造者使用木头和玻璃制造房屋, 第二个建造者使用石头和钢铁, 而第三个建造者使用黄金和钻石。 在调用同一组步骤后, 第一个建造者会给你一栋普通房屋, 第二个会给你一座小城堡, 而第三个则会给你一座宫殿。 但是, 只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时, 这样的调用才能返回需要的房屋。

    主管

    你可以进一步将用于创建产品的一系列建造者步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而建造者则提供这些步骤的实现。

    主管知道需要哪些创建步骤才能获得可正常使用的产品。

    严格来说, 你的程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程, 以便在程序中反复使用。

    此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个建造者与主管类关联, 然后使用主管类来构造产品, 就能从建造者处获得构造结果了。

    模式结构

    建造者 (Builder) 接口声明在所有类型建造者中通用的产品构造步骤。

    具体建造者 (Concrete Builders) 提供构造过程的不同实现。 具体建造者也可以构造不遵循通用接口的产品。

    产品 (Products) 是最终生成的对象。 由不同建造者构造的产品无需属于同一类层次结构或接口。

    主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。

    客户端 (Client) 必须将某个建造者对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用建造者对象完成后续所有的构造任务。 但在客户端将建造者对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的建造者。

    通用代码模板实现

    public class BuilderPatternDemo { public static void main(String[] args) { Product product = new ConcreteBuilder() .field1("值1") .field2("值2") .field3("值3") .create(); System.out.println(product); } public static class Product { private String field1; private String field2; private String field3; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } public String getField3() { return field3; } public void setField3(String field3) { this.field3 = field3; } @Override public String toString() { return "Product [field1=" + field1 + ", field2=" + field2 + ", field3=" + field3 + "]"; } } public interface Builder { Builder field1(String value); Builder field2(String value); Builder field3(String value); Product create(); } public static class ConcreteBuilder implements Builder { private Product product = new Product(); public Builder field1(String value) { System.out.println("在设置field1之前进行复杂的校验逻辑"); product.setField1(value); return this; } public Builder field2(String value) { System.out.println("在设置field2之前进行复杂的数据格式转化逻辑"); product.setField2(value); return this; } public Builder field3(String value) { System.out.println("在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联"); product.setField3(value); return this; } public Product create() { return product; } } }

    分步制造汽车

    使用示例: 建造者模式是 Java 世界中的一个著名模式。 当你需要创建一个可能有许多配置选项的对象时, 该模式会特别有用。

    在本例中, 建造者模式允许你分步骤地制造不同型号的汽车。

    示例还展示了建造者如何使用相同的生产过程制造不同类型的产品 (汽车手册)。

    主管控制着构造顺序。 它知道制造各种汽车型号需要调用的生产步骤。 它仅与汽车的通用接口进行交互。 这样就能将不同类型的建造者传递给主管了。

    最终结果将从建造者对象中获得, 因为主管不知道最终产品的类型。 只有建造者对象知道自己生成的产品是什么。

    通用生成器接口

    /** * Builder interface defines all possible ways to configure a product. */ public interface Builder { void setType(Type type); void setSeats(int seats); void setEngine(Engine engine); void setTransmission(Transmission transmission); void setTripComputer(TripComputer tripComputer); void setGPSNavigator(GPSNavigator gpsNavigator); }

    汽车生成器

    /** * Concrete builders implement steps defined in the common interface. */ public class CarBuilder implements Builder { private Type type; private int seats; private Engine engine; private Transmission transmission; private TripComputer tripComputer; private GPSNavigator gpsNavigator; @Override public void setType(Type 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); } }

    汽车手册生成器

    /** * 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 Type type; private int seats; private Engine engine; private Transmission transmission; private TripComputer tripComputer; private GPSNavigator gpsNavigator; @Override public void setType(Type 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); } }

    汽车产品

    /** * Car is a product class. */ public class Car { private final Type type; 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(Type type, int seats, Engine engine, Transmission transmission, TripComputer tripComputer, GPSNavigator gpsNavigator) { this.type = type; this.seats = seats; this.engine = engine; this.transmission = transmission; this.tripComputer = tripComputer; this.tripComputer.setCar(this); this.gpsNavigator = gpsNavigator; } public Type getType() { return type; } 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; } }

    手册产品

    /** * 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 Type type; private final int seats; private final Engine engine; private final Transmission transmission; private final TripComputer tripComputer; private final GPSNavigator gpsNavigator; public Manual(Type type, int seats, Engine engine, Transmission transmission, TripComputer tripComputer, GPSNavigator gpsNavigator) { this.type = type; this.seats = seats; this.engine = engine; this.transmission = transmission; this.tripComputer = tripComputer; this.gpsNavigator = gpsNavigator; } public String print() { String info = ""; info += "Type of car: " + type + "\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; } } public enum Type { CITY_CAR, SPORTS_CAR, SUV }

    产品特征 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; } }

    产品特征 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; } }

    产品特征 3

    /** * Just another feature of a car. */ public enum Transmission { SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC }

    产品特征 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 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.setType(Type.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.setType(Type.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.setType(Type.SUV); builder.setSeats(4); builder.setEngine(new Engine(2.5, 0)); builder.setTransmission(Transmission.MANUAL); builder.setGPSNavigator(new GPSNavigator()); } }

    客户端代码

    /** * 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.getType()); 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()); } }

    模式的应用场景

    1、使用建造者模式可避免 “重叠构造函数 ” 的出现。

    假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。

    class Pizza { Pizza(int size) { ... } Pizza(int size, boolean cheese) { ... } Pizza(int size, boolean cheese, boolean pepperoni) { ... } // ...

    建造者模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了。

    2、你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用建造者模式。

    如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用建造者模式。

    基本建造者接口中定义了所有可能的制造步骤, 具体建造者将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。

    3、使用建造者构造组合树或其他复杂对象。

    建造者模式让你能分步骤构造产品。 你可以延迟执行某些步骤而不会影响最终产品。 你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。

    建造者在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。

    JDK 中的建造者设计模式示例

    java.lang.StringBuilder#append()java.lang.StringBuffer#append()java.nio.ByteBuffer#put() (还有 Char­Buffer、 Short­Buffer、 Int­Buffer、 Long­Buffer、 Float­Buffer 和 Double­Buffer)javax.swing.GroupLayout.Group#addComponent()java.lang.Appendable 的所有实现

    与其他模式的关系

    在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或建造者模式 (更灵活但更加复杂)。

    建造者重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 建造者则允许你在获取产品前执行一些额外构造步骤。

    你可以在创建复杂组合模式树时使用建造者, 因为这可使其构造步骤以递归的方式运行。

    你可以结合使用建造者和桥接模式: 主管类负责抽象工作, 各种不同的建造者负责实现工作。

    抽象工厂、 建造者和原型都可以用单例模式来实现。

    单例模式

    意图

    让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

    问题

    单例模式同时解决了两个问题, 所以违反了单一职责原则:

    1)保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。

    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

    2)为该实例提供一个全局访问节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

    如今, 单例模式已经变得非常流行, 以至于人们会将只解决上文描述中任意一个问题的东西称为单例。

    解决方案

    所有单例的实现都包含以下两个相同的步骤:

    将默认构造函数设为私有, 防止其他对象使用单例类的 new 运算符。新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

    如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

    模式结构

    单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。

    单例的构造函数必须对客户端 (Client) 代码隐藏。 调用获取实例方法必须是获取单例对象的唯一方式。

    基础单例(单线程)

    使用示例: 许多开发者将单例模式视为一种反模式。 因此它在 Java 代码中的使用频率正在逐步减少。

    实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。

    静态类使用

    public class Singleton { public static Map<String,String> cache = new ConcurrentHashMap<String, String>(); } 以上这种方式在我们平常的业务开发中非常场常见,这样静态类的方式可以在第一次运行的时候直接初始化 Map 类,同时这里我们也不需要到延迟加载在使用。在不需要维持任何状态下,仅仅用于全局访问,这个使用使用静态类的方式更加方便。但如果需要被继承以及需要维持一些特定状态的情况下,就适合使用单例模式。

    懒汉模式(线程不安全)

    public class LazyInitializedSingleton { private static LazyInitializedSingleton instance; private LazyInitializedSingleton(){} public static LazyInitializedSingleton getInstance(){ if(instance == null){ instance = new LazyInitializedSingleton(); } return instance; } } 上面的实现在单线程环境下可以很好地工作,但是对于多线程系统,如果多个线程同时位于 if 条件中,则可能导致问题。它将破坏单例模式,并且两个线程都将获得单例类的不同实例。

    懒汉模式(线程安全)

    public class LazyInitializedSingleton { private static LazyInitializedSingleton instance; private LazyInitializedSingleton(){} public static synchronized LazyInitializedSingleton getInstance(){ if(instance == null){ instance = new LazyInitializedSingleton(); } return instance; } } 此种模式虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。

    饿汉模式(线程安全)

    public class HungryInitializedSingleton { private static final HungryInitializedSingleton instance = new HungryInitializedSingleton(); private HungryInitializedSingleton() { } public static HungryInitializedSingleton getInstance() { return instance; } } 如果您的单例类没有使用大量资源,则可以使用这种方法。但是在大多数情况下,都是为文件系统,数据库连接等资源创建 Singleton 类的 getInstance。除非客户端调用该方法,否则应避免实例化。另外,此方法不提供任何用于异常处理的选项。

    静态内部类(线程安全)

    在 Java 5 之前,Java 内存模型存在很多问题,并且上述方法在某些情况下会失败,在某些情况下,太多的线程试图同时获取 Singleton 类的实例。因此,比尔·普格提出了另一种方法,该方法使用内部静态帮助程序类创建 Singleton 类。

    public class BillPughSingleton { private BillPughSingleton(){} private static class SingletonHelper{ private static final BillPughSingleton INSTANCE = new BillPughSingleton(); } public static BillPughSingleton getInstance(){ return SingletonHelper.INSTANCE; } } 注意包含内部单例类实例的私有内部静态类。加载 singleton 类时,SingletonHelper 该类不会加载到内存中,只有当有人调用 getInstance 方法时,该类才会加载并创建 Singleton 类实例。

    双重锁校验(线程安全)

    public class DoubleCheckSingleton { private static doubleCheckSingleton instance; private doubleCheckSingleton(){} public static doubleCheckSingleton getInstance(){ if(instance == null){ synchronized (doubleCheckSingleton.class) { if(instance == null){ instance = new doubleCheckSingleton(); } } } return instance; } } 网上有人说,这种实现方式有些问题。因为指令重排序,可能会导致 IdGenerator 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。要解决这个问题,需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行。实际上,只有很低版本的 Java 才会有这个问题。现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法就是把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。

    CAS「AtomicReference」(线程安全)

    public class CasSingleton { private static final AtomicReference<CasSingleton> INSTANCE = new AtomicReference<CasSingleton>(); private static CasSingleton instance; private CasSingleton() { } public static final CasSingleton getInstance() { for (;;) { CasSingleton instance = INSTANCE.get(); if (null != instance) return instance; INSTANCE.compareAndSet(null, new CasSingleton()); return INSTANCE.get(); } } } 使用 CAS 的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于 CAS 的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。当然 CAS 也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。

    Effective Java 作者推荐的枚举单例(线程安全)

    public enum EnumIvoryTower { INSTANCE } EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE; EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE; assertEquals(enumIvoryTower1, enumIvoryTower2); // true 这种方式解决了最主要的:线程安全、自由串行化、单一实例。缺点是枚举类型有些不灵活。例如,它不允许延迟初始化。

    使用反射破坏单例模式

    public class ReflectionSingletonTest { public static void main(String[] args) { HungryInitializedSingleton instanceOne = HungryInitializedSingleton.getInstance(); HungryInitializedSingleton instanceTwo = null; try { Constructor[] constructors = HungryInitializedSingleton.class.getDeclaredConstructors(); for (Constructor constructor : constructors) { constructor.setAccessible(true); instanceTwo = (HungryInitializedSingleton) constructor.newInstance(); break; } } catch (Exception e) { e.printStackTrace(); } System.out.println(instanceOne.hashCode()); System.out.println(instanceTwo.hashCode()); } }

    当您运行上述测试类时,您会注意到两个实例的 hashCode 不相同,这会破坏单例模式。

    如何避免反射破坏单例模式?

    在构造方法中,判断类实例是否被创建,如果被创建,则抛出异常。

    private HungryInitializedSingleton() { if (instance != null){ throw new RuntimeException(); } }

    序列化和单例

    有时在分布式系统中,我们需要在 Singleton 类中实现 Serializable 接口,以便我们可以将其状态存储在文件系统中,并在以后的某个时间点检索它。这是一个小的单例类,它也实现了 Serializable 接口。

    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; } }

    序列化单例类的问题在于,每当我们反序列化它时,它将创建该类的新实例。

    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()); } }

    上面程序的输出是:

    2125039532 312714112

    因此,它破坏了单例模式。如果要克服了这种情况,我们需要做的所有工作都提供了 readResolve() 方法的实现。

    /** * 防止反序列化破坏单例,反序列化时,如果定义了readResolve方法,则直接返回吃方法指定的对象,而不需要单独再创建对象 * @return */ protected Object readResolve() { return getInstance(); }

    模式的应用场景

    1、如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

    单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

    2、如果你需要更加严格地控制全局变量, 可以使用单例模式。

    单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

    请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改获取实例方法, 即 getInstance 中的代码即可实现。

    JDK 中的单例设计模式示例

    java.lang.Runtime#getRuntime()java.awt.Desktop#getDesktop()java.lang.System#getSecurityManager()

    与其他模式的关系

    外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。

    如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。

    只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。单例对象可以是可变的。 享元对象是不可变的。

    抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。

    原型模式

    意图

    使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

    问题

    如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于相同类的对象。 然后, 你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。

    不错! 但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。

    “从外部” 复制对象并非总是可行。

    直接复制还有另外一个问题。 因为你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如可向方法的某个参数传入实现了某个接口的任何对象。

    解决方案

    原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个克隆方法。

    所有的类对克隆方法的实现都非常相似。 该方法会创建一个当前类的对象, 然后将原始对象所有的成员变量值复制到新建的类中。 你甚至可以复制私有成员变量, 因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。

    支持克隆的对象即为原型。 当你的对象有几十个成员变量和几百种类型时, 对其进行克隆甚至可以代替子类的构造。

    预生成原型可以代替子类的构造。

    其运作方式如下: 创建一系列不同类型的对象并不同的方式对其进行配置。 如果所需对象与预先配置的对象相同, 那么你只需克隆原型即可, 无需新建一个对象。

    真实世界类比

    现实生活中, 产品在得到大规模生产前会使用原型进行各种测试。 但在这种情况下, 原型只是一种被动的工具, 不参与任何真正的生产活动。

    一个细胞的分裂。

    由于工业原型并不是真正意义上的自我复制, 因此细胞有丝分裂 (还记得生物学知识吗?) 或许是更恰当的类比。 有丝分裂会产生一对完全相同的细胞。 原始细胞就是一个原型, 它在复制体的生成过程中起到了推动作用。

    模式结构

    原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 clone 克隆的方法。

    具体原型 (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。

    客户端 (Client) 可以复制实现了原型接口的任何对象。

    通用代码实现

    public class PrototypePatternDemo { public static void main(String[] args) { try { Product product = new Product("测试产品", new Component("测试组件")); Product copyProduct = (Product) product.clone(); System.out.println(copyProduct); } catch (Exception e) { e.printStackTrace(); } } public static class Component { private String name; public Component(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Component [name=" + name + "]"; } @Override protected Object clone() throws CloneNotSupportedException { return new Component(getName()); } } public static class Product { private String name; private Component component; public Product(String name, Component component) { super(); this.name = name; this.component = component; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Component getComponent() { return component; } public void setComponent(Component component) { this.component = component; } @Override public String toString() { return "Product [name=" + name + ", component=" + component + "]"; } @Override protected Object clone() throws CloneNotSupportedException { // 浅拷贝,就是我们现在的一个实现,就是仅仅简单的对当前所有的变量进行一个拷贝 // return new Product(getName(), getComponent()); // 深拷贝,递归对自己引用的对象也进行拷贝 return new Product(getName(), (Component)getComponent().clone()); } } }

    原型注册表实现

    原型注册表 (Prototype Registry) 提供了一种访问常用原型的简单方法, 其中存储了一系列可供随时复制的预生成对象。 最简单的注册表原型是一个名称 → 原型的哈希表。 但如果需要使用名称以外的条件进行搜索, 你可以创建更加完善的注册表版本。

    复制图形

    使用示例: Java 的 Cloneable(可克隆) 接口就是立即可用的原型模式。

    任何类都可通过实现该接口来实现可被克隆的性质。

    让我们来看看在不使用标准 Cloneable 接口的情况下如何实现原型模式。

    通用形状接口

    public abstract class Shape { public int x; public int y; public String color; public Shape() { } public Shape(Shape target) { if (target != null) { this.x = target.x; this.y = target.y; this.color = target.color; } } public abstract Shape clone(); @Override public boolean equals(Object object2) { if (!(object2 instanceof Shape)) return false; Shape shape2 = (Shape) object2; return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color); } }

    简单形状

    public class Circle extends Shape { public int radius; public Circle() { } public Circle(Circle target) { super(target); if (target != null) { this.radius = target.radius; } } @Override public Shape clone() { return new Circle(this); } @Override public boolean equals(Object object2) { if (!(object2 instanceof Circle) || !super.equals(object2)) return false; Circle shape2 = (Circle) object2; return shape2.radius == radius; } }

    另一个形状

    public class Rectangle extends Shape { public int width; public int height; public Rectangle() { } public Rectangle(Rectangle target) { super(target); if (target != null) { this.width = target.width; this.height = target.height; } } @Override public Shape clone() { return new Rectangle(this); } @Override public boolean equals(Object object2) { if (!(object2 instanceof Rectangle) || !super.equals(object2)) return false; Rectangle shape2 = (Rectangle) object2; return shape2.width == width && shape2.height == height; } }

    克隆示例

    public class Demo { public static void main(String[] args) { List<Shape> shapes = new ArrayList<>(); List<Shape> shapesCopy = new ArrayList<>(); Circle circle = new Circle(); circle.x = 10; circle.y = 20; circle.radius = 15; circle.color = "red"; shapes.add(circle); Circle anotherCircle = (Circle) circle.clone(); shapes.add(anotherCircle); Rectangle rectangle = new Rectangle(); rectangle.width = 10; rectangle.height = 20; rectangle.color = "blue"; shapes.add(rectangle); cloneAndCompare(shapes, shapesCopy); } private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) { for (Shape shape : shapes) { shapesCopy.add(shape.clone()); } for (int i = 0; i < shapes.size(); i++) { if (shapes.get(i) != shapesCopy.get(i)) { System.out.println(i + ": Shapes are different objects (yay!)"); if (shapes.get(i).equals(shapesCopy.get(i))) { System.out.println(i + ": And they are identical (yay!)"); } else { System.out.println(i + ": But they are not identical (booo!)"); } } else { System.out.println(i + ": Shape objects are the same (booo!)"); } } } }

    模式的应用场景

    1、如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。

    这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 你的代码也不能依赖这些对象所属的具体类, 因为你不知道它们的具体信息。

    原型模式为客户端代码提供一个通用接口, 客户端代码可通过这一接口与所有实现了克隆的对象进行交互, 它也使得客户端代码与其所克隆的对象具体类独立开来。

    2、如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。

    在原型模式中, 你可以使用一系列预生成的、 各种类型的对象作为原型。

    客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可。、

    JDK 中的原型设计模式示例

    java.lang.Object#clone()

    与其他模式的关系

    在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

    抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

    原型可用于保存命令模式的历史记录。

    大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

    原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

    有时候原型可以作为备忘录模式的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。

    抽象工厂、 生成器和原型都可以用单例模式来实现。

    Processed: 0.029, SQL: 8