面向对象基本概念
面向对象(Object-Oriented,00) = 对象(Object)+ 分类(Classification)+ 继承(Inheritance)+ 通过消息的通信(Communication with Messages)
对象
在面向对象的系统中,对象是基本的运行时的实体,它包括:
- 属性:数据;
- 行为:作用于数据的操作。
一个对象把属性和行为封装为一个整体。
封装是一种信息隐蔽技术,它的目的是使对象的使用者和生产者分离,使对象的定义和实现分开。
对象可以看作是一个程序模块。
一个对象通常由以下成分组成:
- 对象名;
- 属性;
- 方法。
消息
对象之间进行通信的一种构造叫件消息。
类
类可以分为三种:
-
实体类:实体类的对象表示现实世界中真实的实体;
-
接口类:接口类的对象为用户提供一种与系统合作交互的方式,分 为人和系统两大类(边界类);
- 人的接口可以是显示屏、窗口、Wb窗体、对话框、菜单、列表框、其他显示控制、条形码、二维码或者用户与系统交互的其他方法。
- 系统接口涉及到把数据发送到其他系统,或者从其他系统接收数据。
-
控制类:控制类的对象用来控制活动流,充当协调者。
继承
继承是父类和子类之间共享数据和方法的机制。这是类之间的一种关系,在定义和实现一个类的时候,可以在一个已经存在的类的基础上进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
一个父类可以有多个子类。所以继承又分为:
-
单重继承:只从一个父类得到继承;
-
多重继承:一个子类有两个或更多个父类。
多重继承中可能导致子类存在二义性的成员。
多态
多态(Polymorphism):不同的对象收到同一消息可以产生完全不同的结果。
同一消息就可以调用不同的方法。多态的实现受到继承的支持,利用类的继承的层次关系,把具有通用功能的消息存放在高层次,而不同的实现这一功能的行为放在较低层次,在这些低层次上生成的对象能够给通用消息以不同的响应。
多态有不同的形式:
- 通用的多态:
- 参数多态:应用比较广泛,被称为最纯的多态;
- 包含多态:在许多语言中都存在,最常见的例子就是子类型化。即一个类型是另一个类型的子类型。
- 特定的多态:
- 过载(Overloading)多态:同一个名字在不同的上下文中所代表的含义不同;
- 强制多态。
绑定
绑定是一个把过程调用和响应调用所需要执行的代码加以结合的过程。
绑定分为:
-
静态绑定:在编译时进行的;
-
动态绑定:在运行时进行的。
一个给定的过程调用和代码的结合直到调用发生时才进行。
动态绑定是和类的继承以及多态相联系的。在继承关系中,子类是父类的一个特例,所以父类对象可以出现的地方,子类对象也可以出现。
在运行过程中,当一个对象发送消息请求服务时,要根据匿收对象的其体情况将请求的操作与实现的方法进行连接,即动态绑定。
面向对象分析
同其他分析方法一样,面向对象分析(Object-Oriented Analysis,OOA)的目的是为了获得对应用问题的理解。
面向对象分析包含5个活动:
- 认定对象;
- 组织对象;
- 描述对象间的相互作用;
- 确定对象的操作
- 定义对象的内部信息。
认定对象
在应用领域中,按自然存在的实体确立对象。
在定义域中,首先将自然存在的“名词”作为一个对象,这通常是研究问题、定义域实体的良好开始。
组织对象
分析对象间的关系,将相关对象抽象成类,其目的是为了简化关联对象,利用类的继承性建立具有继承性层次的类结构。
抽象类时可从以下方面考虑:
- 对象间的操作;
- 一个对象是另一个对象的一部分,如房子是由门和窗构成的。
面向对象设计
程序设计范型(Programming Paradigm)是人们在程序设计时所采用的基本方式模型,决定了程序设计时采用的思维方式、使用的工具,同时又有一定的应用范畴。
程序设计范型的发展经历了:
- 过程程序设计;
- 模块化程序设计;
- 函数程序设计;
- 逻辑程序设计;
- 面向对象程序设计范型。
面向对象程序设计(Object-Oriented Programming,OOP)的实质是选用一种面向对象程序设计语言(Object-Oriented Programming Language,OOPL):
- 采用对象、类及其相关概念所进行的程序设计;
- 关键在于加入了类和继承性,从而进一步提高了抽象程度。
特定的OOP概念一般是通过OOPL中特定的语言机制来体现的。
OOP现在已经扩展到系统分析和软件设计的范畴,出现了面向对象分析和面向对象设计的概念。
设计原则
面向对象方法中的五大原则:
-
责任原则(Single Responsibility Principle,SRP):当需要修改某个类的时候原因有且只有一个,让一个类只做一种类型责任。
-
开放封闭原则(Open & Close Principle,OCP):软件实体(类、模块、函数等)应 该可以扩展的,即开放的;但是不可修改的,即封闭的。
-
里氏替换原则(Liskov Substitution Principle,LSP):子类型必须能够替换掉他们的基 类型。
即,在任何父类可以出现的地方,都可以用子类的实例来赋值给父类型的引用。
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有是一个(is-a)关系。
-
依赖倒置原则(Dependence Inversion Principle,DP):抽象不应该依赖于细节,细 节应该依赖于抽象。即,高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
-
接口分离原则(Interface Segregation Principle,ISP):不应该强迫客户依赖于它们不 用的方法。接口属于客户,不属于它所在的类层次结构。
即:依赖于抽象,不要依赖于具体,同时在抽象级别不应该有对于细节的依赖。
这样做的好处就在于可以最大限度地应对可能的变化。
Robert C. Martin提出的面向对象设计原则还包括以下几个:
- 重用发布等价原则(Release Reuse Equivalency Principle,REP):重用的粒度就是发布的粒度。
- 共同封闭原则(Common Closure Principle,CCP):包中的所有类对于同一类性质的变化应该是共同到闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
- 共同重用原则(Common Reuse Principle,CRP):一个包中的所有类应该是共同重用 的。如果重用了包中的一个类那么就要重用包中的所有类。
- 无环依赖原则(Acyclic Dependencies Principle,ADP):在包的依赖关系图中不允许存 在环,即包之间的结构必须是一个直接的五环图形。
- 稳定依赖原则(Stable Dependencies Principle,SDP):朝着稳定的方向进行依赖。
- 稳定抽象原则(Stable Abstractions Principle,SAP):包的抽象程度应该和其稳定程 度一致。
UML
面向对象分析强调的是对一个系统中对象的特征和行为的定义。目前,国际上已经出现了多种面向对象的方法,例如:
- Peter Coad和Edward Yourdon的OOA和OOD方法(又称Coad/Yourdon方法或Coad方法);
- Booch的OOD方法(又称Booch方法);
- OMT(Object Modeling Technique,面向对象建模技术)方法;
- UML(Unified Modeling Language,统一建模语言)。
UML是面向对象软件的标准化建模语言。由于其简单、统一,又能够表达软件设计中的动态和静态信息,目前己经成为可视化建模语言事实上的工业标准。
UML中包含3种基本构造块:
- 事物;
- 关系;
- 图。
事物
UML中有4种事物:
-
结构事物(Structural Thing):是UML模型中的名词。它们通常是模型的静态部分,描述概念或物理元素。
结构事物包括:
-
类(Class);
-
接口(Interface);
-
协作(Collaboration);
-
用例(Use Case);
-
主动类(Active Class);
-
构件(Component);
-
制品(Artifact);
-
结点(Node);
……
-
-
行为事物(Behavior Thing):是UML模型的动态部分。它们是模型中的动词,描述了跨越时间和空间的行为。
行为事物包括:
-
交互(Interaction);
-
状态机(State Machine);
-
活动(Activity);
……
-
-
分组事物(Grouping Thing):UML模型的组织部分,是一些由模型分解成的“盒子”。
在所有的分组事物中,最主要的分组事物是包(Package)。包是把元素组织成组的机制,这种机制具有多种用途。结构事物、行为事物甚至其他分组事物都可以放进包内。
包与构件(仅在运行时存在)不同,它纯粹是概念上的(即它仅在开发时存在)。
-
注释事物(Annotational Thing):注释事物是UML模型的解释部分。这些注释事物用来描述、说明和标注模型的任何元素。
注解(Note)是一种主要的注释事物。注解是一个依附于一个元素或者一组元素之上,对它进行约束或解释的简单符号。
关系
UML中有4种关系,这4种关系是UML模型中可以包含的基本关系事物:
-
依赖(Dependency):是两个事物间的语义关系,其中一个事物(独立事物)发生变化会影响另一个事物(依赖事物)的语义。
在图形上,把一个依赖画成一条可能有方向的虚线,如图:
依赖的变体有:
- 精化;
- 跟踪;
- 包含;
- 延伸。
依赖有偶然性和临时性,即需要的时候依赖,不需要的时候不依赖。
-
关联(Association):是一种结构关系,它描述了一组链,链是对象之间的连接。
关联使用实线表示,在关联上可以标注重复度(Multiplicity)和角色(Role)。
描述了整体和部分间的结构关系的特殊类型的关联:
-
聚集(Aggregation,聚合):部分和整体的生命周期不一致,整体消失了,部分仍然存在,部分可以脱离整体存在。
使用一端带空心菱形的实线表示。
-
组合:部分和整体的生命周期一致,整体消失了,部分也消失了,部分不可以脱离整体而存在。
使用一端带实心菱形的实线表示。
关联和聚集的图形化表示如图:
聚合和组合中,带菱形的一端指向整体,另一端指向部分。
单向关联:用带箭头实线表示。
关联的关系强度比依赖的关系强度要强一点。
多重度:
进行面向对象设计时,类图中可以展现类之间的关联关系,还可以在类图中图示关联中的数量关系,即多重度。表示数量关系时,用多重度说明数量或数量范围,表示有多少个实例(对象)能被连接起来,即一个类的实例能够与另一个类的多少个实例相关联。
关联类:
当两个类之间的关联的重复度是多对多时,需要借助额外的属性来帮助表达它们之间的关系,而这个属性就需要定义在一个新的关联类中。关联类记录了这两个类之间的关联信息。关联中这些额外的属性用一条垂直于关联的实线表示,实线的一段连接接到关联的实线上,另一端指向这些属性。
-
-
泛化(Generalization):是一种特殊/一般关系,特殊元素(子元素)的对象可替代一般元素(父元素)的对象。用这种方法,子元素共享了父元素的结构和行为。
在图形上,把一个泛化关系画成一条带有空心箭头的实线,它指向父元素:
-
实现(Realization):是类元之间的语义关系,其中一个类元指定了由另一个类元保证执行的契约。
在两种情况下会使用实现关系:
- 在接口和实现它们的类或构件之间:
- 在用例和实现它们的协作之间。
在图形上,把一个实现关系画成一条带有空心箭头的虚线,箭头指向模板类,另一端于实现类连接。
UML中的图
UML2.0提供了13种图,分别是(加粗是重点,其它的作为了解):
- 类图
- 对象图
- 用例图
- 状态图
- 活动图
- 构件图
- 组合结构图
- 部署图
- 包图
- 交互图:
- 序列图
- 通信图
- 交互概览图
- 计时图
类图
类图(Class Diagram)展现了一组对象、接口、协作和它们之间的关系。在面向对象系统的建模中所建立的最常见的图就是类图。类图给出系统的静态设计视图,包含主动类的类图(少见)给出了系统的静态进程视图。
类图中通常包括下述内容:
-
类:
类中的方法和属性前面有以下三种修饰符:
修饰符 含义 +
public
公有的-
private
私有的#
protected
受保护的~
package
包的例如:
Student - id : int
+ name : String
+ age : int
+ getId() : int
-
接口:
-
协作:
-
依赖、泛化和关联关系:
类图中也可以包含:
- 注解和约束;
- 包或子系统。
类图成分总结:
名称 | 图示 |
---|---|
类 | |
接口 | |
协作 | |
依赖 | |
泛化 | |
关联 |
类图用于对系统的静态设计视图建模,这种视图主要支持系统的功能需求,通常以下述3种方式之一使用类图:
-
对系统的词汇建模:
涉及做出这样的决定:
- 哪些抽象是考虑中的系统的一部分;
- 哪些抽象处于系统边界之外。
用类图详细描述这些抽象和它们的职责。
-
对简单的协作建模:协作是一些共同工作的类、接口和其他元素的群体,该群体提供的一些合作行为强于所有这些元素的行为之和。
-
对逻辑数据库模式建模:将模式看作为数据库的概念设计的蓝图。在很多领域中,要在关系数据库或面向对象数据库中存储永久信息,可以用类图对这些数据库的模式建模。
对象图
对象图(Object Diagram)展现了某一时刻一组对象以及它们之间的关系,描述了在类图中所建立的事物的实例的静态快照。
对象图一般包括对象和链,如图:
对象:对象图中的对象包含了类名、对象名和属性。
其图形化如下:
对象名 : 类名 |
---|
|
匿名对象(没有对象名):
: 类名 |
---|
|
和类图一样,对象图给出系统的静态设计视图或静态进程视图,但它们是从真实的或原型实例的角度建立的。
用例图
用例图(Use Case Diagram)展现了一组用例、参与者(Actor)以及它们之间的关系。
用例图通常包括以下内容:
-
用例:是从用户角度描述系统的行为,它将系统的一个功能描述成一系列的事件,这些事件最终对操作者产生有价值的观测结果。
用例是一个类,它代表一类功能而不是使用该功能的某一具体实例。
-
参与者:是与系统交互的外部实体,可能是使用者,也可能是与系统交互的外部系统、基础设备等。
参与者用一个人形图标表示。
-
关系:
-
用例之间的关系:
-
包含关系:用带
<<include>>
的虚线箭头表示,如: -
扩展关系:用带
<<extend>>
的虚线箭头表示,如:扩展用例是指,一个用例中,符合某些特定情况才会触发的另一个用例。
即一个用例执行的时候,可能会发生一些特殊情况或可选情况,这种情况就是这个用例的扩展用例。
-
-
参与者和用例之间的关联关系。
-
用例与用例以及参与者与参与者之间的泛化关系。
-
用例图成分总结:
名称 | 图示 |
---|---|
用例 | |
包含关系 | |
扩展关系 | |
泛化关系 |
交互图
交互图用于对系统的动态方面进行建模。一张交互图表现的是一个交互,由一组对象和它们之间的关系组成,包含它们之间可能传递的消息。
每种交互图针对不同的目的,能适用于不同的情况。交互图表现为:
- 序列图:强调消息时间顺序的交互图;
- 通信图:强调接收和发送消息的对象的结构组织的交互图;
- 交互概览图:强调控制流的交互图;
- 计时图。
其中顺序图和通信图是同构的,它们之间可以相互转换。
在多数情况下,交互图包括对以下内容的具体的或原型化的实例以及它们之间传递的消息进行建模:
- 类;
- 接口;
- 构件;
- 结点。
所有这些都位于一个表达行为的脚本的语境中。
交互图一般包含:
-
对象;
-
链;
-
消息
……
序列图
序列图(Sequence Diagram,顺序图)是场景(Scenario)的图形化表示,描述了以时间顺序组织的对象之间的交互活动,强调消息时间顺序。
序列图的组成:
-
把参加交互的对象放在图的上方,沿水平方向排列。
通常把发起交互的对象放在左边,下级对象依次放在右边。
-
把这些对象发送和接收的消息,沿垂直方向,按时间顺序从上到下放置。
序列图的组成部分:
-
对象:用方框框起来的
对象名:类名
,没有属性和方法等成分。 -
对象生命线:表示一个对象存在的时间段。
如上图中对象下方垂直的虚线。
- 对象可以在交互过程中创建:生命线从接收到构造型
create
消息开始; - 也可以在交互过程中撤销:生命线从接收到构造型
destroy
消息结束,并且给出一个大$\times$的标记表明生命线的结束。
- 对象可以在交互过程中创建:生命线从接收到构造型
-
控制焦点:控制焦点表示一个对象执行一个动作所经历的时间段。
如上图中对象下方的空表矩形条框。
-
消息:
普通的消息用带箭头的实线表示。所有消息的箭头都是指向接收对象。
- 返回消息:用带箭头的虚线表示。
- 同步消息(调用消息):指消息发送给接收对象后,需要等待接收对象返回后才可进行下一步操作。
- 异步消息:指消息发送给接收对象后,无需等待接收对象返回即可进行下一步操作。
序列图有两个不同于通信图的特性:
- 序列图有对象生命线;
- 序列图有控制焦点。
通信图
通信图(Communication Diagram,协作图)强调收发消息的对象的结构组织。
通信图的组成:
- 将参加交互的对象作为图的顶点;
- 把连接这些对象的链表示为图的弧;
- 用对象发送和接收的消息来修饰这些链。
这就提供了在协作对象的结构组织的语境中观察控制流的一个清晰的可视化轨迹。
通信图有以下成分:
-
对象:与序列图一样,是用一个方框框起来的
对象名:类名
。 -
路径(链接):用实线表示,可以在链的末端附上一个路径构造型。
通常仅需显式地表示以下几种链的路径:
构造型 含义 <<local>>
局部 <<parameter>>
参数 <<global>>
全局 <<self>>
自身 不必表示
association
(关联)。 -
序号:用来表示消息的时间顺序。是消息前的一个数字前缀,可使用带小数点的号码表示嵌套消息,嵌套可为任意深度。
如2表示第2个消息,2.1表示嵌套在消息2中的第1个消息。
-
消息:沿同一个链可以显示许多消息(可能发自不同方向),并且每个消息都有唯一的序号。
通信图有两个不同于序列图的特性:
- 通信图有路径;
- 通信图有序号。
总结
顺序图和通信图是同构的,它们之间可以相互转换。它们的差异如下:
差异 | ||
---|---|---|
强调 | ||
不同的特性 |
|
|
状态图
状态图(State Diagram,状态转换图)展现了一个状态机。状态图关注系统的动态视图,对于接口、类和协作的行为建模尤为重要,强调对象行为的事件顺序。
状态图由以下组成:
-
状态:指对象的生命周期中某个条件或者状态,是任何可以被观察到的系统行为模式,一个状态代表系统的一种行为模式。
状态规定了系统内对事件的响应方式。
系统对事件的响应:
- 可以是做一个(或一系列)动作;
- 可以是仅仅改变系统本身的状态;
- 可以是即改变状态,又做动作。
状态转换图中定义的状态主要有:
- 初态(初始状态):用一个实心圆点表示。一张状态图只能有一个初态。
- 终态(最终状态):用一个实心圆点外加一个圆圈表示。一张状态图可以没有终态,也可以有多个。
- 中间状态。
状态图中的状态用一个圆角矩形表示,可以用两条水平横线将其分为上中下3个部分:
- 上面部分(必须):状态的名称;
- 中间部分(可选):状态变量的名称和值;
- 下面部分(可选):活动表。
状态还可分为:
-
简单状态。
-
组合状态:含有子状态的状态,这个状态也称为其子状态的超状态。
子状态:嵌套在另外一个状态中的状态。
-
转换(迁移):是两个状态之间的一种关系,表示对象将在源状态中执行一定的动作,并在某个特定事件发生,而且某个特定的警界(监护)条件满足时进入目标状态。
状态转换用一条带箭头的实线表示。
-
事件:是在某个特定时刻发生的事情,它是对引起系统做动作或(和)从一个状态转换到另个状态的外界事件的抽象。
-
事件触发状态转换:状态变迁通常是由事件触发的。状态之间带箭头实线上的事件发生时,状态转换开始(还可称之为状态“点火”或状态被“触发”)。
这种情况下应在表达状态转换的箭头线上标出触发转换的事件表达式:
事件说明 [守卫条件] / 动作表达式
事件说明的语法为:
事件名 (参数表)
守卫条件(监护条件):一个布尔表达式。
- 当且仅当事件发生且守卫条件为真时,状态转换才发生;
- 只有守卫条件没有事件说明时,只要守卫条件为真,状态转换就发生。
动作表达式是一个过程表达式,当状态转换(事件)开始时执行。
-
自动触发状态转换:如果箭头线上未标明事件,则表示在源状态的内部活动执行完之后自动触发转换。
-
-
活动:指状态中的活动表中的活动。
语法如下:
事件名 (参数表) /动作表达式
事件名:可以是任何事件的名称。
在活动表中经常使用以下3中标准事件:
事件名 含义 entry
入口动作,指定进入该状态的动作,立即执行 exit
出口动作,指定退出该状态的动作,立即执行 do
内部活动,指定在该状态下的动作,占有有限时间,并可中断地工作 活动(动作)可以在状态内执行,也可以在状态转换(迁移)时执行。
可以用状态图对系统的动态方面建模。这些动态方面可以包括出现在系统体系结构的任何视图中的任何一种对象的按事件排序的行为,这些对象包括:
- 类(各主动类);
- 接口;
- 构件;
- 结点。
当状态图对系统、类或用例的动态方面建模时,通常是对反应型对象建模。
活动图
活动图(Activity Diagram)是一种特殊的状态图,它展现了在系统内从一个活动到另一个活动的流程。活动图专注于系统的动态视图,它对于系统的功能建模特别重要,并强调对象间的控制流程。
活动图一般包括:
-
状态:
活动图的状态也包含初态和终态。其余的状态还可分为:
- 动作状态:不能被分解,动作不能被中断。
- 活动状态:能够被进一步分解,可以被中断,其活动由其它的活动图来表示。
-
流(转换)。
-
对象。
活动图可以表示:
- 分支(判断):分支的流上用
[]
标记的是监护表达式; - (并发)分岔:将一个流分为多个可并发执行的流;
- (并发)汇合:将分岔出去的多个流合并为同一个流。
当对一个系统的动态方面建模时,有以下几种使用活动图的方式:
- 对工作流建模;
- 对操作建模;
- 对业务的复杂流程建模。
构件图
构件图(Component Diagram,组件图)展现了一组构件之间的组织和依赖。构件图专注于系统的静态实现视图,它与类图相关,通常把构件映射为一个或多个类、接口或协作。
构件图的成分有:
- 构件:用矩形表示,在矩形右上方有一个小标记。
- 供接口:用一个圆圈和连接到构件上的实线表示。构件提供接口给其它构件使用。
- 需接口:用一个半圆和连接到构件上的实线表示。构件使用需接口表示需要调用其它构件提供接口。
- 依赖:将供接口(圆圈)和虚接口(半圆)连接到一起,表示两个构件通过这个接口相依赖。
部署图
部署图(Deployment Diagram)是用来对面向对象系统的物理方面建模的方法,展现了运行时处理结点以及其中构件(制品)的配置。部署图对系统的静态部署视图进行建模,它与构件图相关。
部署图展现了系统的软件和硬件之间的关系,在实施阶段使用。
<<artifact>>
表示制品。
总结
UML图 | 静态建模 | 动态建模 | 物理建模 |
---|---|---|---|
类图 | $\checkmark$ | $\times$ | $\times$ |
对象图 | $\checkmark$ | $\times$ | $\times$ |
用例图 | $\checkmark$ | $\times$ | $\times$ |
构件图(组件图) | $\checkmark$ | $\times$ | $\checkmark$ |
部署图 | $\checkmark$ | $\times$ | $\checkmark$ |
序列图(顺序图,时序图) | $\times$ | $\checkmark$ | $\times$ |
通信图(协作图) | $\times$ | $\checkmark$ | $\times$ |
状态图 | $\times$ | $\checkmark$ | $\times$ |
活动图 | $\times$ | $\checkmark$ | $\times$ |
活动图是一种特殊的状态图,它们的差异如下:
- 相同点:状态中都有初态和终态。
- 主要差异:
- 活动图的转换称为流;
- 活动图有分支、并发分岔和并发汇合。
以下是UML图的总结:
-
类图:展现一组对象(类)、接口、协作和它们之间的关系
-
对象图:展现某一时刻的一组对象以及它们之间的关系,描述了在类图中所建立事物的实例的静态快照
-
用例图:展现了一组用例、参与者以及它们之间的关系(包含、扩展、关联和泛化)
-
序列图(顺序图,时序图):描述了以时间顺序组织的对象之间的交互活动,强调消息时间顺序
-
通信图(协作图):强调收发消息的对象的结构组织
-
状态图(状态转换图):展现了一个状态机,强调对象行为的事件顺序
-
活动图:一种特殊的状态图,展现了在系统内从一个活动到另一个活动的流程,强调对象间的控制流程
-
构件图(组件图):展现了一组构件之间的组织和依赖,将构件映射为类、接口或协作
-
部署图:对物理建模,展现了运行时处理结点以及其中构件(制品)的配置
设计模式
每一个设计模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。使用设计模式能减少一些重复劳动。设计模式的核心在于提供了相关问题的解决方案,使得人们可以更加简单方便地复用成功的设计和体系结构。
设计模式一般有以下4个要素:
- 模式名称(Pattern Name)
- 问题(Problem)
- 解决方案(Solution)
- 效果(Consequences)
设计模式:
- 确定了所包含的类和实例的:
- 角色
- 协作方式
- 职责分配
- 每一个设计模式都:
- 集中于一个特定的面向对象设计问题或设计要点
- 描述了什么时候使用它
- 在另一些设计约束条件下是否还能使用
- 使用的效果和如何取舍
设计模式分类:
创建型 | 结构型 | 行为型 | |
---|---|---|---|
说明 | 与对象的创建有关 | 处理类或对象的组合 | 描述类或对象的交互和职责分配 |
类模式 | Factory Method(工厂方法模式) | Adapter(适配器模式) | Interpreter(解释器模式) Template Method(模板方法模式) |
对象模式 | Abstract Factory(抽象工厂模式) Builder(生成器模式) Prototype(原型模式) Singleton(单例模式) |
Adapter(适配器模式) Bridge(桥接模式) Composite(组合模式) Decorator(装饰器模式) Facade(外观模式) Flyweight(享元模式) Proxy(代理模式) |
Chain of Responsibility(责任链模式) Command(命令模式) Iterator(迭代器模式) Mediator(中介者模式) Memento(备忘录模式) Observer(观察者模式) State(状态模式) Strategy(策略模式) Visitor(访问者模式) |
创建型设计模式
创建型模式抽象了实例化过程。
- 类创建型模式(工厂方法):使用继承改变被实例化的类;
- 对象创建型模式(剩余的其它):将实例化委托给另一个对象。
简单工厂模式
简单工厂模式属于创建型模式,但不属于23种设计模式之一。
简单工厂模式定义了一个工厂类,它可以根据参数的不同,返回不同类的实例,被创建的实例通常都具有共同的父类。
在简单工厂模式中用于创建实例的方法通常长为静态(static
)方法,因此简单工厂模式又称为静态工厂方法(Static Factroy Method)。
简单工厂模式包含以下三种角色:
- 工厂(核心):负责实现创建所有产品的内部逻辑。工厂类可以被外界直接调用,创建所需对象。
- 抽象产品:工厂类所创建的所有对象的父类,封装了产品对象的公共方法,所有的具体产品为其子类对象。
- 具体产品:简单工厂模式的创建目标,所有被创建的对象都是某个具体类的实例,它要实现抽象产品中声明的抽象方法。
代码示例如下:
Product
:
/**
* 抽象产品
*/
public abstract class Product {
/**
* 抽象方法,返回产品信息
*/
public abstract void info();
}
ProductA
:
/**
* 具体产品A
*/
public class ProductA extends Product {
@Override
public void info() {
System.out.println("产品的信息:A");
}
}
ProductB
:
/**
* 具体产品B
*/
public class ProductB extends Product {
@Override
public void info() {
System.out.println("产品的信息:B");
}
}
Factory
:
/**
* 静态工厂类
*/
public class Factory {
/**
* 根据参数获取Product
* @param type 指定创建的对象的类型
*/
public static Product createProduct(String type) {
Product product = null;
// 判读参数,声明对应的对象
if (type.equals("A")) {
product = new ProductA();
} else if (type.equals("B")) {
product = new ProductB();
} else {
System.out.println("没有" + type + "类型的产品");
}
return product;
}
}
测试方法:
public class SimpleFactory {
public static void main(String[] args) {
Product productA = Factory.createProduct("A");
productA.info();
Product productB = Factory.createProduct("B");
productB.info();
Product productC = Factory.createProduct("C");
System.out.println(productC);
}
}
工厂方法
工厂方法(Factory Method)模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。
简单工厂违反了开放封闭原则,对扩展是开发的,对修改是封闭的。所以工厂方法的接口不创建具体的对象,而是交由子类来实现。
工厂方法结构:
-
Product
:定义工厂方法所创建的对象的接口。 -
ConcreteProduct
:实现Product
接口。 -
Creator
:声明工厂方法,该方法返回一个Product
类型的对象。Creator
也可以定义一个工厂方法的默认实现,它返回一个默认的ConcreteProduct
对象。 -
ConcreteCreator
:重定义工厂方法以返回一个ConcreteProduct
实例。
代码示例:
Product
:
/**
* 产品接口
* <p>
* 这里抽象产品是接口还是抽象类不重要,重要的是Factory一定得是接口
* </p>
*/
public interface Product {
/**
* 接口方法,返回产品信息
*/
public void info();
}
ProductA
:
public class ProductA implements Product {
@Override
public void info() {
System.out.println("产品的信息:A");
}
}
ProductB
与ProductA
类似,代码略。
Factory
:
/**
* 工厂方法接口
*/
public interface Factory {
/**
* 接口方法,获取Product
*/
public Product createProduct();
}
FactoryA
:
/**
* 工厂A
* <p>
* 只负责生产ProductA
* </p>
*/
public class FactoryA implements Factory {
/**
* 实现方法创建ProductA
*/
@Override
public Product createProduct() {
// 直接返回ProductA
return new ProductA();
}
}
FactoryB
:
/**
* 工厂B
* <p>
* 只负责生产ProductB
* </p>
*/
public class FactoryB implements Factory {
/**
* 实现方法创建ProductB
*/
@Override
public Product createProduct() {
// 直接返回ProductB
return new ProductB();
}
}
测试方法:
public class FactoryMethod {
public static void main(String[] args) {
Factory factory = new FactoryA();
Product productA = factory.createProduct();
productA.info();
factory = new FactoryB();
Product productB = factory.createProduct();
productB.info();
}
}
Factory Method模式适用于:
- 当一个类不知道它所必须创建的对象的类(不知道要创建的具体类)的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
工厂方法:
- 特点:用户类和工厂类分开。
- 优点:用户需要什么对象,只需向工厂请求即可。用户无需修改就可使用对象。
- 缺点:对象修改时,工厂类也需要做相应的修改。
抽象工厂模式
抽象工厂(Abstract Factory)提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
抽象工厂的工厂类是
abstract class
(抽象类),工厂方法是interface
。抽象工厂可以创建更多的类。
抽象工厂模式的结构:
AbstractFactory
:声明一个创建抽象产品对象的操作接口。ConcreteFactory
:实现创建具体产品对象的操作。AbstractProduct
:为一类产品对象声明一个接口。ConcreteProduct
:定义一个将被相应的具体工厂创建的产品对象,实现AbstractProduct
接口。Client
:仅使用由AbstractFactory
和AbstractProduct
类声明的接口。
代码示例:
创建两个产品接口ProductA
和ProductB
,并分别创建这两个产品接口的实现类ProductA1
和ProductA2
以及ProductB1
和ProductB2
。代码略。需要注意的是这里ProductA
和ProductB
代表两种不同的产品,它们的类型可以完全不相同。
Factory
:
public interface Factory {
/**
* 接口方法获取ProductA
*/
public ProductA createProductA();
/**
* 接口方法获取ProductB
*/
public ProductB createProductB();
}
Factory1
:
/**
* 工厂1创建产品ProductA1和ProductB1
*/
public class Factory1 implements Factory {
/**
* 创建产品ProductA1
*/
@Override
public ProductA createProductA() {
return new ProductA1();
}
/**
* 创建产品ProductB1
*/
@Override
public ProductB createProductB() {
return new ProductB1();
}
}
Factory2
:
/**
* 工厂2创建产品ProductA2和ProductB2
*/
public class Factory2 implements Factory {
/* 代码与Factory1类似,略 */
}
测试类:
public class AbstractFactory {
public static void main(String[] args) {
Factory factory = new Factory1();
ProductA productA1 = factory.createProductA();
ProductB productB1 = factory.createProductB();
productA1.info();
productB1.info();
factory = new Factory2();
ProductA productA2 = factory.createProductA();
ProductB productB2 = factory.createProductB();
productA2.info();
productB2.info();
}
}
Abstract Factory模式适用于:
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当提供一个产品类库,只想显示它们的接口而不是实现时。
抽象工厂模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类(具体工厂)去做。它仅负责给存储具体工厂类必须实现的接口。
生成器模式
生成器(Builder,建造者)模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
生成器模式结构:
-
Product
:表示被构造的复杂对象。 -
Builder
:为创建一个Product
对象的各个部件指定抽象接口。BuildPart()
:生成零件。 -
ConcreteBuilder
:实现Builder
的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,提供一个检索产品的接口。ConcreteBuilder
创建Product
对象产品的内部表示并定义它的装配过程。包含定义组成组件的类,包括将这些组件装配成最终产品的接口。GetResult()
:获取生成(组装完成)的产品。 -
Director
:构造一个使用Builder
接口的对象。最终是使用Director
来构造Product
。
代码示例:
Product
:
public class Product {
private List<String> parts = new ArrayList<>(); // 零件
/**
* 添加零件
* @param part 要添加的零件
*/
public void add(String part) {
parts.add(part);
}
@Override
public String toString() {
return "Product{" +
"parts=" + parts +
'}';
}
}
Builder
:
public abstract class Builder {
/**
* 生成产品零件
*/
public abstract void buildPart();
/**
* 获取结果
*/
public abstract Product getResult();
}
Builder1
:
public class Builder1 extends Builder {
private Product product = new Product();
@Override
public void buildPart() {
product.add("A");
product.add("B");
product.add("C");
product.add("D");
product.add("E");
product.add("F");
}
@Override
public Product getResult() {
return product;
}
}
Builder2
与Builder1
类似,略。
Director
:
public class Director {
/**
* 根据指定的Builder来生产零件
* @param builder 指定用来生产零件的Builder
*/
public void construct(Builder builder) {
builder.buildPart();
}
}
测试:
public class BuilderTest {
public static void main(String[] args) {
Director director = new Director();
Builder builder1 = new Builder1();
director.construct(builder1);
Product product1 = builder1.getResult();
System.out.println(product1);
Builder2 builder2 = new Builder2();
director.construct(builder2);
Product product2 = builder2.getResult();
System.out.println(product2);
}
}
Builder模式适用于:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
原型模式
原型(Prototype)模式用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式结构:
Prototype
:声明一个复制自身的接口。ConcretePrototype
:实现一个复制自身的操作。Client
:让一个原型复制自身从而创建一个新的对象。
代码示例:
Prototype
:
public interface Prototype {
/**
* 克隆方法
* @return
*/
public Object clone();
}
Product
:
public class Product implements Prototype {
private Integer id;
private Double price; // 价格
public Product() {}
public Product(Integer id, Double price) {
this.id = id;
this.price = price;
}
@Override
public Object clone() {
Product object = new Product(); // 这里也可以直接使用带参的构造方法,而不必使用setter
object.setId(this.id);
object.setPrice(this.price);
return object;
}
// 省略 getter, setter
@Override
public String toString() {
return "Product{" +
"id=" + id +
", price=" + price +
'}';
}
}
测试:
public class PrototypeTest {
public static void main(String[] args) {
Product product1 = new Product(2023, 5.22);
System.out.println("product1: " + product1);
Product product2 = (Product) product1.clone();
System.out.println("product2: " + product2);
}
}
Prototype模式适用于:
- 当一个系统应该独立于它的产品创建、构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
原型模式:
- 特点:通过给出一个原型对象来指明所要创建的对象的类型,然后复制这个原型对象的方法创建出更多同类型的对象。
- 优点:允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。
- 缺点:每一个类都必须配备一个克隆方法。
单例模式
单例(Singleton,单态)模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。
就好像Spring中的Bean,每个Bean默认有且仅有一个实例,通过
@Autowired
自动装配(来访问)。
单例模式结构:
Singleton
:指定一个Instance
操作,允许客户访问它的唯一实例。Instance
:是一个类操作;可能负责创建它自己的唯一实例。
代码示例:
Singleton
:
public class Singleton {
// 在内部示例化
private static Singleton instance = new Singleton();
/**
* 定义私有的构造方法,让其它外部类无法通过构造方法示例化对象
*/
private Singleton() {}
/**
* 全局访问方法
* <p>
* 因为该类为单例,所以使用静态方法获取即可
* </p>
*/
public static Singleton getInstance() {
return instance;
}
}
测试:
public class SingletonTest {
/**
* 验证Singleton是否为单例,打印它的地址即可
*/
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton1);
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton2);
}
}
Singleton模式适用于:
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户无须更改代码就能使用一个扩展的实例时。
单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的“单一实例”的需求时才可使用。
结构型设计模式
结构型设计模式涉及如何组合类或对象以获得更大的结构。
-
结构型类模式:采用继承机制来组合接口或实现。
一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。
这一模式尤其有助于多个独立开发的类库协同工作。
-
结构型对象模式:描述了如何对一些对象进行组合,从而实现新功能的一些方法(不是对接口和实现进行组合)。
因为可以在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。
适配器模式
适配器(Adapter,变压器)模式将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
-
类适配器使用多重继承对一个接口与另一个接口进行匹配:
-
对象适配器依赖于对象组合:
其中:
Target
:定义Client
使用的与特定领域相关的接口。Client
:与符合Target
接口的对象协同。Adaptee
:定义一个已经存在的接口,这个接口需要适配。Adapter
(适配器):对Adaptee
的接口与Target
接口进行适配。
对象适配器对象模式代码示例:
Target
:
/**
* 目标接口
*/
public class Target {
public void request() {
System.out.println("Target request...");
}
}
Adaptee
:
/**
* 需适配接口
*/
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specificRequest...");
}
}
Adapter
:
public class Adapter extends Target {
// 对象适配
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
// 重写并在其中调用Adaptee的对应方法
adaptee.specificRequest();
}
}
测试:
public class AdapterTest {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
Adapter模式适用于:
- 想使用一个已经存在的类,而它的接口不符合要求。
- 想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
- (仅适用于对象Adapter)想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
桥接模式
桥接(Bridge)模式将抽象部分与其实现部分分离,使它们都可以独立地变化。
桥接模式结构:
-
Abstraction
:定义抽象类的接口,维护一个指向Implementor
类型对象的指针。 -
RefinedAbstraction
:扩充由Abstraction
定义的接口。 -
Implementor
:定义实现类的接口,该接口不一定要与Abstraction
的接口完全一致;事实上这两个接口可以完全不同。一般来说,
Implementor
接口仅提供基本操作,而Abstraction
定义了基于这些基本操作的较高层次的操作。 -
ConcreteImplementor
:实现Implementor
接口并定义它的具体实现。
代码实现:
Color
:
/**
* 产品颜色接口
*/
public interface Color {
public void operationImp(Product product);
}
Product
:
/**
* 产品接口
*/
public abstract class Product {
// 默认名称
private String name = Product.class + "@" + Integer.toHexString(this.hashCode());
protected Color color; // 指向Implementor的指针,其实用private也可以,只需要为其定义getter、setter
public abstract void operation();
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", color=" + color +
'}';
}
}
Red
:
public class Red implements Color {
@Override
public void operationImp(Product product) {
System.out.println(product);
}
}
Bule
与Red
类似,略。
ProductA
:
public class ProductA extends Product {
@Override
public void operation() {
color.operationImp(this);
}
}
测试:
public class Bridge {
public static void main(String[] args) {
Product productA1 = new ProductA();
Color red = new Red();
productA1.setColor(red);
productA1.operation();
Product productA2 = new ProductA();
Color blue = new Blue();
productA2.setColor(blue);
productA2.operation();
}
}
Bridge模式适用于:
-
不希望在抽象和它的实现部分之间有一个固定的绑定关系。
例如,这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
-
类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。
这是Bridge模式使得开发者可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
-
对一个抽象的实现部分的修改应对客户不产生影响,即客户代码不必重新编译。
-
(C++)想对客户完全隐藏抽象的实现部分。
-
有许多类要生成的类层次结构。
-
想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。
桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联。也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
组合模式
组合(Composite,合成)模式将对象组合成树型结构以表示“部分——整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
组合模式的结构:
-
Component
:为组合中的对象声明接口。- 在适当情况下实现所有类共有接口的默认行为;
- 声明一个接口用于访问和管理
Component
的子组件; - (可选)在递归结构中定义一个接口,用于访问一个父组件,并在合适的情况下实现它。
-
Leaf
:在组合中表示叶结点对象,叶结点没有子结点;在组合中定义图元对象的行为。 -
Composite
:- 定义有子组件的那些组件的行为;
- 存储子组件;
- 在
Component
接口中实现与子组件有关的操作。
-
Client
:通过Component
接口操纵组合组件的对象。
代码示例:
AbstractFile
:
public abstract class AbstractFile {
protected String name;
public String getName() {
return name;
}
/**
* 模范Linux tree命令的输出
* <p>
* 只是为了展示,并不重要
* </p>
*/
public static String toTreeString(AbstractFile file) {
String treeString = file.getName();
List<AbstractFile> childrenList = file.getChildrenList();
if (childrenList != null) {
treeString += '\n';
for (int i = 0; i < childrenList.size(); i++) {
AbstractFile children = childrenList.get(i);
String childTreeString = toTreeString(children);
String[] split = childTreeString.split("\n");
childTreeString = "";
for (int j = 0; j < split.length; j++) {
if (split[j].charAt(0) == '├'
|| split[j].charAt(0) == '└'
|| split[j].charAt(0) == '│'
|| split[j].charAt(0) == ' ') {
if (i == childrenList.size() - 1) {
split[j] = " " + split[j];
} else {
split[j] = "│ " + split[j];
}
} else {
if (i == childrenList.size() - 1) {
split[j] = "└── " + split[j];
} else {
split[j] = "├── " + split[j];
}
}
split[j] += '\n';
childTreeString += split[j];
}
treeString += childTreeString;
}
}
return treeString;
}
public abstract boolean add(AbstractFile file);
public abstract boolean remove(AbstractFile file);
public abstract List<AbstractFile> getChildrenList();
}
File
:
public class File extends AbstractFile {
public File(String name) {
this.name = name;
}
@Override
public boolean add(AbstractFile file) {
return false;
}
@Override
public boolean remove(AbstractFile file) {
return false;
}
@Override
public List<AbstractFile> getChildrenList() {
return null;
}
@Override
public String toString() {
return this.name;
}
}
Folder
:
public class Folder extends AbstractFile {
private List<AbstractFile> childrenList = new ArrayList<AbstractFile>();
public Folder(String name) {
this.name = name;
}
@Override
public boolean add(AbstractFile file) {
return childrenList.add(file);
}
@Override
public boolean remove(AbstractFile file) {
return childrenList.remove(file);
}
@Override
public List<AbstractFile> getChildrenList() {
return childrenList;
}
@Override
public String toString() {
return toTreeString(this);
}
}
测试:
public class Composite {
/**
* 模仿Linux的文件结构
*/
public static void main(String[] args) {
AbstractFile root = new Folder("/");
String[] childrenNameList = {"sbin", "boot", "sys", "etc", "usr", "home", "root"};
for (String childrenName : childrenNameList) {
AbstractFile children = new Folder(childrenName);
root.add(children);
}
List<AbstractFile> childrenList = root.getChildrenList();
AbstractFile myFolder = new Folder("linner");
AbstractFile homeFolder = childrenList.get(5);
homeFolder.add(myFolder);
for (int i = 0; i < 5; i++) {
AbstractFile file = new File("file" + Integer.toString(i + 1));
myFolder.add(file);
if (i % 2 == 0) {
AbstractFile folder = new Folder("folder" + Integer.toString(i + 1));
myFolder.add(folder);
for (int j = 0; j < 3; j++) {
AbstractFile folderFile = new File("file"
+ Integer.toString(i + 1)
+ '_' + Integer.toString(j + 1));
folder.add(folderFile);
}
}
}
System.out.println(root);
}
}
输出如下:
/
├── sbin
├── boot
├── sys
├── etc
├── usr
├── home
│ └── linner
│ ├── file1
│ ├── folder1
│ │ ├── file1_1
│ │ ├── file1_2
│ │ └── file1_3
│ ├── file2
│ ├── file3
│ ├── folder3
│ │ ├── file3_1
│ │ ├── file3_2
│ │ └── file3_3
│ ├── file4
│ ├── file5
│ └── folder5
│ ├── file5_1
│ ├── file5_2
│ └── file5_3
└── root
Composite模式适用于:
- 想表示对象的部分——整体层次结构。
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
装饰器模式
装饰器(Decorator)模式动态地给一个对象添加一些额外的职责。装饰器提供了用子类扩展功能的灵活替代。就增加功能而言,Decorator模式比派生子类更加灵活。
装饰器模式结构:
Component
:定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent
:定义一个对象,可以给这个对象添加一些职责。Decorator
:维持一个指向Component
对象的指针,并定义一个与Component
接口一致的接口。ConcreteDecorator
:向组件添加职责。
代码示例:
Person
:
public abstract class Person {
protected String name;
/**
* 职责
*/
public abstract void operation();
public String getName() {
return name;
}
}
Student
:
public class Student extends Person {
public Student(String name){
this.name = name;
}
@Override
public void operation() {
System.out.println(name + "的职责:学习");
}
}
Decorator
:
public abstract class Decorator extends Person {
protected Person person;
}
DecoratorA
:
public class DecoratorA extends Decorator {
public DecoratorA(Person person) {
this.person = person;
this.name = person.getName();
}
@Override
public void operation() {
person.operation(); // 原本的职责
System.out.println(name + "新职责:摸鱼");
}
}
DecoratorB
与DecoratorA
类似,略;
测试:
public class DecoratorPattern {
public static void main(String[] args) {
Person zhangsan = new Student("张三");
zhangsan.operation();
System.out.println("============================");
/*Decorator decoratorA = new DecoratorA(zhangsan);
decoratorA.operation();*/
// 装饰器对象也相当于Person
// 装饰链
zhangsan = new DecoratorA(zhangsan);
zhangsan = new DecoratorB(zhangsan);
zhangsan.operation();
}
}
Decorator模式适用于:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤销的职责。
- 当不能采用生成子类的方式进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是,由于类定义被隐藏,或类定义不能用于生成子类。
外观模式
外观(Facade,门面)模式为子系统中的一组接口提供一个一致的门面(界面),Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式的结构:
-
Facade
:知道哪些子系统类负责处理请求;将客户的请求代理给适当的子系统对象。 -
Subsystem classes
:- 实现子系统的功能;
- 处理有
Facade
对象指派的任务; - 没有
Facade
的任何相关信息,即没有指向Facade
的指针。
代码示例:
SubSystem1
:
public class SubSystem1 {
public void method1() {
System.out.println("执行子系统1的功能");
}
}
SubSystem2
和SubSystem3
与SubSystem1
类似,代码略。
Facade
:
public class Facade {
private SubSystem1 subSystem1;
private SubSystem2 subSystem2;
private SubSystem3 subSystem3;
public Facade() {
subSystem1 = new SubSystem1();
subSystem2 = new SubSystem2();
subSystem3 = new SubSystem3();
}
public void methodA() {
subSystem1.method1();
}
public void methodB() {
subSystem2.method2();
}
public void methodC() {
subSystem3.method3();
}
}
测试:
public class FacadePattern {
public static void main(String[] args) {
Facade facade = new Facade();
facade.methodA();
facade.methodB();
facade.methodC();
}
}
Facade模式适用于:
-
Facade可以提供一个简单的默认视图,供大多数用户使用。
要为一个复杂子系统提供一个简单接口时,子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类,这使得子系统更具有可重用性,也更容易对子系统进行定制,但也给那些不需要定制子系统的用户带来一些使用上的困难。
Facade提供的简单默认视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
-
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
-
当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,则可以让它们仅通过Facade进行通信,从而简化了它们之间的依赖关系。
外观模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。
外观模式提供一个高层次的接口,使得子系统更易于使用:
- 每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。
- 整个系统可以有多个门面类。
享元模式
享元(Flyweight)模式运用共享技术有效地支持大量细粒度的对象。
细粒度对象:在业务模型上,按照业务需求将对象加以细分,从而得到更多的业务模型对象。
享元模式的结构:
-
Flyweight
:描述一个接口,通过这个接口Flyweight
可以接受并作用于外部状态。 -
ConcreteFlyweight
:实现Flyweight
接口,并为内部状态(如果有)增加存储空间。ConcreteFlyweight
对象必须是可共享的。它所存储的状态必须是内部的,即它必须独立于ConcreteFlyweight
对象的场景。 -
并非所有的
Flyweight
子类都需要被共享。Flyweight
接口使共享成为可能,但它并不强制共享。在Flyweight
对象结构的某些层次,UnsharedConcreteFlyweight
对象通常将ConcreteFlyweight
对象作为子结点。 -
FlyweightFactory
:创建并管理Flyweight
对象;确保合理地共享Flyweight
,当用户请求一个Flyweight
时,FlyweightFactory
对象提供一个已创建的实例或者在不存在时创建一个实例。 -
Client
:维持一个对Flyweight
的引用;计算或存储一个或多个Flyweight
的外部状态。
代码示例:
Piece
:
public abstract class Piece {
protected String color;
public abstract void draw(int x, int y);
public String getColor() {
return color;
}
}
WhitePiece
:
public class WhitePiece extends Piece {
public WhitePiece() {
this.color = PieceFactory.WHITE;
}
@Override
public void draw(int x, int y) {
// 打印地址和坐标信息
String piece = "WhitePiece@" + this.hashCode() + '{' +
"x=" + x + ", " +
"y=" + y +
'}';
System.out.println(piece);
}
}
BlackPiece
与WhitePiece
类似,代码略。
PieceFactory
:
public class PieceFactory {
public static final String WHITE = "white";
public static final String BLACK = "black";
private Map<String, Piece> piecesMap = Map.of(
WHITE, new WhitePiece(),
BLACK, new BlackPiece());
public Piece getPiece(String key) {
return piecesMap.get(key);
}
}
测试:
public class Flyweight {
public static void main(String[] args) {
PieceFactory factory = new PieceFactory();
Piece whitePiece1 = factory.getPiece(PieceFactory.WHITE);
whitePiece1.draw(20, 23);
Piece whitePiece2 = factory.getPiece(PieceFactory.WHITE);
whitePiece2.draw(5, 27);
Piece blackPiece1 = factory.getPiece(PieceFactory.BLACK);
blackPiece1.draw(20, 22);
Piece blackPiece2 = factory.getPiece(PieceFactory.BLACK);
blackPiece2.draw(5, 28);
}
}
Flyweight模式适用于:
- 一个应用程序使用了大量的对象。
- 完全由于使用大量的对象,造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 外部状态:容易发生改变的状态;
- 内部状态:不容易发生改变的状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
享元模式能做到共享的关键是区分状态:
- 内蕴状态:存储在享元内部,不会随环境的改变而有所不同。
- 外蕴状态:随环境的改变而改变的。
外蕴状态不能影响内蕴状态,它们是相互独立的。
享元模式:将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。
代理模式
代理(Proxy)模式为其他对象提供一种代理以控制对这个对象的访问。
代理模式的结构:
-
Proxy
:保存一个引用使得代理可以访问实体;提供一个与Subject
的接口相同的接口,使代理可以用来代替实体;控制对实体的存取,并可能负责创建和删除它。其他功能依赖于代理的类型:
Remote Proxy
:负责对请求及其参数进行编码,并向不同地址空间中的实体发送己编码的请求;Virtual Proxy
:可以缓存实体的附加信息,以便延迟对它的访问;Protection Proxy
:检查调用者是否具有实现一个请求所必需的访问权限。
-
Subject
:定义RealSubject
和Proxy
的共用接口,这样就在任何使用RealSubject
的地方都可以使用Proxy
。 -
RealSubject
:定义Proxy
所代表的实体。
代码示例:
Subject
:
public interface Subject {
public void request();
}
RealSubject
:
/**
* 代理实体
*/
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject request...");
}
}
Proxy
:
/**
* 代理
*/
public class Proxy implements Subject {
protected RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("Proxy pre-request...");
realSubject.request();
System.out.println("Proxy post-request...");
}
}
测试:
public class ProxyPattern {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.request();
}
}
Proxy模式适用于在需要比较通用和复杂的对象指针代替简单的指针的时候,常见情况有:
-
远程代理(Remote Proxy):为一个对象在不同地址空间提供局部代表。
-
虚代理(Virtual Proxy):根据需要创建开销很大的对象。
-
保护代理(Protection Proxy):控制对原始对象的访问,用于对象应该有不同的访问权限的时候。
-
智能引用(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作。
典型用途包括:
- 对指向实际对象的引用计数,这样当该对象没有引用时,可以被自动释放;
- 当第一次引用一个持久对象时,将它装入内存;
- 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
某些情况下,用户不想或者不能够直接引用一个对象,代理对象可以在用户和目标对象直接起到中介的作用。
客户端分辨不出代理主题对象与真实主题对象。代理模式可以不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
行为型设计模式
行为模式涉及算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信模式。
-
行为类模式:使用继承机制在类间分派行为。
-
行为对象模式:使用对象复合。
一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任一个对象都无法单独完成的任务。
责任链模式
责任链(Chain of Responsibility)模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
JavaWeb中
Filter
(过滤器)就是责任链模式。
责任链模式的结构:
-
Handler
:- 定义一个处理请求的接口;
- (可选)实现后继链。
-
ConcreteHandler
:- 处理它所负责的请求;
- 可访问它的后继者;
- 如果可处理该请求,就处理它,否则将该请求转发给后继者。
-
Client
:向链上的具体处理者(ConcreteHandler
)对象提交请求。
代码示例:
Handler
:
public abstract class Handler {
protected Handler next;
public void setNext(Handler next) {
this.next = next;
}
public abstract void handlerRequest(int request);
}
FuDaoYuan
:
/**
* 辅导员
*/
public class FuDaoYuan extends Handler {
/**
* 审批7天内的假期
* @param request
*/
@Override
public void handlerRequest(int request) {
if (request <= 7) {
System.out.println("FuDaoYuan allow...");
} else {
if (next != null) {
next.handlerRequest(request);
} else {
System.out.println("无法审批");
}
}
}
}
YuanZhang
和XiaoZhang
与FuDaoYuan
类似,代码略。
测试:
public class ChainOfResponsibility {
public static void main(String[] args) {
Handler fuDaoYuan = new FuDaoYuan();
Handler yuanZhang = new YuanZhang();
Handler xiaoZhang = new XiaoZhang();
fuDaoYuan.setNext(yuanZhang);
yuanZhang.setNext(xiaoZhang);
fuDaoYuan.handlerRequest(2);
System.out.println("=================");
fuDaoYuan.handlerRequest(8);
System.out.println("=================");
fuDaoYuan.handlerRequest(29);
System.out.println("=================");
fuDaoYuan.handlerRequest(31);
}
}
Chain of Responsibility模式适用于以下条件:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
- 想在不明确指定接收者的情况下向多个对象中的一个提交一个请求。
- 可处理一个请求的对象集合应被动态指定。
责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。
处理者有两个选择:承担责任或者把责任推给下家。
一个请求可以最终不被任何接收端对象所接受。
命令模式
命令(Command)模式将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式的结构:
-
Command
:声明执行操作的接口。 -
ConcreteCommand
:将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现Execute
。 -
Client
:创建一个具体命令对象并设定它的接收者Receiver
。 -
Invoker
:要求该命令执行这个请求。 -
Receiver
:知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
代码示例:
Command
:
/**
* 命令接口
*/
public interface Command {
/**
* 执行命令
*/
public void execute();
}
Tv
:
public class Tv {
/**
* 开机行为
*/
public void powerOnAction() {
System.out.println("Tv power-on...");
}
/**
* 关机行为
*/
public void powerOffAction() {
System.out.println("Tv power-off...");
}
}
PowerOn
:
/**
* 开机命令
*/
public class PowerOn implements Command {
private Tv tv;
public PowerOn(Tv tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.powerOnAction();
}
}
PowerOff
与PowerOn
类似,代码略。
Invoker
:
/**
* 请求者
*/
public class Invoker {
private Command command;
/**
* 设置请求命令
*/
public void setCommand(Command command) {
this.command = command;
}
/**
* 执行命令
*/
public void call() {
command.execute();
}
}
测试:
public class CommandPattern {
public static void main(String[] args) {
Tv tv = new Tv();
Command powerOn = new PowerOn(tv);
Command powerOff = new PowerOff(tv);
Invoker invoker = new Invoker();
invoker.setCommand(powerOn);
invoker.call();
invoker.setCommand(powerOff);
invoker.call();
}
}
Command模式适用于:
-
抽象出待执行的动作以参数化某对象。
Command模式是过程语言中的回调(Callback)机制的一个面向对象的替代品。
-
在不同的时刻指定、排列和执行请求。
一个Command对象可以有一个与初始请求无关的生存期。
如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可以将负责该请求的命令对象传递给另一个不同的进程,并在那实现该请求。
-
支持取消操作。
Command的Execute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。
Command接口必须添加一个Unexecute操作,该操作取消上一次Execute 调用的效果。
执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消和“重做”。
-
支持修改日志。
这样当系统崩溃时,这些修改可以被重做一遍。
在 Command 接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。
从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
-
用构建在原语操作上的高层操作构造一个系统。
这样一种结构在支持事务(Transaction)的信息系统中很常见。
Command模式提供了对事务进行建模的方法。Command有一个公共接口,使得可以用同一种方式调用所有的事务,同时使用该模式也易于添加新事务以扩展系统。
命令模式:把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来,使得请求的一方:
- 不必知道接收请求的一方的接口;
- 不必知道请求是怎么被接收
- 不必知道请求的操作是否执行,何时被执行以及是怎么被执行的。
解释器模式
解释器(Interpreter)模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
可用于实现程序语言设计中的上下文无关文法。
解释器模式结构:
-
AbstractExpression
:声明一个程序的解释操作,这个接口为抽象语法树中所有的结点所共享。 -
TerminalExpression
:实现与文法中的终结符相关联的解释操作;一个句子中的每个终结符需要该类的一个实例。 -
NonterminalExpression
:对文法中的每一条规则都需要一个NonterminalExpression
类。- 为每个符号都维护一个
AbstractExpression
类型的实例变量; - 为文法中的非终结符实现解释(
Interpret
)操作。
- 为每个符号都维护一个
-
Context
:包含解释器之外的一些全局信息。 -
Client
:构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树,该抽象语法树由NonterminalExpression
和TerminalExpression
的实例装配而成;调用解释操作。
Interpreter模式适用于当有一个语言需要解释执行,且可将该语言中的句子表示为一个抽象语法树时,以下情况效果最好:
-
该文法简单。
对于复杂的发文,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无须构建抽象语法树即可解释表达式,这样可以节省空间还可能节省时间。
即Interpreter模式需要构建抽象语法树,如果文法太复杂,需要构建复杂的语法树,不便于管理。
-
效率不是一个关键问题。
最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。不过,即使在这种情况下,转换器仍然可用该模式实现。
题目选项描述:
有一个语言需要解释执行,并且可将句子表示为一个抽象语法树。
解释器模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在SQL解析、符号处理引擎等。
迭代器模式
迭代器(Iterator)模式提供一种方法顺序访问一个聚合对象中的各个元素,且不需要暴露该对象的内部表示。
多个对象聚在一起形成的总体称之为聚合(聚集),聚合对象是能够包容一组对象的容器对象。
迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。
迭代器模式的结构:
Iterator
(迭代器):定义访问和遍历元素的接口。ConcreteIterator
(具体迭代器):实现迭代器接口;对该聚合遍历时跟踪当前位置。Aggregate
(聚合):定义创建相应迭代器对象的接口。ConcreteAggregate
(具体聚合):实现创建相应迭代器的接口,该操作返回ConcreteIterator
的一个适当的实例。
代码示例:
Book
:
public class Book {
private String name;
private Double price;
// getter、setter和toString,略
}
Aggregate
:
public interface Aggregate {
public Iterator iterator();
}
Iterator
:
public interface Iterator {
public boolean hasNext();
public Object next();
}
BookAggregate
:
public class BookAggregate implements Aggregate {
List<Book> books = new ArrayList<>();
public void add(Book book) {
books.add(book);
}
public Book get(int index) {
return books.get(index);
}
public int size() {
return books.size();
}
@Override
public Iterator iterator() {
return new BookIterator(this);
}
}
BookIterator
:
/**
* 迭代对象
*/
public class BookIterator implements Iterator {
private BookAggregate bookAggregate;
private int index;
public BookIterator(BookAggregate bookAggregate) {
index = 0;
this.bookAggregate = bookAggregate;
}
@Override
public boolean hasNext() {
return index < bookAggregate.size();
}
@Override
public Object next() {
return bookAggregate.get(index++);
}
}
测试:
public class IteratorPattern {
public static void main(String[] args) {
BookAggregate bookAggregate = new BookAggregate();
Map<String, Double> booksMap = Map.of(
"数据结构", 10.24,
"操作系统", 20.48,
"计算机网络", 40.96,
"计算机组成原理", 81.92);
booksMap.forEach((key, value) -> {
bookAggregate.add(new Book(key, value));
});
Iterator iterator = bookAggregate.iterator();
while (iterator.hasNext()) {
Book book = (Book) iterator.next();
System.out.println(book);
}
}
}
Iterator模式适用于:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 支持对聚合对象的多种遍历。
- 为遍历不同的聚合结构提供一个统一的接口。
中介者模式
中介者模式(Mediator,调停者)用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的结构:
Mediator
(中介者):定义一个接口用于各同事(Colleague
)对象通信。ConcreteMediator
(具体中介者):通过协调各同事对象实现协作行为;了解并维护它的各个同事。Colleague
(同事类):知道它的中介者对象;每一个同事类对象在需要与其他同事通信的时候与它的中介者通信。
Mediator模式适用于:
- 一组对象以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
中介者模式:包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。
当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。中介者模式将多对多的相互作用转化为一对多的相互作用。
中介者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
备忘录模式
备忘录(Memento)模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可以将对象恢复到原先保存的状态。
Memento模式的结构:
Memento
(备忘录):存储原发器对象的内部状态,原发器根据需要决定备忘录存储原发器的哪些内部状态;防止原发器以外的其他对象访问备忘录。Originator
(原发器):创建一个备忘录,用于记录当前时刻它的内部状态;使用备忘录恢复内部状态。Caretaker
(管理者)负责保存好备忘录;不能对备忘录的内容进行操作或检查。
Memento模式适用于:
- 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
- 如果用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
题目选项描述:
- 将对象的状态恢复到先前的状态。
- 在不破坏封装性的前提下,捕获对象的内部状态并在对象之外保存。
- 必须保存一个对象在某一个时刻的(部分)状态。
关键词:状态。
备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
观察者模式
观察者(Observer)模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
即,观察者模式让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使他们能够自动更新自己。
JavaWeb中
Listener
就是观察者模式。
观察者模式的结构:
Subject
(目标):提供注册和删除观察者对象的接口;知道它的观察者,可以有任意多个观察者观察同一个目标。Observer
(观察者):为那些在目标发生改变时需获得通知的对象定义一个更新接口。ConcreteSubject
(具体目标):将有关状态存入各ConcreteObserver
对象;当它的状态发生改变时,向它的各个观察者发出通知。ConcreteObserver
(具体观察者):维护一个指向ConcreteSubject
对象的引用;存储有关状态,这些状态应与目标的状态保持一致;实现Observer
的更新接口,以使自身状态与目标的状态保持一致。
代码实现:
Observer
:
/**
* 观察者
*/
public interface Observer {
/**
* 更新观察者状态
*/
public void update();
}
/**
* 观察目标
*/
public interface Subject {
/**
* 注册观察者
*/
public void attach(Observer observer);
/**
* 删除观察者
*/
public void detach(Observer observer);
/**
* 状态改变,通知所有观察者
*/
public void notifyAllObservers();
public String getState();
public void setState(String state);
}
ConcreteObserver
:
public class ConcreteObserver implements Observer {
private String name;
private String state;
private Subject subject;
public ConcreteObserver(String name, Subject subject) {
this.name = name;
this.subject = subject;
subject.attach(this);
state = subject.getState();
}
@Override
public void update() {
state = subject.getState();
System.out.println("[收到通知] " + this);
}
@Override
public String toString() {
return "ConcreteObserver{" +
"name='" + name + '\'' +
" , state='" + state + '\'' +
" , subject=" + subject +
'}';
}
}
ConcreteSubject
:
public class ConcreteSubject implements Subject {
private String name;
private List<Observer> observerList;
private String state;
public ConcreteSubject(String name) {
this.name = name;
observerList = new ArrayList<>();
}
@Override
public void attach(Observer observer) {
observerList.add(observer);
}
@Override
public void detach(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyAllObservers() {
System.out.println("[发出通知] " + this);
for (Observer observer : observerList) {
observer.update();
}
}
@Override
public String getState() {
return state;
}
@Override
public void setState(String state) {
this.state = state;
System.out.println("[状态改变] " + this);
notifyAllObservers();
}
@Override
public String toString() {
return "ConcreteSubject{" +
"name='" + name + '\'' +
" , state='" + state + '\'' +
'}';
}
}
测试:
public class ObserverPattern {
public static void main(String[] args) {
Subject subject = new ConcreteSubject("主题");
Observer zhangsan = new ConcreteObserver("张三", subject);
Observer lisi = new ConcreteObserver("李四", subject);
Observer wangwu = new ConcreteObserver("王五", subject);
subject.notifyAllObservers();
subject.setState("new");
}
}
Observer模式适用于:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变时。
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁,即不希望这些对象是紧耦合的。
状态模式
状态(State)模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。
状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时,系统便改变所选的子类。
状态模式的结构:
Context
(上下文):定义客户感兴趣的接口;维护一个ConcreteState
子类的实例,这个实例定义当前状态。State
(状态):定义一个接口以封装与Context
的一个特定状态相关的行为。ConcreteState
(具体状态子类):每个子类实现与Context
的一个状态相关的行为。
代码示例:
State
:
public interface State {
public void handle(Context context);
}
Context
:
/**
* 贩卖机
*/
public class Context {
private int count;
private State state;
public Context(int count) {
this.count = count;
if (count > 0) {
this.state = new StateA();
} else {
this.state = new StateB();
}
}
/**
* 购买饮料
*/
public void request() {
state.handle(this);
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
StateA
:
/**
* 有货
*/
public class StateA implements State {
@Override
public void handle(Context context) {
int count = context.getCount();
if (count >= 1) {
System.out.println("购买成功");
context.setCount(--count);
if (context.getCount() == 0) {
context.setState(new StateB());
}
} else {
System.out.println("购买失败");
}
}
@Override
public String toString() {
return "State@" + this.hashCode() + '{' + "有货" + '}';
}
}
StateB
:
/**
* 有货
*/
public class StateB implements State {
@Override
public void handle(Context context) {
int count = context.getCount();
if (count == 0) {
System.out.println("购买失败,等待补货");
context.setCount(5);
System.out.println("补货成功,请重新购买");
context.setState(new StateA());
}
}
@Override
public String toString() {
return "State@" + this.hashCode() + '{' + "无货" + '}';
}
}
测试:
public class StatePattern {
public static void main(String[] args) {
Context context = new Context(3);
System.out.println(context.getState());
context.request();
context.request();
context.request();
System.out.println(context.getState());
context.request();
System.out.println(context.getState());
}
}
State模式适用于:
-
一个对象的行为决定于它的状态,并且它必须在运行时刻根据状态改变它的行为。
-
一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
这个状态常用一个或多个枚举常量表示。
State模式将每一个条件分支放入一个独立的类中。
策略模式
策略(Strategy)模式定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。此模式使得算法可以独立于使用它们的客户而变化。
即,策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
-
Strategy
(策略):定义所有支持的算法的公共接口。Context
使用这个接口来调用某ConcreteStrategy
定义的算法。 -
ConcreteStrategy
(具体策略):以Strategy
接口实现某具体算法。 -
Context
(上下文):用一个ConcreteStrategy
对象来配置;维护一个对Strategy
对象的引用;可定义一个接口来让Strategy
访问它的数据。
代码实现:
Strategy
:
public interface Strategy {
public int twoNumberOperation(int x, int y);
}
OperationContext
:
public class OperationContext {
private Strategy strategy;
public int operation(int x, int y) {
return strategy.twoNumberOperation(x, y);
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
}
AddStrategy
:
public class AddStrategy implements Strategy {
@Override
public int twoNumberOperation(int x, int y) {
return x + y;
}
}
与AddStrategy
类似,代码略。
测试:
public class StrategyPattern {
public static void main(String[] args) {
OperationContext context = new OperationContext();
Strategy add = new AddStrategy();
Strategy sub = new SubStrategy();
Strategy multiply = new MultiplyStrategy();
/**
* 我觉得下面这部分代码应该是放在OperationContext.operation()中实现的<br>
* 因为策略模式就是要让用户不知道使用的是哪种算法<br>
* 但是对于这个案例来说不适合这样实现
*/
context.setStrategy(add);
System.out.println(context.operation(2023, 527));
context.setStrategy(sub);
System.out.println(context.operation(2023, 527));
context.setStrategy(multiply);
System.out.println(context.operation(2023, 527));
}
}
Strategy模式适用于:
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要使用一个算法的不同变体。例如,定义一些反映不同空间的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使厨策略模式。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,将相关的条件分支移入它们各自的
Strategy
类中,以代替这些条件语句。
策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模把行为和环境分开:环境类负责维持和查询行为类,各种算法在具体的策略类中提供。
由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。
模板方法模式
模板方法模式(Template Method)定义一个操作中的算法骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法模式的结构:
-
AbstractClass
(抽象类):- 定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤;
- 实现模板方法,定一个算法的骨架,该模板方法不仅调用原语操作,也调用定义在
AbstractClass
或其他对象中的操作。
-
ConcreteClass
(具体类):实现原语操作以完成算法中与特定子类相关的步骤。
Template Method模式适用于:
-
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
-
各子类中公共的行为应被提取出来并集中到一个公共父类中,以避免代码重复。
-
控制子类扩展。
模板方法旨在特定点调用“hook”操作(默认的行为,子类可以在必要时进行重定义扩展),这就只允许在这些点进行扩展。
模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。
访问者模式
访问者(Visitor)模式表示一个作用于某对象结构中的各元素的操作。它允许在不改变各元素的类的前提下,定义作用于这些元素的新操作。
即,访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。
就是结构和行为分离,结构被定义在具体对象中(具体对象实现操作的一小部分),行为被定义在访问者中(操作的大部分由访问者实现)。
访问者模式的结构:
-
ConcreteVisitor
(具体访问者):实现每个有Visitor
声明的操作,每个操作实现本算法的一部分,而该算法片段乃是对应于结构中对象的类。ConcreteVisitor
为该算法提供了上下文并存储它的局部状态。这一状态常常在遍历该结构的过程中累积结果。 -
Element
(元素):定义以一个访问者为参数的Accept
操作。 -
ConcreteElement
(具体元素):实现以一个访问者为参数的Accept
操作。 -
ObjectStructure
(对象结构):能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个组合或者一个集合。
代码实现:
Person
:
public abstract class Person {
private String name;
/**
* 接受访问对象
*/
public abstract void accept(Visitor visitor);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
String[] split = getClass().toString().split("\\.");
int last = split.length - 1;
return split[last] + '{' +
"name='" + name + '\'' +
'}';
}
}
Visitor
:
public interface Visitor {
/**
* 访问学生
*/
public void visit(Student student);
/**
* 访问老师
*/
public void visit(Teacher teacher);
}
Student
:
public class Student extends Person {
public Student(String name) {
setName(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Teacher
与Student
类似,代码略。
Visitor1
:
public class Visitor1 implements Visitor {
@Override
public void visit(Student student) {
System.out.println("Visitor1 " + student);
}
@Override
public void visit(Teacher teacher) {
System.out.println("Visitor1 " + teacher);
}
}
Visitor2
与Visitor1
类似,代码略。
PersonStructure
:
public class PersonStructure {
private List<Person> people = new ArrayList<>();
public PersonStructure() {
people.add(new Student("张三"));
people.add(new Student("李四"));
people.add(new Student("王五"));
people.add(new Teacher("李老师"));
people.add(new Teacher("陈老师"));
people.add(new Teacher("刘老师"));
}
/**
* 使用访问者访问集合中所有对象
*/
public void accept(Visitor visitor) {
for (Person person : people) {
person.accept(visitor);
}
}
}
测试:
public class VisitorPattern {
public static void main(String[] args) {
PersonStructure structure = new PersonStructure();
Visitor visitor1 = new Visitor1();
structure.accept(visitor1);
System.out.println("==============================");
Visitor visitor2 = new Visitor2();
structure.accept(visitor2);
}
}
Visitor模式适用于:
-
一个对象结构包含很多类对象,它们有不同的接口,而用户想对这些对象实施一些依赖于其具体类的操作。
-
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而又想要避免这些操作“污染”这些对象的类。
Visitor使得用户可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
-
定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。
改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中,而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。
设计模式总结
创建型设计模式(抽象了对象的实例化过程):
模式 | 关键字 | 意图 |
---|---|---|
工厂方法 | 动态生产对象 | 定义创建对象的接口,由子类实例化对象。让类的实例化延迟到其子类。 |
抽象工厂 | 生成系列对象 | 提供创建一系列对象的接口,无需指定具体的类。 |
生成器 | 构造复杂对象 | 将复杂对象的构建与表示分离。使得同样的构建可以创建不同的表示。 |
原型 | 克隆对象 | 用原型实例指定创建对象的类型,通过复制原型来创建对象。 |
单例 | 一个实例 | 保证一个类仅有一个实例,并提供一个全局访问点。 |
模式 | 适用性 |
---|---|
工厂方法 |
|
抽象工厂 |
|
生成器 |
|
原型 |
|
单例 |
|
结构型模式(组合类或对象获得新的结构):
模式 | 关键字 | 意图 |
---|---|---|
适配器(类/对象) | 接口转换 | 将类的接口转换成兼容其他类的接口。 使原本接口不兼容的类可以一起工作。 |
桥接 | 抽象与实现分离 | 将类的抽象与实现分离,使它们可以独立变化。 |
组合 | 组合对象 | 将对象组合成树型结构以表示“部分——整体”的层次结构。 使得用户对单个对象和组合对象的使用具有一致性。 |
装饰 | 动态附加职责 | 动态地给一个对象添加一些额外的职责,比用子类来扩展功能更灵活。 |
外观 | 对外统一接口 | 为子系统定义和提供一个统一的对外高层接口(外观)。 简化了该子系统的使用。 |
享元 | 共享大量细粒度对象 | 提供支持大量细粒度对象共享的有效方法。 |
代理 | 中介代理 | 为其他对象提供一种代理以控制对这个对象的访问。 |
模式 | 适用性 |
---|---|
适配器 |
|
桥接 |
|
组合 |
|
装饰器 |
|
外观 |
|
享元 |
|
代理 |
|
行为型模式:
模式 | 关键字 | 意图 |
---|---|---|
责任链 | 职责传递 | 将处理请求的多个对象连成一条链,请求在链中传递,直到有对象处理。 给多个对象处理请求的机会,减少请求的发送者与接收者之间的耦合。 |
命令 | 请求封装为对象 | 将一个请求封装为一个对象,可用不同请求对客户进行参数化。 将请求排队或记录日志,支持撤销操作。 |
解释器 | 语句解释 | 给定一种语言,定义其文法和解释器,解释器根据文法解释语言中的句子。 |
迭代器 | 顺序访问数据集 | 提供一个顺序访问聚合对象中元素的方法,不需要暴露对象的内部表示。 |
中介者 | 不直接引用 | 用对象封装一系列的对象交互。 使各对象不需显式地相互调用,达到低耦合。 可以独立改变对象间的交互。 |
备忘录 | 保存状态 | 不破坏封装的前提下,捕获对象的内部状态,并在该对象之外保存。 可以在以后恢复保存的状态。 |
观察者 | 联动 | 定义对象间的一种一对多依赖关系。 一个对象状态改变,所有依赖于它的对象都得到通知并被自动更新。 |
状态 | 状态封装成类 | 把对象的行为封装在不同的状态对象中。 允许一个对象在其内部状态改变时改变它的行为。 |
策略 | 多方案切换 | 定义并封装一系列算法,使它们可以在不影响客户端的情况下相互替换。 |
模板方法 | 框架 | 定义一个操作中的算法骨架,让其子类来实现算法中的剩余逻辑。 可以不改变算法结构而重新定义其某些特定步骤。 |
访问者 | 数据与操作分离 | 封装作用与某对象结构中元素的操作。 可以在不改变元素类的前提下,定义或修改作用于这些元素的操作。 |
模式 | 适用性 |
---|---|
责任链 |
|
命令 |
|
解释器 |
|
迭代器 |
|
中介者 |
|
备忘录 |
|
观察者 |
|
状态 |
|
策略 |
|
模板方法 |
|
访问者 |
|
个人理解的方式:
模式 | 简述 |
---|---|
工厂方法 | 具体工厂(工厂接口的实现)创建具体对象。 |
抽象工厂 | 一个具体工厂(抽象工厂的子类)创建多个产品,不同工厂用不同方式创建这一系列产品。 |
生成器 | 复杂对象通过切换构建construct(Builder) 来创建不同表示。 |
原型 | 多个原型之间通过克隆Prototype.clone() 来复制对象。 |
单例 | 通过私有化无参构造方法、静态Singleton instance 属性和静态getInstance() 方法使对象保持单例。 |
适配器 | 适配器继承目标类,重写目标类的方法,方法将不兼容的接口包装成与目标类一致的接口。 |
桥接 | 将产品(抽象)与其某属性(实现)分开,通过桥接(组合)产品与其属性独立出来的类来制造不同产品。 |
组合 | 用树形结构和一致的抽象类让部分和整体的操作一致。如文件树。 |
装饰器 | 装饰器继承被装饰类,通过构造器传入被装饰对象,然后在与被装饰类一致的方法中添加新操作。多个装饰器嵌套可组成一条装饰链。 |
外观 | 复杂子系统有很多操作,外观将其简化。跟适配器很像。 |
享元 | 让一个对象假装成许多个对象。就是很多个对象之间可能有一部分属性的值是一样的,定义一个对象然后共享这部分属性。 |
代理 | 代理就是给被代理对象加一些操作,跟适配器和外观不同的是代理的接口是与被代理对象一致的。 |
责任链 | 有多个接口一致的对象,将请求在这些对象间层层转发,请求可以被其中一个对象处理(JavaWeb里的过滤器)。和嵌套的装饰器很像。 |
命令 | 就是一个命令一个对象,调用这些对象的方式一致(对象接口一致)。 |
解释器 | 解释语言的上下文。 |
迭代器 | 在集合对象的外部,通过迭代器访问集合中的元素,对应的迭代器可由集合对象给出iterator() 。跟Java里的迭代器一个样。 |
中介者 | 有多个类似对象,这些对象通过中介互相发送消息(就好像微信聊天一样,微信就是中介)。 |
备忘录 | 捕获并保存对象的内部状态,并且可以恢复到原型保存的状态。 |
观察者 | 在目标对象状态更新时,观察者们可以收到通知update() ,然后更新自身状态,与目标对象的状态保持一致。 |
状态 | 一个状态一个类,在状态中通过判断变换到其它状态。 |
策略 | 就是动态切换算法。 |
模板方法 | 模板实现算法操作中不变的部分,其余的交给子类去实现。 |
访问者 | 就是在Visitor.visit(访问对象) 中定义对象的操作,然后在结构类中提供accept(Visitor) 来访问这些对象。 |
评论