设计模式系列文章导航

  1. 设计模式概述
  2. 面向对象设计原则
  3. 设计模式 - 创建型模式 📍当前位置
  4. 设计模式 - 结构型模式
  5. 设计模式 - 行为型模式

创建型模式

  • 创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离,对用户隐藏了类的实例的创建细节。
  • 创建型模式描述如何将对象的创建和使用分离,让用户在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。每一个创建型模式都通过采用不同的解决方案来回答3个问题,即创建什么(What)、由谁创建(Who)和何时创建 (When)。

简单工厂模式(Simple Factory)

定义

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。

模式结构

结构图

image-20231228135115130

实现代码

  • 抽象产品类:

    1
    2
    3
    4
    5
    6
    7
    8
    public abstract class Product {
    // 所有产品类的公共业务方法
    public void methodSame() {
    //公共方法的实现
    }
    //声明抽象业务方法
    public abstract void methodDiff();
    }
  • 具体产品类:

    1
    2
    3
    4
    5
    6
    public class ConcreteProduct extends Product{
    //实现业务方法
    public void methodDiff() {
    //业务方法的实现
    }
    }
  • 工厂类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Factory {
    //静态工厂方法
    public static Product getProduct(String arg) {
    Product product = null;
    if (arg.equalsIgnoreCase("A")) {
    product = new ConcreteProductA();
    //初始化设置product
    }
    else if (arg.equalsIgnoreCase("B")) {
    product = new ConcreteProductB();
    //初始化设置product
    }
    return product;
    }
    }
  • 客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Client {
    public static void main(String args[]) {
    Product product;
    //通过工厂类创建产品对象
    product = Factory.getProduct("A");
    product.methodSame();
    product.methodDiff();
    }
    }

应用实例

​ 某软件公司要基于Java语言开发一套图表库,该图表库可以为应用系统提供多种不同外观的图表,例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。该软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,通过设置不同的参数即可得到不同类型的图表,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。

​ 现使用简单工厂模式来设计该图表库

image-20231228135130792

  • 抽象产品类:Chart
  • 具体产品类:HistogramChartPieChartLineChart
  • 工厂类:ChartFactory

特点及使用环境

优点

  1. 实现了对象创建和使用的分离
  2. 客户端无须知道所创建的具体产品类的类名
  3. 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,提高了系统的灵活性

缺点

  1. 工厂类集中了所有产品的创建逻辑,职责过重
  2. 增加系统中类的个数,提高了系统的复杂度和理解难度
  3. 系统扩展困难,添加新产品需要修改工厂逻辑
  4. 由于使用了静态工厂方法,工厂角色无法形成基于继承的等级结构

适用环境

  1. 工厂类中需要创建的对象较少
  2. 客户端只知道传入工厂的参数,不关心对象如何创建

工厂方法模式★(Factory Method)

定义

定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。

模式结构

结构图

image-20231228134757404

实现代码

  • 抽象工厂(Factory):

    在抽象工厂类中声明了工厂方法,用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。

    1
    2
    3
    public interface Factory {
    public Product factoryMethod();
    }
  • 具体工厂(ConcreteFactory):

    它是抽象工厂类的子类,实现了在抽象工厂中声明的工厂方法,并可由客户端调用,返回一个具体产品类的实例。

    1
    2
    3
    4
    5
    public class ConcreteFactory implements Factory {
    public Product factoryMethod() {
    return new ConcreteProduct();
    }
    }
  • 抽象产品(Product):

    它是定义产品的接口,是工厂方法模式所创建对象的超类型, 也就是产品对象的公共父类。

    1
    2
    3
    4
    5
    6
    7
    8
    public abstract class Product {
    // 所有产品类的公共业务方法
    public void methodSame() {
    //公共方法的实现
    }
    //声明抽象业务方法
    public abstract void methodDiff();
    }
  • 具体产品(ConcreteProduct):

    它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。

    1
    2
    3
    4
    5
    6
    public class ConcreteProduct extends Product{
    //实现业务方法
    public void methodDiff() {
    //业务方法的实现
    }
    }
  • 客户端:

    在客户端代码中,开发人员只需关心工厂类即可,不同的具体工厂可以创建不同的产品。

    1
    2
    3
    // 可通过配置文件与反射机制实现
    Factory factory = new ConoreteFactory();
    Product product = factory.factoryMethod();

    反射机制与配置文件可参考:Java反射机制与配置文件

