4-2 面向复用的软件构造技术

目录

  • 设计可复用的类(重要)
    • 继承与重写
    • 重载
    • 参数多态与泛型编程
    • 行为子类型与Liskov替换原则
    • 组合和委托
  • 设计可复用库与框架
    • API和库
    • 框架
    • Java集合框架(一个例子)

阅读材料

  • CMU 17-214:Oct 10、Oct 15、Oct 17、Oct 22
  • Java编程思想:第14、15章 (重要
  • Effective Java:第5章
    阅读是学习的金钥匙

1、设计可复用的类

在OOP中设计可复用的类
  • 封装和信息隐藏
  • 继承和重写
  • 多态性、子类型和重载
  • 泛型程序设计
    (在3.4节中已经介绍了OOP,此节不再叙述)
  • 行为子类型与Liskov替换原则
  • 组合与委托
    (本节重点)

(1)行为子类型与Liskov替换原则

行为子类型
  • 子类型多态性:客户端代码可以统一处理不同类型的对象。
    • 如果类型Cat是Animal的子类型,那么在使用类型Animal的表达式的任何地方,都可以使用类型Cat的表达式
Animal a = new Animal(); 
Animal c1 = new Cat(); 
Cat c2 = new Cat();

(在声明变量时尽量使用父类型声明)
在可以使用a的场景,都可以用c1和c2代替而不会有任何问题

a = c1;
a = c2;
  • 假设q(x)是T类型的对象x可以证明的属性,那么q(y)应该可以证明S类型的对象y,其中S是T的子类型

——Barbara Liskov

在这里插入图片描述
MIT http://www.pmg.csail.mit.edu/~liskov

  • Java中编译器强制的规则(可以被静态类型检查)
    • 子类型可以增加方法,但不能删除方法
    • 具体类必须实现所有未定义的方法(子类型需要实现抽象 类型中的所有未实现方法 ):能做更多的事情
    • 重写方法必须返回相同的类型或子类型(子类型中重写的方法 必须有相同或子类型的返回值或者符合co-variance的参数 )
    • 重写方法必须接受相同的参数类型(子类型中重写的 方法必须使用同样类型的参数或者符合contra-variance的参数 )
    • 重写方法不能抛出其他异常(子类型中重写的方 法不能抛出额外的异常 )
  • 也适用于指定的行为(方法)(不可以被静态类型检查:程序员的负担):
    Liskov Substitution Principle (LSP)
    • 更强的不变量
    • 更弱的前置条件
    • 更强的后置条件
例1(行为子类型) (LSP)
  • 子类满足和父类相同的不变量(要完完整整继承下来),子类可以有新增的的不变量(新定义的属性可以增加RI)
  • 被重写的方法具有相同的前置和后置条件
    (另一方面,下面的例子还增加了子类型的方法)
    (父类型能干的事,子类型都能干)
    在这里插入图片描述
例2(行为子类型) (LSP)
  • 子类满足相同的不变量(以及附加的不变量)
  • 被重写的方法start的前置条件较弱
  • 被重写的方法brake的后置条件较强
    在这里插入图片描述
  • 这两个类怎么样?满足LSP吗?
    正方形不是长方形的子类型
    在这里插入图片描述

在这里插入图片描述
使得子类中更强的不变量(w==h)失效了
在这里插入图片描述
后置条件改变了(注意,不是变强了)
在这里插入图片描述
考试:A是不是B的一个好的子类型

Liskov Substitution Principle (LSP)
  • LSP是子类型关系的一个特殊定义,称为 (强)行为子类型化
  • 在编程语言中,LSP依赖于以下限制:
    • 前置条件不能强化
    • 后置条件不能弱化
    • 不变量要保持
    • 子类型方法参数:逆变
    • 子类型方法的返回值:协变
    • 子类型的方法不应该抛出任何新的异常,除非这些异常本身是超类型的方法抛出的异常的子类型。(这将在第7-2节中讨论)
协变 (Covariance)

父类型→子类型:越来越具体specific
返回值类型:不变或变得更具体
异常类型:不变或变得更具体

  • 看这个例子
    在这里插入图片描述
  • 更具体的类可能有更具体的返回类型
  • 这在子类型中称为返回类型的协变
    在这里插入图片描述
  • 为子类型的方法声明的每个异常都应该是为父类的方法声明的某个异常的子类型。
反协变、逆变 (Contravariance)

父类型→子类型:越来越具体specific
参数类型:要相反的变化,要不变或者越来越抽象

  • 你觉得这段代码怎么样?
    在这里插入图片描述
  • 从逻辑上讲,它在子类型中称为方法参数的逆变
  • 这在Java中实际上是不允许的,因为它会使重载规则变得复杂。(目前Java中遇到这种情况,当作overload看待)(编译器会报错)
  • 类型S的方法c(Object)必须重写或实现超类型方法
关于子类型和LSP的总结

在这里插入图片描述

协变和反协变
  • 数组是协变的:根据Java的子类型规则,T[]类型的数组可能包含T类型的元素或T的任何子类型。
    在这里插入图片描述
    Q:赋值调用的什么操作
  • 在运行时,Java知道这个数组实际上是作为整数数组实例化的,而整数数组只是通过类型为Number[]的引用进行访问。
  • 区分:一个对象的类型和引用的类型
    Q&A: 长方形不包含任何操作 ,可以有子类型正方形,
    注意:是行为子类型
    注意:继承尽量少用,他们的语义性很强
    注意:Java中不允许泛型数组
考虑泛型中的LSP

在这里插入图片描述
思考:传进去的是重载方法,参数又存在协变

  • 那么,在泛型中呢?
  • 泛型是类型不变量(<>里面的东西要一样)
    • ArrayList< String >是List< String >的子类型
    • List< String >是List< Object >的子类型
  • 类型参数的类型信息在代码编译完成后由编译器丢弃;因此,此类型信息在运行时不可用。
  • 这个过程称为类型擦除
  • 泛型不是协变的
什么是类型擦除?
  • 类型擦除:如果类型参数是无界的,则将泛型类型中的所有类型参数替换为它们的界限或对象。因此,生成的字节码只包含普通类、接口和方法。
    在这里插入图片描述
一个例子

在这里插入图片描述
这里会在编译时报错
在这里插入图片描述

我们不能认为整数列表是数字列表的子类型。
这对于类型系统来说是不安全的,编译器会立即拒绝它。

在这里插入图片描述

泛型中的LSP
  • Box< Integer >不是Box< Number >的子类型,即使Integer是Number的子类型。
    在这里插入图片描述

  • 给定两种具体泛型A和B(例如Number和Integer),MyClass< A >与MyClass< B >没有关系,不管A和B是否相关。MyClass< A >和MyClass< B >的共同父类是Object

  • 有关在类型参数相关时如何在两个泛型类之间创建类似子类型关系的知识,请参阅通配符

    • https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
泛型的通配符
  • 无界通配符类型是使用通配符(?)指定的,例如List<?>。(一般不用)
    • 这称为未知类型列表。
  • 有两种情况下,无界通配符是一种有用的方法:
    • 如果您正在编写一个可以使用Object类中提供的功能来实现的方法。
    • 当代码使用不依赖于类型参数的泛型类中的方法时。例如,List.size或List.clear。
    • 事实上,Class < ? >因为Class< T >中的大多数方法不依赖于T,所以经常使用。
      在这里插入图片描述
  • 下界通配符: <? super A>
    • – List< Integer > List<? super Integer>
    • 前者只匹配Integer类型的列表,而后者匹配Integer超类型(如Integer、Number和Object)的任何类型的列表。
  • 上界通配符:<? extends A>
    • List<? extends Number>
      在这里插入图片描述
考虑使用通配符的泛型的LSP

在这里插入图片描述
在这里插入图片描述

(2)委托和组合

一个排序的例子:

在这里插入图片描述
实现比较大小功能的类叫做比较因子

接口 Comparator< T >
  • int compare(T o1, T o2):为了排序而比较它的两个r参数。
    • 一个比较函数,它对一些对象集合强制执行总排序。
    • 比较器(Comparator)可以被传递到排序方法(例如 Collections.sort 或者 Arrays.sort)允许对排序顺序进行精确控制。比较器(Comparator)还可以用来控制某些数据结构的顺序(例如排序set或排序map),或者为没有自然排序的对象集合提供排序。
  • 如果你的ADT需要比较大小,或者要放入Collections或Arrays进行 排序,可实现Comparator接口并override compare()函数。
    在这里插入图片描述
  • 该接口对实现它的每个类的对象强制执行总排序。
  • 这种顺序称为类的自然排序,类的compareTo方法称为类的自然比较方法
  • 另一种方法:让你的ADT实现Comparable接口,然后override compareTo() 方法
  • 与使用Comparator的区别:不需要构建新的Comparator类,比较代 码放在ADT内部。
  • 这不再是委托。在这里插入图片描述
  • 一般通过委托方式来实现
委托
  • 委托是指一个对象依赖于另一个对象来实现其功能的某个子集(一个实体将某些东西传递给另一个实体)(委派/委托:一个对象请求另一个对象的功能 )
    • 例如,排序器(Sorter)正在把功能委托给某个比较器(Comparator)
  • 明智的委托支持代码重用(委派是复用的一种常见形式 )
    • 排序器(Sorter)可以与任意排序顺序一起重用
    • 比较器(Comparator)可以与需要比较整数的任意客户机代码重用。
  • 委托可以描述为在实体之间共享代码和数据的低级机制。
    • 显式委托:将发送对象传递给接收对象
    • 隐式委托:通过语言的成员查找规则
一个简单的委托例子

在这里插入图片描述

使用委托来扩展功能
  • 考虑java.util.List (我需要一个能log的List
    在这里插入图片描述
  • 假设我们需要一个将其操作记录打印到控制台的列表……
    • LoggingList由一个List组成,并将(非日志记录)功能委托给该列表。
      在这里插入图片描述
委托与继承
  • 继承:通过新操作或覆盖(overwrite)操作扩展基类。(有明确的父子关系才使用,Subtyping,满足Liskov原则,粗粒度,简单 )
  • 委托:捕获一个操作并将其发送给另一个对象。
  • 许多设计模式(design pattern)使用继承和委托的组合。
    在这里插入图片描述
    (委托是细粒度的操作,继承是粗粒度的操作)
  • Lab2的P2中,要复用P1中开发出的Graph完成社交网络相关功能
    在这里插入图片描述
    上图是委托
    在这里插入图片描述
    上图是继承
用委托代替继承
  • 问题:您有一个子类,它只使用它的超类的一部分方法(或者不可能继承超类数据)。(如果子类只需要复用父类中的一小部分方法 )
  • 解决方案:创建一个字段并在其中放置一个超类对象,将方法委托给超类对象,并消除继承。(可以不需要使用继承,而是通过委派机制来实现)
  • 本质上,这种重构将两个类分开,使超类成为子类的助手,而不是子类的父类。
    • 继承所有超类方法不同,子类将只将必要的方法来委托给超类对象的方法。(一个类不需要继承另一个类的全部方法,通过委托机制调用部分方法 )
    • 类不包含从超类继承的任何不需要的方法。(从而避免大量无用的方法)
      在这里插入图片描述
组合优于继承原则(Composite over inheritance principle)
  • 或称为组合重用原则(CRP)
    • 类应该通过它们的组合(通过包含实现所需功能的其他类实例)来实现多态行为和代码重用,而不是从基类或父类继承。
    • 最好是组合一个对象可以做什么(has_a或use_a),而不是扩展它是什么(is_a)。
  • 委托可以被看作是对象级(Object level)的重用机制,而继承则是类级(Class level)的重用机制。(“委托” 发生在object层面,而“继承”发生在class层面)
    (鸟的例子:鸵鸟不会飞)
CRP的例子
  • Employee类有一个计算雇员年度奖金的方法:
    在这里插入图片描述
  • 员工的不同子类:Manager(经理)、Programmer(程序员)、Secretary(秘书)等可能希望覆盖此方法,以反映某些类型员工比其他类型员工能获得更丰厚奖金的情况
    在这里插入图片描述
  • 这个解决方案有几个问题:
    • 所有Manager对象都得到相同的奖励。如果我们想在不同的经理之间改变奖金的计算方法呢?——引入一个特殊的Manager子类?
      在这里插入图片描述
    • 如果我们想要改变某个特定员工的奖金计算方法呢?例如,如果我们想把史密斯从Manager提升到SeniorManager,该怎么办?
    • 如果我们决定给所有经理和程序员一样的奖金呢?我们应该把计算奖金的方法从程序员复制并粘贴到经理?(同一份代码出现了两次,复杂继承树没有任何意义,只有一个方法改变了,尽量不要形成复杂继承树)
    • 把雇员史密斯的工资提高到管理者
  • 问题是:每个雇主的奖金计算方法可能不同,而且经常变动。
    • 员工和奖金calculator 之间的关系应该在对象级别,而不是类级别。(核心问题:每个Employee对象的奖金计算方法都不同,在object层面而非class层面。
  • A CRP 解决方案:
    在这里插入图片描述
CRP 的例子: 更一般的设计

在这里插入图片描述
在这里插入图片描述

组合优于继承原则——更普适的
  • 一个假象的场景:
    • 你要开发一套动物ADT,各种不同种类的动物,每类动物有自己的独特 “行为”,某些行为可能在不同类型的动物之间复用。
    • 考虑到生物学和AI的进展,动物的“行为”可能发生会发生变化;
  • 例如:
    • 行为:飞、叫、…
    • 动物:既会飞又会叫的鸭子、天鹅;不会飞但会叫的猫、狗;…
    • 有10余种“飞”的方式、有10余种“叫”的方式;而且持续增加
      在这里插入图片描述
  • 直接面向具体类型动物的编程:类
  • 缺陷:存在大量的重复;不易变化

在这里插入图片描述

通过inheritance实现对某些通用行为的复用 缺点:需要针对“飞法”设计复杂的继承关系树;不能 同时支持针对“叫法”的继承;动物行为发生变化时, 继承树要随之变化。

  • 组合而非继承的实现通常从创建表示系统必须显示的行为的各种接口开始。
  • 实现所标识的接口的类将根据需要构建并添加到业务域类中。
  • 因此,系统行为是在没有继承的情况下实现的。
  • 使用接口定义系统必须对外 展示的不同侧面的行为
  • 接口之间通过extends实现 行为的扩展(接口组合)
  • 类implements 组合接口
  • 从而规避了复杂的继承关系
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    委托的类型
  • Use (A use B)
  • Association (A has B)
  • Composition/aggregation (A owns B)
    • 可以认为Composition/Aggregation是Association的两种具体形态
  • 这种分类是根据委托和委托方之间的“耦合程度”进行的。

在这里插入图片描述

(1)Dependency: 临时性的委托
  • 使用类的最简单形式是调用它的方法;
  • 两个类之间的这种形式的关系称为“use -a”关系,在这种关系中,一个类使用另一个类,而实际上不将其作为属性合并。例如,它可以是一个参数,或者在方法中局部使用。
  • 依赖关系:一个对象需要其他对象(供应商)来实现它们的临时关系。

在这里插入图片描述

(2)Association: 永久性的委托
  • 关联:对象的类之间的持久关系,允许一个对象实例使另一个对象实例代表它执行一个操作。
    • has_a:一个类有另一个作为属性/实例变量
    • 这种关系是结构性的,因为它指定一种对象连接到另一种对象,并不代表行为。
      在这里插入图片描述
(3)Composition: 更强的association,但难以变化
  • 组合是一种将简单对象或数据类型组合成更复杂对象或数据类型的方法。
    • is_part_of:一个类有另一个作为属性/实例变量
    • 实现这样一个对象包含另一个对象。
      在这里插入图片描述
(4) Aggregation: 更弱的association,可动态变化
  • 聚合:对象存在于其他对象之外,是在外部创建的,因此它作为参数传递给构造者。
    • has_a
      在这里插入图片描述
组合 vs. 聚合

在这里插入图片描述

委托的类型
  • Use (A use one or multipleB)
  • Association (A has one or multiple B)
  • Composition/aggregation (A owns one or multiple B)
  • 都支持1对多的delegation——
    在这里插入图片描述

2* 设计系统级可重用库和框架

  • 库:一组提供可重用功能的类和方法(api)

在这里插入图片描述

框架
  • 框架:可定制到应用程序中的可重用框架代码
  • 框架调用回客户端代码
    • 好莱坞原则:“不要调用我们。我们会打电话给你。”
      在这里插入图片描述
一般区别:库与框架

在这里插入图片描述

(1) API design

为什么API设计很重要?
  • 如果你编程,你就是一个API设计者,而API可以是你最大的资产之一( API是程序员最重要的资产和“荣耀”,吸引外部用户,提高声誉 )
    • 好的代码是模块化的——每个模块都有一个API
    • 用户投入巨大:获取、写作、学习
    • 从api的角度考虑可以提高代码质量
    • 成功的公共api捕获用户
  • 建议:始终以开发API的 标准面对任何开发任务
  • 面向“复用”编程 而不是面向“应用”编程
  • 也可能是你最大的负债之一
    • 糟糕的API会导致源源不断的支持调用流
    • 能抑制前进的能力
  • 公共api是永远的——一个让它正确的机会
    • 一旦模块有用户,就不能随意改变API
  • 难度:要有足够良好的设 计,一旦发布就无法再自 由改变

(2) Framework design

一个例子: web browser plugin

在这里插入图片描述

白盒和黑盒框架
  • 白盒框架
    • 通过子类化和覆盖方法进行扩展
    • 常见的设计模式:模板模式
    • 子类有主方法,但给框架控制
  • 黑盒框架
    • 通过实现插件接口进行扩展
    • 常见的设计模式:策略模式观察者模式
    • 插件加载机制加载插件并对框架进行控制
一个计算器的例子 (不用框架)

在这里插入图片描述

一个简单的白盒框架

在这里插入图片描述

使用白盒框架

在这里插入图片描述

一个简单的黑盒框架

在这里插入图片描述

使用黑盒框架

在这里插入图片描述

通过实现插件接口插件加载机制进行扩展,加载插件并将控制权交给框架

另一个白盒框架的例子

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

白盒与黑盒框架
  • 白盒框架使用子类化/子类型化——继承
    • 允许扩展每一个非私有的方法
    • 需要了解超类的实现
    • 一次只有一个扩展
    • 编译在一起
    • 通常是所谓的开发人员框架
  • 黑盒框架使用组合 – 委派/组合
    • 允许在接口中公开功能的扩展
    • 只需要了解接口
    • 多个插件
    • 通常提供更多的模块化
    • 独立部署可能 (.jar, .dll, …)
    • 通常被称为终端用户框架、平台
      在这里插入图片描述
框架设计注意事项
  • 一旦设计出来,就几乎没有改变的机会
  • 关键决策:将通用部件与可变部件分离
    • 你想解决什么问题?
  • 可能出现的问题
    • 扩展点太少:局限于狭窄的用户类别
    • 扩展点太多:难学、慢
    • 太泛:重用价值很小
      “最大化重用最小化使用”
进化设计:提取共性
  • 提取接口是进化设计的一个新步骤:
    • 抽象类是从具体类中发现的
    • 接口是从抽象类中提取的
  • 一旦架构稳定就开始
    • 从类中移除非公共方法
    • 将默认实现移动到实现接口的抽象类中
运行一个框架
  • 有些框架可以自己运行
    • e.g. Eclipse
  • 必须扩展其他框架才能运行
    • Swing, JUnit, MapReduce, Servlets
  • 加载插件的方法:
    • 客户端写main(),创建一个插件并把它传递给框架
    • 框架写main(),客户端把插件的名字作为一个命令行参数或环境变量传递
    • 框架寻找一个神奇的位置,然后配置文件或.jar文件被自动加载和处理。
示例:一个Eclipse插件
  • Eclipse是一个流行的Java IDE
  • 更一般地说,是一个工具框架,用于促进“跨生命周期构建、部署和管理软件”。
  • 基于OSGI标准的插件框架
  • 起始点:清单文件
    • Plugin name
    • Activator class
    • Meta-data
      在这里插入图片描述
例子 : A JUnit Plugin

在这里插入图片描述

学习一个框架
  • 文档
  • 教程、向导和示例
  • 其他客户端应用程序和插件
  • 社区、电子邮件列表和论坛
    在这里插入图片描述

(3)Java集合框架

什么是集合和集合框架?
  • 集合:对元素进行分组的对象
  • 主要用途:数据存储和检索,数据传输
    • 熟悉的例子:java.util.Vector,java.util.Hashtable,Array
  • 集合框架:一个统一的架构
    • 接口-独立实现
    • 实现-可重用的数据结构
    • 算法-可重用的功能
架构总述
  • 核心集合接口
  • 通用的实现
  • 包装器实现
  • 抽象实现
  • 算法
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Collection Interface

在这里插入图片描述

Iterator Interface
  • 替换枚举接口
    • 添加删除方法
    • 改善方法名称
      在这里插入图片描述
Set Interface
  • 不向集合添加任何方法!
  • 附加规定:无重复元素
  • 强制等于和hashCode计算
    在这里插入图片描述
  • Set Idioms:

在这里插入图片描述

List接口:一个对象的序列

在这里插入图片描述

List Example
  • 可重用算法交换和随机:
    在这里插入图片描述
  • List Idioms
    在这里插入图片描述

Map接口:键值映射
在这里插入图片描述

Map Idioms

在这里插入图片描述

通用的目的实现
  • 一致的命名和行为

在这里插入图片描述

选择一个实现
  • Set
    • HashSet - O(1)访问,无顺序保证
    • TreeSet - O(log n)访问,排序
  • Map
    • HashMap – (See HashSet)
    • TreeMap – (See TreeSet)
  • List
    • ArrayList – O(1) random access, O(n) insert/remove
    • LinkedList – O(n) random access, O(1) insert/remove;
      • 用于队列和deques(不再是一个好主意!)
不可变包装器
  • 匿名实现
  • 静态工厂方法
  • 每个核心接口一个
  • 提供只读访问-不可变!
  • 我们已经在第3章讨论了这些包装
同步包装器
  • 非线程安全
  • 同步包装器:线程安全的新方法
    • 匿名实现,每个核心接口一个
    • 静态工厂采用适当类型的集合
    • 线程安全保证,如果所有访问都通过包装
    • 必须手动同步迭代
  • 那时它还是新的;现在老了!
    • 同步包装器在很大程度上已经过时了
    • 由于并发收集而变得过时
  • 将在第七章讨论
同步包装的例子

在这里插入图片描述

便利实现
  • Arrays.asList(E[] a)
    • 允许数组被“查看”为列表
    • 过渡到基于集合的api
  • EMPTY_SET, EMPTY_LIST, EMPTY_MAP
    • 不变的常量
  • singleton(E o)
    • 指定对象的不可变集合
  • nCopies(E o)
    • 具有n个对象副本的不可变列表
可复用的算法

在这里插入图片描述

例1:对可比较元素的列表进行排序

在这里插入图片描述

算法示例2:使用比较器排序

在这里插入图片描述

兼容性
  • 旧的和新的集合可以自由地互操作
  • 向上兼容性
    • Vector< E > implements List< E >
    • Hashtable<K,V> implements Map<K,V>
    • Arrays.asList(myArray)

新版本的特性,使它能够与更新的或更强大的版本一起工作

  • 向后兼容性
    • myCollection.toArray()
    • new Vector<>(myCollection)
    • new Hashtable<>(myMap)

允许与旧版本互操作的版本的属性

Java集合的演化

在这里插入图片描述

总结

  • 设计可重用的类
    • 继承和重写
    • 重载
    • 参数多态性和泛型编程-
    • 行为子类型和Liskov代换原理(LSP)
    • 组合和委托
  • 设计系统级可重用库和框架
    • API和库
    • 框架
    • Java集合框架(示例)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面向对象是一种程序设计的思想,它将程序中的数据和对数据的操作封装在一起,形成对象。对象是类的一个实例,类定义了对象的属性和行为。在Java中,面向对象的概念包括类与对象的关系、封装、构造函数、this关键字、static关键字以及设计模式等方面。 设计模式是在软件设计中常用的解决问题的经验总结,它提供了一套可重用的解决方案。在Java中,单例设计模式是一种常见的设计模式之一,它保证一个类只有一个实例,并提供一个全局访问点。通过使用单例设计模式,可以确保在程序中只有一个对象实例被创建,从而节省了系统资源并提高了性能。 通过使用单例设计模式,可以实现以下效果: - 限制一个类只能有一个实例。 - 提供一个全局访问点,使其他对象可以方便地访问该实例。 - 保证对象的唯一性,避免多个对象的状态不一致。 在Java中,实现单例设计模式有多种方式,包括饿汉式、懒汉式、双重检测锁等。每种方式都有各自的特点和适用场景,开发者可以根据具体的需求选择合适的实现方式。设计模式是一种通用的解决问题的方法,它可以在面向对象的程序设计中提供灵活、可复用的解决方案。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [计算机后端-Java-Java核心基础-第15章 面向对象07 14. 设计模式与单例设计模式.avi](https://download.csdn.net/download/programxh/85435560)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [完整版Java全套入门培训课件 Java基础 03-面向对象(共18页).pptx](https://download.csdn.net/download/qq_27595745/21440470)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值