GoF 的 23 种设计模式的分类和功能
设计模式有两种分类方法,即根据模式的目的来分和根据模式的作用的范围来分。
- 根据目的来分
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
- 根据作用范围来分
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
表 1 介绍了这 23 种设计模式的分类。
表1GoF 的 23 种设计模式的分类表
范围\目的 创建型模式 结构型模式 行为型模式
类模式 工厂方法 (类)适配器 模板方法、解释器
对象模式 单例
原型
抽象工厂
建造者 代理
(对象)适配器
桥接
装饰
外观
享元
组合 策略
命令
职责链
状态
观察者
中介者
迭代器
访问者
备忘录
- GoF的23种设计模式的功能
前面说明了 GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍。
单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
必须指出,这 23 种设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大的系统开发中常常同时使用多种设计模式,希望读者认真学好它们。
UML中的类图及类图之间的关系
UML工具 Rational Rose, Umlet
类、接口和类图
- 类
在 UML 中,类使用包含类名、属性和操作且带有分隔线的矩形来表示。
(1) 类名(Name)是一个字符串,例如,Student。
(2) 属性(Attribute)是指类的特性,即类的成员变量。UML 按以下格式表示:
[可见性]属性名:类型[=默认值]
例如:-name:String
注意:“可见性”表示该属性对类外的元素是否可见,包括公有(Public)、私有(Private)、受保护(Protected)和朋友(Friendly)4 种,在类图中分别用符号+、-、#、~表示。
(3) 操作(Operations)是类的任意一个实例对象都可以使用的行为,是类的成员方法。UML 按以下格式表示:
[可见性]名称(参数列表)[:返回类型]
例如:+display():void。
图 1 所示是学生类的 UML 表示。
图1 Student 类
- 接口
接口(Interface)是一种特殊的类,它具有类的结构但不可被实例化,只可以被子类实现。它包含抽象操作,但不包含属性。它描述了类或组件对外可见的动作。在 UML 中,接口使用一个带有名称的小圆圈来进行表示。
图 2 所示是图形类接口的 UMDL 表示。
- 类图
类图(ClassDiagram)是用来显示系统中的类、接口、协作以及它们之间的静态结构和关系的一种静态模型。它主要用于描述软件系统的结构化设计,帮助人们简化对软件系统的理解,它是系统分析与设计阶段的重要产物,也是系统编码与测试的重要模型依据。
类图中的类可以通过某种编程 语言直接实现。类图在软件系统开发的整个生命周期都是有效的,它是面向对象系统的建模中最常见的图。图 3 所示是“计算长方形和圆形的周长与面积”的类图,图形接口有计算面积和周长的抽象方法,长方形和圆形实现这两个方法供访问类调用。
图3 “计算长方形和圆形的周长与面积”的类图
类之间的关系
在软件系统中,类不是孤立存在的,类与类之间存在各种关系。根据类与类之间的耦合度从弱到强排列,UML 中的类图有以下几种关系:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系。其中泛化和实现的耦合度相等,它们是最强的。
- 依赖关系
就是局部变量中引用了其他对象, 关联关系最弱
依赖(Dependency)关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。图 4 所示是人与手机的关系图,人通过手机的语音传送方法打电话。
2.关联关系
两个对象之间互相引用
关联(Association)关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。
关联可以是双向的,也可以是单向的。在 UML 类图中,双向的关联可以用带两个箭头或者没有箭头的实线来表示,单向的关联用带一个箭头的实线来表示,箭头从使用类指向被关联的类。也可以在关联线的两端标注角色名,代表两种不同的角色。
在代码中通常将一个类的对象作为另一个类的成员变量来实现关联关系。图 5 所示是老师和学生的关系图,每个老师可以教多个学生,每个学生也可向多个老师学,他们是双向关联。
图5 关联关系的实例
3.聚合关系
一对多的关系, 大学内有多个教师
聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。 一对多的关系, 一个大学有多个教师
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。图 6 所示是大学和教师的关系图。
图6 聚合关系的实例
4.组合关系
一个类必须依赖于另一个类才能实现价值 就是主dto对象和子dto对象那种
组合(Composition)关系也是关联关系的一种,也表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系,是 cxmtains-a 关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。图 7 所示是头和嘴的关系图。
图7 组合关系的实例
5.泛化关系
继承
泛化(Generalization)关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是 is-a 的关系。
在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如图 8 所示。
图8 泛化关系的实例
6.实现关系
实现
实现(Realization)关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。
图9 实现关系的实例
开闭原则——面向对象设计原则
为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 7 条原则来开发程序,
开闭原则的定义
软件实体应当对扩展开放,对修改关闭 这就是开闭原则的经典定义。
这里的软件实体包括以下几个部分:
项目中划分出的模块
类与接口
方法
开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
开闭原则的作用
开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。具体来说,其作用如下。
- 对软件测试的影响
软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。 - 可以提高代码的可复用性
粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。 - 可以提高软件的可维护性
遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。
开闭原则的实现方法
可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
里氏替换原则——面向对象设计原则
里氏替换原则的定义
继承必须确保超类所拥有的性质在子类中仍然成立
由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
里氏替换原则的作用
里氏替换原则的主要作用如下。
里氏替换原则是实现开闭原则的重要方式之一。
它克服了继承中重写父类造成的可复用性变差的缺点。
它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
里氏替换原则的实现方法
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误。这时其修正方法是:取消原来的继承关系,重新设计它们之间的关系。
依赖倒置原则——面向对象设计原则
依赖倒置原则的定义
其核心思想是:要面向接口编程,不要面向实现编程。
依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
每个类尽量提供接口或抽象类,或者两者都具备。
变量的声明类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
使用继承时尽量遵循里氏替换原则。
单一职责原则——面向对象设计原则
单一职责原则的定义
一个类就干一种事情
单一职责原则规定一个类应该有且仅有一个引起它变化的原因
该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:
一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。
单一职责原则的优点
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。
降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
提高类的可读性。复杂性降低,自然其可读性会提高。
提高系统的可维护性。可读性提高,那自然更容易维护了。
变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。
单一职责原则的实现方法
单一职责原则是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
接口隔离原则——面向对象设计原则
接口隔离原则的定义
将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
迪米特法则——面向对象设计原则
迪米特法则的定义
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
从依赖者的角度来说,只依赖应该依赖的对象。
从被依赖者的角度说,只暴露应该暴露的方法。
所以,在运用迪米特法则时要注意以下 6 点。
在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
在类的结构设计上,尽量降低类成员的访问权限。
在类的设计上,优先考虑将一个类设置成不变类。
在对其他类的引用上,将引用其他对象的次数降到最低。
不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
谨慎使用序列化(Serializable)功能。
合成复用原则——面向对象设计原则
合成复用原则的定义
合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
合成复用原则的重要性
通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。
继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。
它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
合成复用原则的实现方法
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
创建型模式的特点和分类
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
创建型模式分为以下几种。
单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
以上 5 种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式,我们将在之后的教程中详细地介绍它们的特点、结构与应用。
**单例模式
单例模式的结构与实现
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
主要实现
双重检查锁
静态内部类
原型模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型模式的定义与特点
原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。例如,Windows 操作系统的安装通常较耗时,如果复制就快了很多。在生活中复制的例子非常多,这里不一一列举了。
原型模式的结构与实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
- 模式的结构
原型模式包含以下主要角色。
抽象原型类:规定了具体原型对象必须实现的接口。
具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
访问类:使用具体原型类中的 clone() 方法来复制新的对象。
其结构图如图 1 所示。
图1 原型模式的结构图
2. 模式的实现
原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。其代码如下:
- //具体原型类
- class Realizetype implements Cloneable
- {
-
Realizetype()
-
{
-
System.out.println("具体原型创建成功!");
-
}
-
public Object clone() throws CloneNotSupportedException
-
{
-
System.out.println("具体原型复制成功!");
-
return (Realizetype)super.clone();
-
}
- }
- //原型模式的测试类
- public class PrototypeTest
- {
-
public static void main(String[] args)throws CloneNotSupportedException
-
{
-
Realizetype obj1=new Realizetype();
-
Realizetype obj2=(Realizetype)obj1.clone();
-
System.out.println("obj1==obj2?"+(obj1==obj2));
-
}
- }
程序的运行结果如下:
具体原型创建成功!
具体原型复制成功!
obj1==obj2?false
原型模式的应用场景
原型模式通常适用于以下场景。
对象之间相同或相似,即只是个别的几个属性不同的时候。
对象的创建过程比较麻烦,但复制比较简单的时候。
原型模式的扩展
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。其结构图如图 5 所示。
**工厂方法模式
模式的定义与特点
工厂方法(FactoryMethod)模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于 GoF 的 23 种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。
本节介绍的“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
工厂方法模式的主要优点有:
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
其缺点是:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
模式的结构与实现
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。本节来分析其基本结构和实现方法。
- 模式的结构
工厂方法模式的主要角色如下。
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
其结构图如图 1 所示。
图1 工厂方法模式的结构图
2. 模式的实现
根据图 1 写出该模式的代码如下:
- package FactoryMethod;
- public class AbstractFactoryTest
- {
-
public static void main(String[] args)
-
{
-
try
-
{
-
Product a;
-
AbstractFactory af;
-
af=(AbstractFactory) ReadXML1.getObject();
-
a=af.newProduct();
-
a.show();
-
}
-
catch(Exception e)
-
{
-
System.out.println(e.getMessage());
-
}
-
}
- }
- //抽象产品:提供了产品的接口
- interface Product
- {
-
public void show();
- }
- //具体产品1:实现抽象产品中的抽象方法
- class ConcreteProduct1 implements Product
- {
-
public void show()
-
{
-
System.out.println("具体产品1显示...");
-
}
- }
- //具体产品2:实现抽象产品中的抽象方法
- class ConcreteProduct2 implements Product
- {
-
public void show()
-
{
-
System.out.println("具体产品2显示...");
-
}
- }
- //抽象工厂:提供了厂品的生成方法
- interface AbstractFactory
- {
-
public Product newProduct();
- }
- //具体工厂1:实现了厂品的生成方法
- class ConcreteFactory1 implements AbstractFactory
- {
-
public Product newProduct()
-
{
-
System.out.println("具体工厂1生成-->具体产品1...");
-
return new ConcreteProduct1();
-
}
- }
- //具体工厂2:实现了厂品的生成方法
- class ConcreteFactory2 implements AbstractFactory
- {
-
public Product newProduct()
-
{
-
System.out.println("具体工厂2生成-->具体产品2...");
-
return new ConcreteProduct2();
-
}
- }
- package FactoryMethod;
- import javax.xml.parsers.*;
- import org.w3c.dom.*;
- import java.io.*;
- class ReadXML1
- {
-
//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
-
public static Object getObject()
-
{
-
try
-
{
-
//创建文档对象
-
DocumentBuilderFactory dFactory=DocumentBuilderFactory.newInstance();
-
DocumentBuilder builder=dFactory.newDocumentBuilder();
-
Document doc;
-
doc=builder.parse(new File("src/FactoryMethod/config1.xml"));
-
//获取包含类名的文本节点
-
NodeList nl=doc.getElementsByTagName("className");
-
Node classNode=nl.item(0).getFirstChild();
-
String cName="FactoryMethod."+classNode.getNodeValue();
-
//System.out.println("新类名:"+cName);
-
//通过类名生成实例对象并将其返回
-
Class<?> c=Class.forName(cName);
-
Object obj=c.newInstance();
-
return obj;
-
}
-
catch(Exception e)
-
{
-
e.printStackTrace();
-
return null;
-
}
-
}
- }
注意:该程序中用到了 XML 文件,如果想要获取该文件,请点击“下载”,就可以对其进行下载。
程序运行结果如下:
具体工厂1生成–>具体产品1…
具体产品1显示…
如果将 XML 配置文件中的 ConcreteFactory1 改为 ConcreteFactory2,则程序运行结果如下:
具体工厂2生成–>具体产品2…
具体产品2显示…
模式的应用场景
工厂方法模式通常适用于以下场景。
客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
客户不关心创建产品的细节,只关心产品的品牌。
模式的扩展
当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式,其结构图如图 4 所示。
抽象工厂模式
个人理解
工厂模式就是针对一个产品进行生产, 抽象工厂就是针对多个产品进行的生产
模式的定义与特点
抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。
可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当增加一个新的产品族时不需要修改原代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
模式的结构与实现
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。现在我们来分析其基本结构和实现方法。
- 模式的结构
抽象工厂模式的主要角色如下。
抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
抽象工厂模式的结构图如图 2 所示。
图2 抽象工厂模式的结构图
2. 模式的实现
从图 2 可以看出抽象工厂模式的结构同工厂方法模式的结构相似,不同的是其产品的种类不止一个,所以创建产品的方法也不止一个。下面给出抽象工厂和具体工厂的代码。
(1) 抽象工厂:提供了产品的生成方法。
- interface AbstractFactory
- {
-
public Product1 newProduct1();
-
public Product2 newProduct2();
- }
(2) 具体工厂:实现了产品的生成方法。
纯文本复制
- class ConcreteFactory1 implements AbstractFactory
- {
-
public Product1 newProduct1()
-
{
-
System.out.println("具体工厂 1 生成-->具体产品 11...");
-
return new ConcreteProduct11();
-
}
-
public Product2 newProduct2()
-
{
-
System.out.println("具体工厂 1 生成-->具体产品 21...");
-
return new ConcreteProduct21();
-
}
- }
模式的应用场景
抽象工厂模式最早的应用是用于创建属于不同操作系统的视窗构件。如 java 的 AWT 中的 Button 和 Text 等构件在 Windows 和 UNIX 中的本地实现是不同的。
抽象工厂模式通常适用于以下场景:
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
模式的扩展
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
**建造者模式(Bulider模式)
网络文章
http://m.biancheng.net/view/1354.html
https://www.iteye.com/blog/chuichuige-2439021
https://blog.csdn.net/wwwdc1012/article/details/82660567
个人理解
分为两种实现情况,
第一种: 将组装各个组件的操作, 组装什么组件 都定义在具体建造者, 客户端不需要知道,
第二种: 客户端指定自己要建造的组件的值是什么, 建造者只提供方法
中间涉及了几个主要方法, 如果getResult, setPartX, 等在不同的代码中有不同方式实现,
其中StringBuilder中的实现方式就有所不同, 可以参考网络文章中的第三个
要想线程之间不共享呢,可以参考StringBuilder, new对象, 然后调用append方法, 方法返回当前对象, 返回当前对象很适合链式编程
模式的定义与特点
建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
该模式的主要优点如下:
各个具体的建造者相互独立,有利于系统的扩展。
客户端不必知道产品内部组成的细节,便于控制细节风险。
其缺点如下:
产品的组成部分必须相同,这限制了其使用范围。
如果产品的内部变化复杂,该模式会增加很多的建造者类。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
模式的结构与实现
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。
- 模式的结构
建造者(Builder)模式的主要角色如下。
产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个子部件。
抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
其结构图如图 1 所示。
- 模式的实现
图 1 给出了建造者(Builder)模式的主要结构,其相关类的代码如下。
(1) 产品角色:包含多个组成部件的复杂对象。 - class Product
- {
-
private String partA;
-
private String partB;
-
private String partC;
-
public void setPartA(String partA)
-
{
-
this.partA=partA;
-
}
-
public void setPartB(String partB)
-
{
-
this.partB=partB;
-
}
-
public void setPartC(String partC)
-
{
-
this.partC=partC;
-
}
-
public void show()
-
{
-
//显示产品的特性
-
}
- }
(2) 抽象建造者:包含创建产品各个子部件的抽象方法。
- 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;
-
}
- }
(3) 具体建造者:实现了抽象建造者接口。
- public class ConcreteBuilder extends Builder
- {
-
public void buildPartA()
-
{
-
product.setPartA("建造 PartA");
-
}
-
public void buildPartB()
-
{
-
product.setPartA("建造 PartB");
-
}
-
public void buildPartC()
-
{
-
product.setPartA("建造 PartC");
-
}
- }
(4) 指挥者:调用建造者中的方法完成复杂对象的创建。
- class Director
- {
-
private Builder builder;
-
public Director(Builder builder)
-
{
-
this.builder=builder;
-
}
-
//产品构建与组装方法
-
public Product construct()
-
{
-
builder.buildPartA();
-
builder.buildPartB();
-
builder.buildPartC();
-
return builder.getResult();
-
}
- }
(5) 客户类。
纯文本复制
- public class Client
- {
-
public static void main(String[] args)
-
{
-
Builder builder=new ConcreteBuilder();
-
Director director=new Director(builder);
-
Product product=director.construct();
-
product.show();
-
}
- }
模式的应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
模式的扩展
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。
由客户端指定值的方式
代码思路由main方法中调用展开, 通过create和builder方法创建
这个代码中的builder方法, 可以用来检查对象的合法性, 也可以不使用内部类, 之间create时候返回外部类, 然后在builder中检查外部类的合法性即可
此类方式面向具体编程了, 不适合扩展, 可以参考建造者模式的定义进行改造, 或者参考
StringBuilder进行改造
Java代码
- package com.zhaoyou.tars.common;
- /**
-
- @author: Mr_Q
-
- @create: 2019-03-12 11:40
-
- @program: basic-client-test
-
- @description:
- **/
- public class HeaderReqVo {
-
/***
-
*
-
* "version": // 版本号
-
* "traceId"://贯穿始终的ID, 调用方传
-
* "timestamp": //自己的当前时间
-
* "id": 自己的服务ID
-
* "method":com.zhaoyou.osd.service.osp.xxx //自己方法名
-
* "ip"://自己的IP
-
*
-
*
-
*/
-
private final String version;
-
private final String traceId;
-
private final String timestamp;
-
private final String id;
-
private final String method;
-
private final String ip;
-
public static HeaderInner create(){
-
return new HeaderInner();
-
}
-
public static final class HeaderInner {
-
private String version;
-
private String traceId;
-
private String timestamp;
-
private String id;
-
private String method;
-
private String ip;
-
public HeaderInner setVersion(String version) {
-
this.version = version;
-
return this;
-
}
-
public HeaderInner setTraceId(String traceId) {
-
this.traceId = traceId;
-
return this;
-
}
-
public HeaderInner setTimestamp(String timestamp) {
-
this.timestamp = timestamp;
-
return this;
-
}
-
public HeaderInner setId(String id) {
-
this.id = id;
-
return this;
-
}
-
public HeaderInner setMethod(String method) {
-
this.method = method;
-
return this;
-
}
-
public HeaderInner setIp(String ip) {
-
this.ip = ip;
-
return this;
-
}
-
public HeaderReqVo build() {
-
return new HeaderReqVo(this);
-
}
-
}
-
private HeaderReqVo(HeaderInner builder) {
-
this.version = builder.version;
-
this.traceId = builder.traceId;
-
this.timestamp = builder.timestamp;
-
this.id = builder.id;
-
this.method = builder.method;
-
this.ip = builder.ip;
-
}
-
@Override
-
public String toString() {
-
return "HeaderReqVo{" +
-
"version='" + version + '\'' +
-
", traceId='" + traceId + '\'' +
-
", timestamp='" + timestamp + '\'' +
-
", id='" + id + '\'' +
-
", method='" + method + '\'' +
-
", ip='" + ip + '\'' +
-
'}';
-
}
- }
测试:
Java代码
- public static void main(String[] args) {
-
ExecutorService executorService = Executors.newFixedThreadPool(10);
-
executorService.execute(()->{
-
HeaderReqVo getUserInfo = HeaderReqVo.create().setVersion("1.0").setTraceId("user.server").setTimestamp(String.valueOf(System.currentTimeMillis())).setId("111").setMethod("getUserInfo").setIp("127.0.0.1").build();
-
System.out.println(getUserInfo);
-
});
-
executorService.execute(()->{
-
HeaderReqVo getUserInfo = HeaderReqVo.create().setVersion("2.0").setTraceId("user.server2").setTimestamp(String.valueOf(System.currentTimeMillis())).setId("222").setMethod("getUserInfo2").setIp("127.0.0.2").build();
-
System.out.println(getUserInfo);
-
});
- }
结构型模式概述
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模式分为以下 7 种:
代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
以上 7 种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式,下面我们会分别、详细地介绍它们的特点、结构与应用。
**代理模式
代理模式的定义与特点
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
增加了系统的复杂度;
代理模式的结构与实现
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。
- 模式的结构
代理模式的主要角色如下。
抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图 1 所示。
图1 代理模式的结构图
2. 模式的实现
代理模式的实现代码如下:
- package proxy;
- public class ProxyTest
- {
-
public static void main(String[] args)
-
{
-
Proxy proxy=new Proxy();
-
proxy.Request();
-
}
- }
- //抽象主题
- interface Subject
- {
-
void Request();
- }
- //真实主题
- class RealSubject implements Subject
- {
-
public void Request()
-
{
-
System.out.println("访问真实主题方法...");
-
}
- }
- //代理
- class Proxy implements Subject
- {
-
private RealSubject realSubject;
-
public void Request()
-
{
-
if (realSubject==null)
-
{
-
realSubject=new RealSubject();
-
}
-
preRequest();
-
realSubject.Request();
-
postRequest();
-
}
-
public void preRequest()
-
{
-
System.out.println("访问真实主题之前的预处理。");
-
}
-
public void postRequest()
-
{
-
System.out.println("访问真实主题之后的后续处理。");
-
}
- }
程序运行的结果如下:
访问真实主题之前的预处理。
访问真实主题方法…
访问真实主题之后的后续处理。
代理模式的应用场景
前面分析了代理模式的结构与特点,现在来分析以下的应用场景。
远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
代理模式的扩展
在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。
真实主题与代理主题一一对应,增加真实主题也要增加代理。
设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP,其结构图如图 4 所示。
图4 动态代理模式的结构图
**适配器模式
个人理解
就是给目标的方法调用转为适配者的方法调用, 客户端只调用适配器就可
模式的定义与特点
适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。
客户端通过适配器可以透明地调用目标接口。
复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
其缺点是:对类适配器来说,更换适配器的实现过程比较复杂。
模式的结构与实现
类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构。
- 模式的结构
适配器模式(Adapter)包含以下主要角色。
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
类适配器模式的结构图如图 1 所示。
图1 类适配器模式的结构图
对象适配器模式的结构图如图 2 所示。
图2 对象适配器模式的结构图
2. 模式的实现
(1) 类适配器模式的代码如下。
- package adapter;
- //目标接口
- interface Target
- {
-
public void request();
- }
- //适配者接口
- class Adaptee
- {
-
public void specificRequest()
-
{
-
System.out.println("适配者中的业务代码被调用!");
-
}
- }
- //类适配器类
- class ClassAdapter extends Adaptee implements Target
- {
-
public void request()
-
{
-
specificRequest();
-
}
- }
- //客户端代码
- public class ClassAdapterTest
- {
-
public static void main(String[] args)
-
{
-
System.out.println("类适配器模式测试:");
-
Target target = new ClassAdapter();
-
target.request();
-
}
- }
程序的运行结果如下:
类适配器模式测试:
适配者中的业务代码被调用!
(2)对象适配器模式的代码如下。
- package adapter;
- //对象适配器类
- class ObjectAdapter implements Target
- {
-
private Adaptee adaptee;
-
public ObjectAdapter(Adaptee adaptee)
-
{
-
this.adaptee=adaptee;
-
}
-
public void request()
-
{
-
adaptee.specificRequest();
-
}
- }
- //客户端代码
- public class ObjectAdapterTest
- {
-
public static void main(String[] args)
-
{
-
System.out.println("对象适配器模式测试:");
-
Adaptee adaptee = new Adaptee();
-
Target target = new ObjectAdapter(adaptee);
-
target.request();
-
}
- }
说明:对象适配器模式中的“目标接口”和“适配者类”的代码同类适配器模式一样,只要修改适配器类和客户端的代码即可。
程序的运行结果如下:
对象适配器模式测试:
适配者中的业务代码被调用!
模式的应用场景
适配器模式(Adapter)通常适用于以下场景。
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
模式的扩展
适配器模式(Adapter)可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口,其结构图如图 4 所示。
图4 双向适配器模式的结构图
程序代码如下:
- package adapter;
- //目标接口
- interface TwoWayTarget
- {
-
public void request();
- }
- //适配者接口
- interface TwoWayAdaptee
- {
-
public void specificRequest();
- }
- //目标实现
- class TargetRealize implements TwoWayTarget
- {
-
public void request()
-
{
-
System.out.println("目标代码被调用!");
-
}
- }
- //适配者实现
- class AdapteeRealize implements TwoWayAdaptee
- {
-
public void specificRequest()
-
{
-
System.out.println("适配者代码被调用!");
-
}
- }
- //双向适配器
- class TwoWayAdapter implements TwoWayTarget,TwoWayAdaptee
- {
-
private TwoWayTarget target;
-
private TwoWayAdaptee adaptee;
-
public TwoWayAdapter(TwoWayTarget target)
-
{
-
this.target=target;
-
}
-
public TwoWayAdapter(TwoWayAdaptee adaptee)
-
{
-
this.adaptee=adaptee;
-
}
-
public void request()
-
{
-
adaptee.specificRequest();
-
}
-
public void specificRequest()
-
{
-
target.request();
-
}
- }
- //客户端代码
- public class TwoWayAdapterTest
- {
-
public static void main(String[] args)
-
{
-
System.out.println("目标通过双向适配器访问适配者:");
-
TwoWayAdaptee adaptee=new AdapteeRealize();
-
TwoWayTarget target=new TwoWayAdapter(adaptee);
-
target.request();
-
System.out.println("-------------------");
-
System.out.println("适配者通过双向适配器访问目标:");
-
target=new TargetRealize();
-
adaptee=new TwoWayAdapter(target);
-
adaptee.specificRequest();
-
}
- }
程序的运行结果如下:
目标通过双向适配器访问适配者:
适配者代码被调用!
适配者通过双向适配器访问目标:
目标代码被调用!
桥接模式(Bridge模式)
个人理解
主要就是使用组合模式来完成内部的调用,抽象化角色持有实现化对象的引用,从而产生调用
桥接模式的定义与特点
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式的优点是:
由于抽象与实现分离,所以扩展能力强;
其实现细节对客户透明。
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
桥接模式的结构与实现
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
- 模式的结构
桥接(Bridge)模式包含以下主要角色。
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
其结构图如图 1 所示。
图1 桥接模式的结构图
2. 模式的实现
桥接模式的代码如下:
- package bridge;
- public class BridgeTest
- {
-
public static void main(String[] args)
-
{
-
Implementor imple=new ConcreteImplementorA();
-
Abstraction abs=new RefinedAbstraction(imple);
-
abs.Operation();
-
}
- }
- //实现化角色
- interface Implementor
- {
-
public void OperationImpl();
- }
- //具体实现化角色
- class ConcreteImplementorA implements Implementor
- {
-
public void OperationImpl()
-
{
-
System.out.println("具体实现化(Concrete Implementor)角色被访问" );
-
}
- }
- //抽象化角色
- abstract class Abstraction
- {
- protected Implementor imple;
- protected Abstraction(Implementor imple)
- {
-
this.imple=imple;
- }
- public abstract void Operation();
- }
- //扩展抽象化角色
- class RefinedAbstraction extends Abstraction
- {
- protected RefinedAbstraction(Implementor imple)
- {
-
super(imple);
- }
- public void Operation()
- {
-
System.out.println("扩展抽象化(Refined Abstraction)角色被访问" );
-
imple.OperationImpl();
- }
- }
程序的运行结果如下:
扩展抽象化(Refined Abstraction)角色被访问
具体实现化(Concrete Implementor)角色被访问
桥接模式的应用场景
桥接模式通常适用于以下场景。
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式模式的扩展
在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其具体结构图如图 5 所示。
图5 桥接模式与适配器模式联用的结构图
装饰模式
将构建角色注入到装饰角色中,客户端调用装饰角色代码, 装饰内部调用构建角色的方法,并通过额外调用来增加功能
装饰模式的定义与特点
装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
装饰(Decorator)模式的主要优点有:
采用装饰模式扩展对象的功能比采用继承方式更加灵活。
可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
其主要缺点是:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
装饰模式的结构与实现
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。
- 模式的结构
装饰模式主要包含以下角色。
抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
装饰模式的结构图如图 1 所示。
图1 装饰模式的结构图
2. 模式的实现
装饰模式的实现代码如下:
- package decorator;
- public class DecoratorPattern
- {
-
public static void main(String[] args)
-
{
-
Component p=new ConcreteComponent();
-
p.operation();
-
System.out.println("---------------------------------");
-
Component d=new ConcreteDecorator(p);
-
d.operation();
-
}
- }
- //抽象构件角色
- interface Component
- {
-
public void operation();
- }
- //具体构件角色
- class ConcreteComponent implements Component
- {
-
public ConcreteComponent()
-
{
-
System.out.println("创建具体构件角色");
-
}
-
public void operation()
-
{
-
System.out.println("调用具体构件角色的方法operation()");
-
}
- }
- //抽象装饰角色
- class Decorator implements Component
- {
-
private Component component;
-
public Decorator(Component component)
-
{
-
this.component=component;
-
}
-
public void operation()
-
{
-
component.operation();
-
}
- }
- //具体装饰角色
- class ConcreteDecorator extends Decorator
- {
-
public ConcreteDecorator(Component component)
-
{
-
super(component);
-
}
-
public void operation()
-
{
-
super.operation();
-
addedFunction();
-
}
-
public void addedFunction()
-
{
-
System.out.println("为具体构件角色增加额外的功能addedFunction()");
-
}
- }
程序运行结果如下:
创建具体构件角色
调用具体构件角色的方法operation()
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()
装饰模式的应用场景
前面讲解了关于装饰模式的结构与特点,下面介绍其适用的应用场景,装饰模式通常在以下几种情况使用。
当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
装饰模式在 Java 语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。
下面代码是为 FileReader 增加缓冲区而采用的装饰类 BufferedReader 的例子:
BufferedReader in=new BufferedReader(new FileReader("filename.txtn));
String s=in.readLine();
装饰模式的扩展
装饰模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的,如以下两种情况。
(1) 如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件,其结构图如图 4 所示。
图4 只有一个具体构件的装饰模式
(2) 如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并,其结构图如图 5 所示。
图5 只有一个具体装饰的装饰模式
**外观模式
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
图 1 给出了客户去当地房产局办理房产证过户要遇到的相关部门。
外观模式的定义与特点
外观(Facade)模式的定义:是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
外观(Facade)模式的主要缺点如下。
不能很好地限制客户使用子系统类。
增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
外观模式的结构与实现
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。
- 模式的结构
外观(Facade)模式包含以下主要角色。
外观(Facade)角色:为多个子系统对外提供一个共同的接口。
子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
客户(Client)角色:通过一个外观角色访问各个子系统的功能。
其结构图如图 2 所示。
- 模式的实现
外观模式的实现代码如下: - package facade;
- public class FacadePattern
- {
-
public static void main(String[] args)
-
{
-
Facade f=new Facade();
-
f.method();
-
}
- }
- //外观角色
- class Facade
- {
-
private SubSystem01 obj1=new SubSystem01();
-
private SubSystem02 obj2=new SubSystem02();
-
private SubSystem03 obj3=new SubSystem03();
-
public void method()
-
{
-
obj1.method1();
-
obj2.method2();
-
obj3.method3();
-
}
- }
- //子系统角色
- class SubSystem01
- {
-
public void method1()
-
{
-
System.out.println("子系统01的method1()被调用!");
-
}
- }
- //子系统角色
- class SubSystem02
- {
-
public void method2()
-
{
-
System.out.println("子系统02的method2()被调用!");
-
}
- }
- //子系统角色
- class SubSystem03
- {
-
public void method3()
-
{
-
System.out.println("子系统03的method3()被调用!");
-
}
- }
程序运行结果如下:
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
外观模式的应用场景
通常在以下情况下可以考虑使用外观模式。
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
外观模式的扩展
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题,其结构图如图 5 所示。
实战使用
controller给前端的多个调用提供一个接口,
IAAS项目中给DCOS提供的LB的接口, 当创建LB时候需要调用创建LB,创建LBL,创建目标组,绑定主机到目标组, 这四个接口,现在给DCOS提供一个接口, 接口中包含了对这四个操作的调用
新DCOS中, 比对功能那种形式可以移植到这里,
创建一个接口,定义一个方法, 每个子系统实现这个方法, 客户端调用时候给提供一个方法调用这个接口的每一个实现, 通过application的getBeansByType(接口.class)来获取每个实现类,然后循环调用实现的方法
享元模式
定义与特点
参考地址 http://m.biancheng.net/view/1371.html
享元(Flyweight)模式的定义:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的又橡来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
享元模式的主要优点是:相同对象只要保存一份
其主要缺点是:
为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
读取享元模式的外部状态会使得运行时间稍微变长。
享元模式的结构与实现
享元模式中存在以下两种状态:
内部状态,即不会随着环境的改变而改变的可共享部分;全都对象共享
外部状态,指随环境改变而改变的不可以共享的部分。 每个对象不同的
享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。下面来分析其基本结构和实现方法。
- 模式的结构
享元模式的主要角色有如下。
抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
图 1 是享元模式的结构图。
图中的 UnsharedConcreteFlyweight 是与非享元角色,里面包含了非共享的外部状态信息 info;
而 Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
FlyweightFactory 是享元工厂角色,它逝关键字 key 来管理具体享元;客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。
- 模式的实现
享元模式的实现代码如下: - package flyweight;
- import java.util.HashMap;
- public class FlyweightPattern
- {
-
public static void main(String[] args)
-
{
-
FlyweightFactory factory=new FlyweightFactory();
-
Flyweight f01=factory.getFlyweight("a");
-
Flyweight f02=factory.getFlyweight("a");
-
Flyweight f03=factory.getFlyweight("a");
-
Flyweight f11=factory.getFlyweight("b");
-
Flyweight f12=factory.getFlyweight("b");
-
f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
-
f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
-
f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
-
f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
-
f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
-
}
- }
- //非享元角色
- class UnsharedConcreteFlyweight
- {
-
private String info;
-
UnsharedConcreteFlyweight(String info)
-
{
-
this.info=info;
-
}
-
public String getInfo()
-
{
-
return info;
-
}
-
public void setInfo(String info)
-
{
-
this.info=info;
-
}
- }
- //抽象享元角色
- interface Flyweight
- {
-
public void operation(UnsharedConcreteFlyweight state);
- }
- //具体享元角色
- class ConcreteFlyweight implements Flyweight
- {
-
private String key;
-
ConcreteFlyweight(String key)
-
{
-
this.key=key;
-
System.out.println("具体享元"+key+"被创建!");
-
}
-
public void operation(UnsharedConcreteFlyweight outState)
-
{
-
System.out.print("具体享元"+key+"被调用,");
-
System.out.println("非享元信息是:"+outState.getInfo());
-
}
- }
- //享元工厂角色
- class FlyweightFactory
- {
-
private HashMap<String, Flyweight> flyweights=new HashMap<String, Flyweight>();
-
public Flyweight getFlyweight(String key)
-
{
-
Flyweight flyweight=(Flyweight)flyweights.get(key);
-
if(flyweight!=null)
-
{
-
System.out.println("具体享元"+key+"已经存在,被成功获取!");
-
}
-
else
-
{
-
flyweight=new ConcreteFlyweight(key);
-
flyweights.put(key, flyweight);
-
}
-
return flyweight;
-
}
- }
程序运行结果如下:
具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。
享元模式的应用场景
前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。
系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
享元模式的扩展
在前面介绍的享元模式中,其结构图通常包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,
即存在两种特殊的享元模式:
单纯享元模式和复合享元模式,下面分别对它们进行简单介绍。
(1) 单纯享元模式
,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类,其结构图如图 4 所示
(2) 复合享元模式
,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享,其结构图如图 5 所示。 具体享元对象2中的list集合保存了多个享元对象
总结
其实就是所有的操作的对象都指向享元对象, 每次都返回已经存在的享元对象, 通过调用接口的方法, 来设置每次不同的属性, 相同的属性可以在构造器或者方法中赋值都可以,
使用享元对象时候基本就是调用工厂方法获取对象, 然后调用接口方法设置对象特有的属性, 之后就可以使用了, 但是需要考虑并发问题,
由于存在并发安全性, 所以可以使用单纯享元模式即可
组合模式
行为型模式概述
模板方法模式
主要就是定义一个方法,调用一堆抽象方法,从而确定这么一个模板, 具体的方法由子类实现
模式的定义与特点
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
该模式的主要优点如下。
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
该模式的主要缺点如下。
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
模式的结构与实现
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。
- 模式的结构
模板方法模式包含以下主要角色。
(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
模板方法模式的结构图如图 1 所示。
图1 模板方法模式的结构图
2. 模式的实现
模板方法模式的代码如下:
- package templateMethod;
- public class TemplateMethodPattern
- {
-
public static void main(String[] args)
-
{
-
AbstractClass tm=new ConcreteClass();
-
tm.TemplateMethod();
-
}
- }
- //抽象类
- abstract class AbstractClass
- {
-
public void TemplateMethod() //模板方法
-
{
-
SpecificMethod();
-
abstractMethod1();
-
abstractMethod2();
-
}
-
public void SpecificMethod() //具体方法
-
{
-
System.out.println("抽象类中的具体方法被调用...");
-
}
-
public abstract void abstractMethod1(); //抽象方法1
-
public abstract void abstractMethod2(); //抽象方法2
- }
- //具体子类
- class ConcreteClass extends AbstractClass
- {
-
public void abstractMethod1()
-
{
-
System.out.println("抽象方法1的实现被调用...");
-
}
-
public void abstractMethod2()
-
{
-
System.out.println("抽象方法2的实现被调用...");
-
}
- }
程序的运行结果如下:
抽象类中的具体方法被调用…
抽象方法1的实现被调用…
抽象方法2的实现被调用…
模式的应用场景
模板方法模式通常适用于以下场景。
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
模式的扩展
在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。
图3 含钩子方法的模板方法模式的结构图
程序代码如下:
- package templateMethod;
- public class HookTemplateMethod
- {
-
public static void main(String[] args)
-
{
-
HookAbstractClass tm=new HookConcreteClass();
-
tm.TemplateMethod();
-
}
- }
- //含钩子方法的抽象类
- abstract class HookAbstractClass
- {
-
public void TemplateMethod() //模板方法
-
{
-
abstractMethod1();
-
HookMethod1();
-
if(HookMethod2())
-
{
-
SpecificMethod();
-
}
-
abstractMethod2();
-
}
-
public void SpecificMethod() //具体方法
-
{
-
System.out.println("抽象类中的具体方法被调用...");
-
}
-
public void HookMethod1(){} //钩子方法1
-
public boolean HookMethod2() //钩子方法2
-
{
-
return true;
-
}
-
public abstract void abstractMethod1(); //抽象方法1
-
public abstract void abstractMethod2(); //抽象方法2
- }
- //含钩子方法的具体子类
- class HookConcreteClass extends HookAbstractClass
- {
-
public void abstractMethod1()
-
{
-
System.out.println("抽象方法1的实现被调用...");
-
}
-
public void abstractMethod2()
-
{
-
System.out.println("抽象方法2的实现被调用...");
-
}
-
public void HookMethod1()
-
{
-
System.out.println("钩子方法1被重写...");
-
}
-
public boolean HookMethod2()
-
{
-
return false;
-
}
- }
程序的运行结果如下:
抽象方法1的实现被调用…
钩子方法1被重写…
抽象方法2的实现被调用…
如果钩子方法 HookMethod1() 和钩子方法 HookMethod2() 的代码改变,则程序的运行结果也会改变。
**策略模式
通过给环境注入不同的策略来实现不同情况下的调用
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。
策略模式的定义与特点
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下。
多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
其主要缺点如下。
客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
策略模式造成很多的策略类。
策略模式的结构与实现
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法。
- 模式的结构
策略模式的主要角色如下。
抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
其结构图如图 1 所示。
图1 策略模式的结构图
2. 模式的实现
策略模式的实现代码如下:
- package strategy;
- public class StrategyPattern
- {
-
public static void main(String[] args)
-
{
-
Context c=new Context();
-
Strategy s=new ConcreteStrategyA();
-
c.setStrategy(s);
-
c.strategyMethod();
-
System.out.println("-----------------");
-
s=new ConcreteStrategyB();
-
c.setStrategy(s);
-
c.strategyMethod();
-
}
- }
- //抽象策略类
- interface Strategy
- {
-
public void strategyMethod(); //策略方法
- }
- //具体策略类A
- class ConcreteStrategyA implements Strategy
- {
-
public void strategyMethod()
-
{
-
System.out.println("具体策略A的策略方法被访问!");
-
}
- }
- //具体策略类B
- class ConcreteStrategyB implements Strategy
- {
- public void strategyMethod()
- {
-
System.out.println("具体策略B的策略方法被访问!");
- }
- }
- //环境类
- class Context
- {
-
private Strategy strategy;
-
public Strategy getStrategy()
-
{
-
return strategy;
-
}
-
public void setStrategy(Strategy strategy)
-
{
-
this.strategy=strategy;
-
}
-
public void strategyMethod()
-
{
-
strategy.strategyMethod();
-
}
- }
程序运行结果如下:
具体策略A的策略方法被访问!
具体策略B的策略方法被访问!
策略模式的应用场景
策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
策略模式的扩展
在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度,其结构图如图 5 所示。
图5 策略工厂模式的结构图
命令模式
网络文章
http://c.biancheng.net/view/1380.html
个人理解
主要就是调用者持有命令对象, 命令对象持有接收者对象, 从而可以使调用者调用接收者的中间有一个命令对象,
每个具体命令持有每个具体接收者的引用, 从而产生调用关系
命令模式的定义与特点
命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式的主要优点如下。
降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
命令模式的结构与实现
可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。
- 模式的结构
命令模式包含以下主要角色。
抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。
具体命令角色(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。
调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
其结构图如图 1 所示。
- 模式的实现
命令模式的代码如下: - package command;
- public class CommandPattern
- {
-
public static void main(String[] args)
-
{
-
Command cmd=new ConcreteCommand();
-
Invoker ir=new Invoker(cmd);
-
System.out.println("客户访问调用者的call()方法...");
-
ir.call();
-
}
- }
- //调用者
- class Invoker
- {
-
private Command command;
-
public Invoker(Command command)
-
{
-
this.command=command;
-
}
-
public void setCommand(Command command)
-
{
-
this.command=command;
-
}
-
public void call()
-
{
-
System.out.println("调用者执行命令command...");
-
command.execute();
-
}
- }
- //抽象命令
- interface Command
- {
-
public abstract void execute();
- }
- //具体命令
- class ConcreteCommand implements Command
- {
-
private Receiver receiver;
-
ConcreteCommand()
-
{
-
receiver=new Receiver();
-
}
-
public void execute()
-
{
-
receiver.action();
-
}
- }
- //接收者
- class Receiver
- {
-
public void action()
-
{
-
System.out.println("接收者的action()方法被调用...");
-
}
- }
程序的运行结果如下:
客户访问调用者的call()方法…
调用者执行命令command…
接收者的action()方法被调用…
命令模式的应用场景
命令模式通常适用于以下场景。
当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
命令模式的扩展
在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,其具体结构图如图 3 所示。
当然,命令模式还可以同备忘录(Memento)模式组合使用,这样就变成了可撤销的命令模式,这将在后面介绍。
**责任链模式
通过客户类组装好处理者的调用链,通过ifelse来判断是当前处理还是继续向后调用
例子就是Struts2 的拦截器、JSP 和 Servlet 的 Filter
模式的定义与特点
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
注意:责任链模式也叫职责链模式。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下。
降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
其主要缺点如下。
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
模式的结构与实现
通常情况下,可以通过数据链表来实现职责链模式的数据结构。
- 模式的结构
职责链模式主要包含以下角色。
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
其结构图如图 1 所示。客户端可按图 2 所示设置责任链。
图1 责任链模式的结构图
图2 责任链
2. 模式的实现
职责链模式的实现代码如下:
纯文本复制
- package chainOfResponsibility;
- public class ChainOfResponsibilityPattern
- {
-
public static void main(String[] args)
-
{
-
//组装责任链
-
Handler handler1=new ConcreteHandler1();
-
Handler handler2=new ConcreteHandler2();
-
handler1.setNext(handler2);
-
//提交请求
-
handler1.handleRequest("two");
-
}
- }
- //抽象处理者角色
- abstract class Handler
- {
-
private Handler next;
-
public void setNext(Handler next)
-
{
-
this.next=next;
-
}
-
public Handler getNext()
-
{
-
return next;
-
}
-
//处理请求的方法
-
public abstract void handleRequest(String request);
- }
- //具体处理者角色1
- class ConcreteHandler1 extends Handler
- {
-
public void handleRequest(String request)
-
{
-
if(request.equals("one"))
-
{
-
System.out.println("具体处理者1负责处理该请求!");
-
}
-
else
-
{
-
if(getNext()!=null)
-
{
-
getNext().handleRequest(request);
-
}
-
else
-
{
-
System.out.println("没有人处理该请求!");
-
}
-
}
-
}
- }
- //具体处理者角色2
- class ConcreteHandler2 extends Handler
- {
-
public void handleRequest(String request)
-
{
-
if(request.equals("two"))
-
{
-
System.out.println("具体处理者2负责处理该请求!");
-
}
-
else
-
{
-
if(getNext()!=null)
-
{
-
getNext().handleRequest(request);
-
}
-
else
-
{
-
System.out.println("没有人处理该请求!");
-
}
-
}
-
}
- }
程序运行结果如下:
具体处理者2负责处理该请求!
模式的应用场景
前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。
有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
可动态指定一组对象处理请求,或添加新的处理者。
在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
模式的扩展
职责链模式存在以下两种情况。
纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
状态模式
在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。如人的情绪有高兴的时候和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。
状态模式的定义与特点
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式是一种对象行为型模式,其主要优点如下。
状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
状态模式的主要缺点如下。
状态模式的使用必然会增加系统的类与对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
状态模式的结构与实现
状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。
- 模式的结构
状态模式包含以下主要角色。
环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态(Concrete State)角色:实现抽象状态所对应的行为。
其结构图如图 1 所示。
- 模式的实现
状态模式的实现代码如下: - package state;
- public class StatePatternClient
- {
-
public static void main(String[] args)
-
{
-
Context context=new Context(); //创建环境
-
context.Handle(); //处理请求
-
context.Handle();
-
context.Handle();
-
context.Handle();
-
}
- }
- //环境类
- class Context
- {
-
private State state;
-
//定义环境类的初始状态
-
public Context()
-
{
-
this.state=new ConcreteStateA();
-
}
-
//设置新状态
-
public void setState(State state)
-
{
-
this.state=state;
-
}
-
//读取状态
-
public State getState()
-
{
-
return(state);
-
}
-
//对请求做处理
-
public void Handle()
-
{
-
state.Handle(this);
-
}
- }
- //抽象状态类
- abstract class State
- {
-
public abstract void Handle(Context context);
- }
- //具体状态A类
- class ConcreteStateA extends State
- {
-
public void Handle(Context context)
-
{
-
System.out.println("当前状态是 A.");
-
context.setState(new ConcreteStateB());
-
}
- }
- //具体状态B类
- class ConcreteStateB extends State
- {
-
public void Handle(Context context)
-
{
-
System.out.println("当前状态是 B.");
-
context.setState(new ConcreteStateA());
-
}
- }
程序运行结果如下:
当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.
状态模式的应用场景
通常在以下情况下可以考虑使用状态模式。
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
状态模式的扩展
在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构图如图 5 所示
个人理解
主要就是当多次调用环境的方法时, 会根据当前的情况来改变角色的某些状态,
**观察者模式(Observer模式)(会)
文章地址 http://m.biancheng.net/view/1390.html
定义与特点
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
模式的结构与实现
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
- 模式的结构
观察者模式的主要角色如下。
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
观察者模式的结构图如图 1 所示
- 模式的实现
观察者模式的实现代码如下: - package observer;
- import java.util.*;
- public class ObserverPattern
- {
-
public static void main(String[] args)
-
{
-
Subject subject=new ConcreteSubject();
-
Observer obs1=new ConcreteObserver1();
-
Observer obs2=new ConcreteObserver2();
-
subject.add(obs1);
-
subject.add(obs2);
-
subject.notifyObserver();
-
}
- }
- //抽象目标
- abstract class Subject
- {
-
protected List<Observer> observers=new ArrayList<Observer>();
-
//增加观察者方法
-
public void add(Observer observer)
-
{
-
observers.add(observer);
-
}
-
//删除观察者方法
-
public void remove(Observer observer)
-
{
-
observers.remove(observer);
-
}
-
public abstract void notifyObserver(); //通知观察者方法
- }
- //具体目标
- class ConcreteSubject extends Subject
- {
-
public void notifyObserver()
-
{
-
System.out.println("具体目标发生改变...");
-
System.out.println("--------------");
-
for(Object obs:observers)
-
{
-
((Observer)obs).response();
-
}
-
}
- }
- //抽象观察者
- interface Observer
- {
-
void response(); //反应
- }
- //具体观察者1
- class ConcreteObserver1 implements Observer
- {
-
public void response()
-
{
-
System.out.println("具体观察者1作出反应!");
-
}
- }
- //具体观察者1
- class ConcreteObserver2 implements Observer
- {
-
public void response()
-
{
-
System.out.println("具体观察者2作出反应!");
-
}
- }
程序运行结果如下:
具体目标发生改变…
具体观察者1作出反应!
具体观察者2作出反应
模式的应用场景
通过前面的分析与应用实例可知观察者模式适合以下几种情形。
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
模式的扩展
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
-
Observable类
Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。 -
Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
【例3】利用 Observable 类和 Observer 接口实现原油期货的观察者模式实例。
分析:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。本实例中的抽象目标(Observable)类在 Java 中已经定义,可以直接定义其子类,即原油期货(OilFutures)类,它是具体目标类,该类中定义一个 SetPriCe(float price) 方法,当原油数据发生变化时调用其父类的 notifyObservers(Object arg) 方法来通知所有观察者;另外,本实例中的抽象观察者接口(Observer)在 Java 中已经定义,只要定义其子类,即具体观察者类(包括多方类 Bull 和空方类 Bear),并实现 update(Observable o,Object arg) 方法即可。图 5 所示是其结构图。
中介者模式
将全部人的信息交由中介者管理, 当某个人的信息发生改变需要通知其他人的时候, 可以调用中介者中的方法来通知, 中介者包含一个list集合里面有全部的人
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂。
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参力口工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
模式的定义与特点
中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式,其主要优点如下。
降低了对象之间的耦合性,使得对象易于独立地被复用。
将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
模式的结构与实现
中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析。
- 模式的结构
中介者模式包含以下主要角色。
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
中介者模式的结构图如图 1 所示。
图1 中介者模式的结构图
2. 模式的实现
中介者模式的实现代码如下:
- package mediator;
- import java.util.*;
- public class MediatorPattern
- {
-
public static void main(String[] args)
-
{
-
Mediator md=new ConcreteMediator();
-
Colleague c1,c2;
-
c1=new ConcreteColleague1();
-
c2=new ConcreteColleague2();
-
md.register(c1);
-
md.register(c2);
-
c1.send();
-
System.out.println("-------------");
-
c2.send();
-
}
- }
- //抽象中介者
- abstract class Mediator
- {
-
public abstract void register(Colleague colleague);
-
public abstract void relay(Colleague cl); //转发
- }
- //具体中介者
- class ConcreteMediator extends Mediator
- {
-
private List<Colleague> colleagues=new ArrayList<Colleague>();
-
public void register(Colleague colleague)
-
{
-
if(!colleagues.contains(colleague))
-
{
-
colleagues.add(colleague);
-
colleague.setMedium(this);
-
}
-
}
-
public void relay(Colleague cl)
-
{
-
for(Colleague ob:colleagues)
-
{
-
if(!ob.equals(cl))
-
{
-
((Colleague)ob).receive();
-
}
-
}
-
}
- }
- //抽象同事类
- abstract class Colleague
- {
-
protected Mediator mediator;
-
public void setMedium(Mediator mediator)
-
{
-
this.mediator=mediator;
-
}
-
public abstract void receive();
-
public abstract void send();
- }
- //具体同事类
- class ConcreteColleague1 extends Colleague
- {
-
public void receive()
-
{
-
System.out.println("具体同事类1收到请求。");
-
}
-
public void send()
-
{
-
System.out.println("具体同事类1发出请求。");
-
mediator.relay(this); //请中介者转发
-
}
- }
- //具体同事类
- class ConcreteColleague2 extends Colleague
- {
-
public void receive()
-
{
-
System.out.println("具体同事类2收到请求。");
-
}
-
public void send()
-
{
-
System.out.println("具体同事类2发出请求。");
-
mediator.relay(this); //请中介者转发
-
}
- }
程序的运行结果如下:
具体同事类1发出请求。
具体同事类2收到请求。
具体同事类2发出请求。
具体同事类1收到请求。
模式的应用场景
前面分析了中介者模式的结构与特点,下面分析其以下应用场景。
当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
模式的扩展
在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
不定义中介者接口,把具体中介者对象实现成为单例。
同事对象不持有中介者,而是在需要的时f矣直接获取中介者对象并调用。
图 4 所示是简化中介者模式的结构图。
图4 简化中介者模式的结构图
程序代码如下:
- package mediator;
- import java.util.*;
- public class SimpleMediatorPattern
- {
-
public static void main(String[] args)
-
{
-
SimpleColleague c1,c2;
-
c1=new SimpleConcreteColleague1();
-
c2=new SimpleConcreteColleague2();
-
c1.send();
-
System.out.println("-----------------");
-
c2.send();
-
}
- }
- //简单单例中介者
- class SimpleMediator
- {
-
private static SimpleMediator smd=new SimpleMediator();
-
private List<SimpleColleague> colleagues=new ArrayList<SimpleColleague>();
-
private SimpleMediator(){}
-
public static SimpleMediator getMedium()
-
{ return(smd); }
-
public void register(SimpleColleague colleague)
-
{
-
if(!colleagues.contains(colleague))
-
{
-
colleagues.add(colleague);
-
}
-
}
-
public void relay(SimpleColleague scl)
-
{
-
for(SimpleColleague ob:colleagues)
-
{
-
if(!ob.equals(scl))
-
{
-
((SimpleColleague)ob).receive();
-
}
-
}
-
}
- }
- //抽象同事类
- interface SimpleColleague
- {
-
void receive();
-
void send();
- }
- //具体同事类
- class SimpleConcreteColleague1 implements SimpleColleague
- {
-
SimpleConcreteColleague1(){
-
SimpleMediator smd=SimpleMediator.getMedium();
-
smd.register(this);
-
}
-
public void receive()
-
{ System.out.println("具体同事类1:收到请求。"); }
-
public void send()
-
{
-
SimpleMediator smd=SimpleMediator.getMedium();
-
System.out.println("具体同事类1:发出请求...");
-
smd.relay(this); //请中介者转发
-
}
- }
- //具体同事类
- class SimpleConcreteColleague2 implements SimpleColleague
- {
-
SimpleConcreteColleague2(){
-
SimpleMediator smd=SimpleMediator.getMedium();
-
smd.register(this);
-
}
-
public void receive()
-
{ System.out.println("具体同事类2:收到请求。"); }
-
public void send()
-
{
-
SimpleMediator smd=SimpleMediator.getMedium();
-
System.out.println("具体同事类2:发出请求...");
-
smd.relay(this); //请中介者转发
-
}
- }
程序运行结果如下:
具体同事类1:发出请求…
具体同事类2:收到请求。
具体同事类2:发出请求…
具体同事类1:收到请求。
**迭代器模式
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
暴露了聚合类的内部表示,使其数据不安全;
增加了客户的负担。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
模式的定义与特点
迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。
访问一个聚合对象的内容而无须暴露它的内部表示。
遍历任务交由迭代器完成,这简化了聚合类。
它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
增加新的聚合类和迭代器类都很方便,无须修改原有代码。
封装性良好,为遍历不同的聚合结构提供一个统一的接口。
其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。
模式的结构与实现
迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。现在我们来分析其基本结构与实现方法。
- 模式的结构
迭代器模式主要包含以下角色。
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
其结构图如图 1 所示。
图1 迭代器模式的结构图
2. 模式的实现
迭代器模式的实现代码如下:
- package iterator;
- import java.util.*;
- public class IteratorPattern
- {
-
public static void main(String[] args)
-
{
-
Aggregate ag=new ConcreteAggregate();
-
ag.add("中山大学");
-
ag.add("华南理工");
-
ag.add("韶关学院");
-
System.out.print("聚合的内容有:");
-
Iterator it=ag.getIterator();
-
while(it.hasNext())
-
{
-
Object ob=it.next();
-
System.out.print(ob.toString()+"\t");
-
}
-
Object ob=it.first();
-
System.out.println("\nFirst:"+ob.toString());
-
}
- }
- //抽象聚合
- interface Aggregate
- {
-
public void add(Object obj);
-
public void remove(Object obj);
-
public Iterator getIterator();
- }
- //具体聚合
- class ConcreteAggregate implements Aggregate
- {
-
private List<Object> list=new ArrayList<Object>();
-
public void add(Object obj)
-
{
-
list.add(obj);
-
}
-
public void remove(Object obj)
-
{
-
list.remove(obj);
-
}
-
public Iterator getIterator()
-
{
-
return(new ConcreteIterator(list));
-
}
- }
- //抽象迭代器
- interface Iterator
- {
-
Object first();
-
Object next();
-
boolean hasNext();
- }
- //具体迭代器
- class ConcreteIterator implements Iterator
- {
-
private List<Object> list=null;
-
private int index=-1;
-
public ConcreteIterator(List<Object> list)
-
{
-
this.list=list;
-
}
-
public boolean hasNext()
-
{
-
if(index<list.size()-1)
-
{
-
return true;
-
}
-
else
-
{
-
return false;
-
}
-
}
-
public Object first()
-
{
-
index=0;
-
Object obj=list.get(index);;
-
return obj;
-
}
-
public Object next()
-
{
-
Object obj=null;
-
if(this.hasNext())
-
{
-
obj=list.get(++index);
-
}
-
return obj;
-
}
- }
程序运行结果如下:
聚合的内容有:中山大学 华南理工 韶关学院
First:中山大学
模式的应用场景
前面介绍了关于迭代器模式的结构与特点,下面介绍其应用场景,迭代器模式通常在以下几种情况使用。
当需要为聚合对象提供多种遍历方式时。
当需要为遍历不同的聚合结构提供一个统一的接口时。
当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
由于聚合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类,因此大数情况下使用语言中已有的聚合类的迭代器就已经够了。
模式的扩展
迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问,其结构图如图 4 所示。
图4 组合迭代器模式的结构图
访问者模式(Visitor模式)
备忘录模式
将一个对象的当前值都保存下来, 在某个时刻可以恢复的, 相当于复制一个
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
模式的定义与特点
备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
备忘录模式是一种对象行为型模式,其主要优点如下。
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
模式的结构与实现
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。
- 模式的结构
备忘录模式的主要角色如下。
发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录模式的结构图如图 1 所示。
图1 备忘录模式的结构图
2. 模式的实现
备忘录模式的实现代码如下:
- package memento;
- public class MementoPattern
- {
-
public static void main(String[] args)
-
{
-
Originator or=new Originator();
-
Caretaker cr=new Caretaker();
-
or.setState("S0");
-
System.out.println("初始状态:"+or.getState());
-
cr.setMemento(or.createMemento()); //保存状态
-
or.setState("S1");
-
System.out.println("新的状态:"+or.getState());
-
or.restoreMemento(cr.getMemento()); //恢复状态
-
System.out.println("恢复状态:"+or.getState());
-
}
- }
- //备忘录
- class Memento
- {
-
private String state;
-
public Memento(String state)
-
{
-
this.state=state;
-
}
-
public void setState(String state)
-
{
-
this.state=state;
-
}
-
public String getState()
-
{
-
return state;
-
}
- }
- //发起人
- class Originator
- {
-
private String state;
-
public void setState(String state)
-
{
-
this.state=state;
-
}
-
public String getState()
-
{
-
return state;
-
}
-
public Memento createMemento()
-
{
-
return new Memento(state);
-
}
-
public void restoreMemento(Memento m)
-
{
-
this.setState(m.getState());
-
}
- }
- //管理者
- class Caretaker
- {
-
private Memento memento;
-
public void setMemento(Memento m)
-
{
-
memento=m;
-
}
-
public Memento getMemento()
-
{
-
return memento;
-
}
- }
程序运行的结果如下:
初始状态:S0
新的状态:S1
恢复状态:S0
模式的应用场景
前面学习了备忘录模式的定义与特点、结构与实现,现在来看该模式的以下应用场景。
需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
模式的扩展
在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,其结构图如图 4 所示。
图4 带原型的备忘录模式的结构图
实现代码如下:
- package memento;
- public class PrototypeMemento
- {
-
public static void main(String[] args)
-
{
-
OriginatorPrototype or=new OriginatorPrototype();
-
PrototypeCaretaker cr=new PrototypeCaretaker();
-
or.setState("S0");
-
System.out.println("初始状态:"+or.getState());
-
cr.setMemento(or.createMemento()); //保存状态
-
or.setState("S1");
-
System.out.println("新的状态:"+or.getState());
-
or.restoreMemento(cr.getMemento()); //恢复状态
-
System.out.println("恢复状态:"+or.getState());
-
}
- }
- //发起人原型
- class OriginatorPrototype implements Cloneable
- {
-
private String state;
-
public void setState(String state)
-
{
-
this.state=state;
-
}
-
public String getState()
-
{
-
return state;
-
}
-
public OriginatorPrototype createMemento()
-
{
-
return this.clone();
-
}
-
public void restoreMemento(OriginatorPrototype opt)
-
{
-
this.setState(opt.getState());
-
}
-
public OriginatorPrototype clone()
-
{
-
try
-
{
-
return (OriginatorPrototype) super.clone();
-
}
-
catch(CloneNotSupportedException e)
-
{
-
e.printStackTrace();
-
}
-
return null;
-
}
- }
- //原型管理者
- class PrototypeCaretaker
- {
-
private OriginatorPrototype opt;
-
public void setMemento(OriginatorPrototype opt)
-
{
-
this.opt=opt;
-
}
-
public OriginatorPrototype getMemento()
-
{
-
return opt;
-
}
- }
程序的运行结果如下:
初始状态:S0
新的状态:S1
恢复状态:S0
解释器模式
UMLet的使用与类图的设计
创建型模式应用实验
结构型模式应用实验
行为型模式应用实验