应用实例

​ 某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。

​ 为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统(注:在Java中常用的日志记录工具有 SLF4J、Log4j、GCLogViewer、Logstash 等)。

image-20231228134825144

  • 抽象产品:Logger
  • 具体产品:FileLoggerDatabaseLogger
  • 抽象工厂:LoggerFactory
  • 具体工厂:FileLoggerFactoryDatabaseLoggerFactory

模式变化

工厂方法的重载

在某些情况下,可以通过多种方式来初始化同一个产品类。比如实例中的记录器,可以为各种日志记录器提供默认实现;还可以为数据库日志记录器提供数据库连接字符串,为文件日志记录器提供文件路径;也可以将相关参数封装在一个Object类型的对象中,通过Object对象将配置参数传入工厂类。此时可以提供一组重载的工厂方法,以不同的方式对产品对象进行创建。

image-20231228134856569

  • 抽象工厂类:

    1
    2
    3
    4
    5
    public interface LoggerFactory {
    public Logger createLogger();
    public Logger createLogger(String args);
    public Logger createLogger(Object obj);
    }
  • 具体工厂类(以DatabaseLoggerFactory举例):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class DatabaseLoggerFactory implements LoggerFactory {
    public Logger createLogger() {
    //使用默认方式连接数据库
    Logger logger = new DatabaseLogger();
    //初始化数据库日志记录器
    return logger;
    }
    public Logger createLogger(String args) {
    //使用参数args作为连接字符串来连接数据库
    Logger logger = new DatabaseLogger();
    //初始化数据库日志记录器
    return logger;
    }
    public Logger createLogger(Object obj) {
    //使用封装在参数obj中的连接字符串来连接数据库
    Logger logger = new DatabaseLogger();
    //使用封装在参数obj中的数据来初始化数据库日志记录器
    return logger;
    }
    }

在抽象工厂中声明了多个重载的工厂方法,在具体工厂中实现了这些工厂方法,这些方法可以包含不同的业务逻辑,以满足产品对象的多样化创建需求。

工厂方法的隐藏

有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法。

  • 抽象工厂类,将原先的接口(interface)修改为抽象类(abstract):

    1
    2
    3
    4
    5
    6
    7
    8
    public abstract class LoggerFactory {
    //在工厂类中直接调用日志记录器类的业务方法writeLog()
    public void writeLog() {
    Logger logger = this.createLogger();
    logger.writeLog();
    }
    public abstract Logger createLogger();
    }
  • 客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    public class Client {
    public static void main(String args[ ]) {
    LoggerFactory factory;
    factory = (LoggerFactory)XMLUtil.getBean();
    //直接使用工厂对象来调用产品对象的业务方法
    factory.writeLog();
    }
    }

通过把业务方法的调用移至工厂类中,可以直接使用工厂对象来调用产品对象的业务方法,客户端无须使用工厂方法来创建产品对象。

特点及使用环境

优点

  1. 向客户隐藏了哪种具体产品类将被实例化这一细节
  2. 能够让工厂自主确定创建何种产品对象
  3. 完全符合开闭原则

缺点

  1. 系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
  2. 增加了系统的抽象性和理解难度

适用环境

  1. 客户端不知道它所需要的对象的类(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)
  2. 抽象工厂类通过其子类来指定创建哪个对象

抽象工厂模式(Abstract Factory)

定义

提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

image-20231228141139951

模式结构

结构图

image-20231228135936556

实现代码

  • 抽象工厂(AbstractFactory)

    它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。

    1
    2
    3
    4
    public interface AbstractFactory {
    public AbstractProductA createProductA(); //工厂方法一
    public AbstractProductB createProductB(); //工厂方法二
    }
  • 具体工厂(ConcreteFactory)

    它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ConcreteFactoryl extends AbstractFactory {
    //工厂方法一
    public AbstractProductA createProductA() {
    return new ConcreteProductA1();
    }
    //工厂方法二
    public AbstractProductB createProductB() {
    return new ConcreteProductB1();
    }
    }
  • 抽象产品(AbstractProduct)

    它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。

  • 具体产品(ConcreteProduct)

    它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。

应用实例

​ 某软件公司要开发一套界面皮肤库,可以对基于Java的桌面软件进行界面美化。用户在使用时可以通过菜单来选择皮肤,不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素,例如春天(Spring)风格的皮肤将提供浅绿色的按钮、绿色边框的文本框和绿色边框的组合框,而夏天(Summer)风格的皮肤则提供浅蓝色的按钮、蓝色边框的文本框和蓝色边框的组合框,其结构示意图如下图所示:

image-20231228141412998

​ 该皮肤库需要具备良好的灵活性和可扩展性,用户可以自由选择不同的皮肤,开发人员可以在不修改既有代码的基础上增加新的皮肤。

​ 现使用抽象工厂模式来设计该界面皮肤库。

image-20231228141535559

  • 抽象工厂:SkinFactory
  • 具体工厂:SpringSkinFactorySummerSkinFactory
  • 抽象产品:ButtonTextFieldComboBox
  • 具体产品:SpringButtonSpringTextFieldSpringComboBoxSummerButtonSummerTextFieldSummerComboBox

模式变化

开闭原则的倾斜性

在抽象工厂模式中增加新的产品族很方便,但是增加新的产品等级结构很麻烦,抽象工厂模式的这种性质称为开闭原则的倾斜性。

  • 对于增加新的产品族,抽象工厂模式很好地支持了开闭原则,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改
  • 对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了开闭原则

特点及使用环境

优点

  1. 隔离了具体类的生成,使得客户端并不需要知道什么被创建
  2. 能够保证客户端始终只使用同一个产品族中的对象
  3. 增加新的产品族很方便,无须修改已有系统,符合开闭原则

缺点

  1. 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则

适用环境

  1. 系统不依赖于产品类实例如何被创建、组合和表达的细节
  2. 系统中有多于一个的产品族,但每次只使用其中某一产品族
  3. 属于同一个产品族的产品将在一起使用
  4. 产品等级结构稳定,在设计完成之后不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

建造者模式★(Builder)

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

模式结构

结构图

image-20231228143123688

实现代码

  • 产品(Product)

    复杂对象是指那些包含多个成员变量的对象,这些成员变量也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件。

    1
    2
    3
    4
    5
    6
    7
    public class Product {
    //定义部件,部件可以是任意类型,包括值类型和引用类型
    private String partA;
    private String partB;
    private String partC;
    //Getter方法和Setter方法
    }
  • 抽象建造者(Builder)

    它为创建一个产品对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public abstract class Builder {
    //创建产品对象
    protected Product product = new Product();
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract void buildPartC();
    //返回产品对象
    public Product getResult() {
    return product;
    }
    }
  • 具体建造者(ConcreteBuilder)

    它实现了 Builder接口,实现各个部件的具体构造和装配方法,定义并明确所创建的复杂对象,还可以提供一个方法返回创建好的复杂产品对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class ConcreteBuilder1 extends Builder {
    public void buildPartA() {
    product.setPartA("A1");
    }
    public void buildPartB() {
    product.setPartB("B1");
    }
    public void buildPartC() {
    product.setPartC("C1");
    }
    }
  • 指挥者(Director)

    指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制实现),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Director {
    private Builder builder;
    public Director(Builder builder) {
    this.builder = builder;
    }
    public void setBuilder(Builder builder) {
    this.builder = builer;
    }
    //产品构建与组装方法
    public Product construct() {
    builder.buildPartA();
    builder.buildPartB();
    builder.buildPartC();
    return builder.getResult();
    }
    }

    在指挥者类中可以注入一个抽象建造者类型的对象,它提供了一个建造方法construct(), 在该方法中调用了 builder对象的构造部件的方法,最后返回一个产品对象。

  • 客户端

    对于客户端而言,只需要关心具体建造者的类型,无须关心产品对象的具体组装过程。

    1
    2
    3
    4
    //可通过配置文件实现
    Builder builder = new ConcreteBuilder1();
    Director director = new Director(builder);
    Product product = director.construct();

应用实例

​ 某游戏软件公司决定开发一款基于角色扮演的多人在线网络游戏,玩家可以在游戏中扮演虚拟世界中的一个特定角色,角色根据不同的游戏情节和统计数据(例如力量、魔法、技能等)具有不同的能力,角色也会随着不断升级而拥有更加强大的能力。
​ 作为该游戏的一个重要组成部分,需要对游戏角色进行设计,而且随着该游戏的升级将不断增加新的角色。通过分析发现,游戏角色是一个复杂对象,它包含性别、面容等多个组成部分,不同类型的游戏角色,其性别、面容、服装、发型等外部特性都有所差异,例如“天使”拥有美丽的面容和披肩的长发,并身穿一袭白裙;而“恶魔”极其丑陋,留着光头并穿一件刺眼的黑衣。
​ 无论是何种造型的游戏角色,它的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。现使用建造者模式来实现游戏角色的创建。

image-20231228144252912

  • 指挥者:ActorController
  • 抽象建造者:ActorBuilder
  • 具体建造者:HeroBuilderAngelBuilderDevilBuilder
  • 复杂产品:Actor

特点及使用环境

优点

  1. 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  2. 每一个具体建造者都相对独立,与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,扩展方便,符合开闭原则
  3. 可以更加精细地控制产品的创建过程

缺点

  1. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,不适合使用建造者模式,因此其使用范围受到一定的限制
  2. 如果产品的内部变化复杂,可能会需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加了系统的理解难度和运行成本

适用环境

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量
  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序
  3. 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中
  4. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品

原型模式★(Prototype)

定义

使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象

模式结构

结构图

image-20231228144922896

实现代码

  • 抽象原型类(Prototype)

    它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口,甚至还可以是具体实现类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public abstract class Prototype {
    public abstract Prototype clone();
    }

    public class ConcretePrototype extends Prototype {
    private String attr; //成员变量
    public void setAttr(String attr) {
    this.attr = attr;
    }
    public String getAttr() {
    return this.attr;
    }
    //克隆方法
    public Prototype clone() {
    Prototype prototype = new ConcretePrototype(); //创建新对象
    prototype.setAttr(this.attr);
    return prototype;
    }
    }
  • 具体原型类(ConcretePrototype)

    它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ConcretePrototype implements Cloneable {
    public Prototype clone() {
    Object object = null;
    try {
    object = super.clone(); //浅克隆
    }
    catch (CloneNotSupportedException exception) {
    System.err.println("Not support cloneable");
    }
    return (Prototype) object;
    }
    }
  • 客户端

    在客户类中,让一个原型对象克隆自身从而创建一个新的对象,只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

    1
    2
    Prototype protptype = new ConcretePrototype();
    Prototype copy = protptype.clone();

应用实例

​ 在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。

​ 试使用原型模式对该OA系统中的工作周报创建模块进行改进。

image-20231228170138649

  • 抽象原型类:Object
  • 具体原型类:WeeklyLog
  • 成员类:Attachment

模式变化

通用实现(浅克隆)

  • 通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,同时将相关的参数传入新创建的对象中,保证它们的成员变量相同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public abstract class Prototype {
    public abstract Prototype clone();
    }

    public class ConcretePrototype extends Prototype {
    private String attr; //成员变量
    public void setAttr(String attr) {
    this.attr = attr;
    }
    public String getAttr() {
    return this.attr;
    }
    //克隆方法
    public Prototype clone() {
    //创建新对象
    Prototype prototype = new ConcretePrototype();
    prototype.setAttr(this.attr);
    return prototype;
    }
    }
  • 客户端:

    1
    2
    3
    ConcretePrototype prototype = new ConcretePrototype();
    prototype.setAttr("Sunny");
    ConcretePrototype copy = (ConcretePrototype)prototype.clone();

通过Cloneable实现的浅克隆

  • 所有的Java类均继承自java.lang.Object类,Object类提供了一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的 clone()方法来实现对象的浅克隆。

  • 能够实现克隆的Java类必须实现一个标识接口 Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了 clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ConcretePrototype implements Cloneable {
    public Prototype clone() {
    Object object = null;
    try {
    object = super. clone(); //浅克隆
    }
    catch (CloneNotSupportedException exception) {
    System.err.println("Not support cloneable");
    }
    return (Prototype) object;
    }
    }
  • 客户端:

    1
    2
    Prototype protptype = new ConcretePrototype();
    Prototype copy = protptype.clone();

Java语言中的clone()方法满足以下几点:

  1. 对任何对象x,都有x.clone()!=x,即克隆对象与原型对象不是同一个对象
  2. 对任何对象x,都有x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样
  3. 如果对象x的equals()的方法定义恰当,那么x.clone().equals(x)应该成立。

为了获取对象的一个克隆,可以直接利用Object类的clone()方法,具体步骤如下:

  1. 在派生类中覆盖基类的clone()方法,并声明为public
  2. 在派生类的clone()方法中调用super.clone()
  3. 派生类需实现Cloneable接口。

此时,Object类相当于抽象原型类,所有实现了Cloneable接口的类相当于具体原型类。

通过序列化实现深克隆

  • 为了能够在复制周报的同时也能够复制附件对象,需要采用深克隆机制。在Java语言中可以通过序列化(Serialization)等方式来实现深克隆。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个复制,而原对象仍然存在于内存中。通过序列化实现的复制不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //使用序列化技术实现深克隆
    public WeeklyLog deepClone() throws IQException, ClassNotFoundException, OptionalDataException {
    //将对象写入流中
    ByteArrayOutputStream bao = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bao);
    oos.writeObject(this);
    //将对象从流中取出
    ByteArrayInputstream bis = new ByteArrayInputStrearn(bao.toByteArray());
    Objectinputstream ois = new ObjectInputStream(bis);
    return (WeeklyLog)ois.readObject();
    }

特点及使用环境

优点

  1. 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  2. 扩展性较好
  3. 提供了简化的创建结构,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
  4. 可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作

缺点

  1. 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  2. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦

适用环境

  1. 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
  2. 系统要保存对象的状态,而对象的状态变化很小
  3. 需要避免使用分层次的工厂类来创建分层次的对象

单例模式(Singleton)

定义

确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例

模式结构

结构图

image-20231228174602624

实现代码

  • 单例(Singleton)

    在单例类的内部创建它的唯一实例,并通过静态方法getlnstance()让客户端可以使用它的唯一实例;为了防止在外部对单例类实例化,将其构造函数的可见性设为private;在单例类内部定义了一个Singleton类型的静态对象作为供外部共享访问的唯一实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Singleton {
    //静态私有成员变量
    private static Singleton instance = null;
    //私有构造函数
    private Singleton() {
    }
    //静态公有工厂方法,返回唯一实例
    public static Singleton getInstance() {
    if(instance == null){
    instance = new Singleton();
    }
    return instance;
    }
    }
  • 客户端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Client {
    public static void main(String args[]) {
    Singleton s1 = Singleton.getlnstance();
    Singleton s2 = Singleton.getlnstance();
    //判断两个对象是否相同
    if (sl == s2) {
    System.out.printin("两个对象是相同实例");
    }
    else {
    System.out.printin("两个对象是不同实例");
    }
    }
    }

应用实例

​ 某软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高了系统的整体处理能力,缩短了响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。如何确保负载均衡器的唯一性是该软件成功的关键,试使用单例模式设计服务器负载均衡器。

image-20231228175207480

  • 单例:LoadBalancer

模式变化

饿汉式单例

image-20231228175330686

  • 当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。

    1
    2
    3
    4
    5
    6
    7
    public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getlnstance() {
    return instance;
    }
    }

懒汉式单例

image-20231228180547275

  • 懒汉式单例在第一次调用getlnstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton() { }
    //使用synchronized关键字对方法加锁,确保任意时刻只有一个线程可执行该方法
    synchronized public static LazySingleton getInstance() {
    if (instance = = null) {
    instance = new LazySingleton();
    }
    return instance;
    }
    }
  • 在上述懒汉式单例类中,在getlnstance()方法前面增加了关键字synchronized进行线程锁定,以处理多个线程同时访问的问题。上述代码虽然解决了线程安全问题,但是每次调用getlnstance()时都需要进行线程锁定判断,在多线程高并发访问环境中将会导致系统性能大大降低。

  • 假如在某一瞬间线程A和线程B都在调用getlnstance()方法,此时instance对象为null值,均能通过instance==null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定的代码。但当A执行完毕时线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背了单例模式的设计思想,因此需要进一步改进,在synchronized中再进行一次instance==null判断,这种方式称为双重检查锁定(Double-Check Locking)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static LazySingleton getlnstance() {
    //第一重判断
    if (instance == null) {
    //锁定代码块
    synchronized(LazySingleton.class) {
    //第二重判断
    if (instance == null) {
    //创建单例实例
    instance = new LazySingleton();
    }
    }
    }
    return instance;
    }

特点及使用环境

优点

  1. 提供了对唯一实例的受控访问
  2. 可以节约系统资源,提高系统的性能
  3. 允许可变数目的实例(多例类)

缺点

  1. 扩展困难(缺少抽象层)
  2. 单例类的职责过重
  3. 由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失

适用环境

  1. 系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
  2. 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例