文章目录
- 创建型设计模式之单例模式✳—01
- 创建型设计模式之原型模式—02
- 创建型设计模式之工厂方法模式✳—03
- 创建型设计模式之抽象工厂模式✳—04
- 创建型设计模式之建造者模式✳—05
- 结构型设计模式之代理模式✳—06
- 结构型设计模式之适配器模式✳—07
- 结构型设计模式之桥接模式—08
- 结构型设计模式之装饰模式—09
- 结构型设计模式之外观模式—10
- 结构型设计模式之享元模式—11
- 结构型设计模式之组合模式✳—12
- 行为型设计模式之模板方法模式—13
- 行为型设计模式之策略模式✳—14
- 行为型设计模式之命令模式—15
- 行为型设计模式之责任链模式✳—16
- 行为型设计模式之状态模式—17
- 行为型设计模式之观察者模式✳—18
- 行为型设计模式之中介者模式—19
- 行为型设计模式之迭代器模式—20
- 行为型设计模式之访问者模式—21
- 行为型设计模式之备忘录模式—22
- 行为型设计模式之解释器模式—23
概念
软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
意义
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
正确使用设计模式具有以下优点:
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
七大原则
-
开闭原则
定义 : 当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
作用 : 开闭原则是面向对象程序设计的终极目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。对软件测试的影响
: 软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。可以提高代码的可复用性
: 粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。可以提高软件的可维护性
: 遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。
-
里氏替换原则
定义 : 继承必须确保超类所拥有的性质在子类中仍然成立。通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
作用 :- 里氏替换原则是实现开闭原则的重要方式之一。
- 它克服了继承中重写父类造成的可复用性变差的缺点。
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
-
依赖倒置原则
定义 : 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽。其核心思想是:要面向接口编程,不要面向实现编程。依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性;依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
作用 :- 依赖倒置原则可以降低类间的耦合性。
- 依赖倒置原则可以提高系统的稳定性。
- 依赖倒置原则可以减少并行开发引起的风险。
- 依赖倒置原则可以提高代码的可读性和可维护性。
-
单一职责原则
定义 : 单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
作用 :- 降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
- 提高类的可读性。复杂性降低,自然其可读性会提高。
- 提高系统的可维护性。可读性提高,那自然更容易维护了。
- 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。
-
接口隔离原则
定义 : 客户端不应该被迫依赖于它不使用的方法;该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上。
以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
作用 :- 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
- 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
- 如果接口的粒度大小定义合理,能够保证系统的稳定性;但是,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
- 使用多个专门的接口还能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
- 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
-
迪米特法则
定义 : 又叫作最少知识原则。只与你的直接朋友交谈,不跟“陌生人”说话;其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
作用 :- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
-
合成复用原则
定义 : 又叫组合/聚合复用原则。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。
作用 :- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
- 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
划分
创建型设计模式
1,单例(Singleton)模式 :某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
2,原型(Prototype)模式 :将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3,工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4,抽象工厂(AbstractFactory)模式 :提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5,建造者(Builder)模式 :将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
结构型设计模式
1,代理(Proxy)模式 :为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
2,适配器(Adapter)模式 :将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
3,桥接(Bridge)模式 :将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
4,装饰(Decorator)模式 :动态地给对象增加一些职责,即增加其额外的功能。
5,外观(Facade)模式 :为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
6,享元(Flyweight)模式 :运用共享技术来有效地支持大量细粒度对象的复用。
7,组合(Composite)模式 :将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
行为型模式
1,模板方法(Template Method)模式 :定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
2,策略(Strategy)模式 :定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
3,命令(Command)模式 :将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
4,职责链(Chain of Responsibility)模式 :把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
5,状态(State)模式 :允许一个对象在其内部状态发生改变时改变其行为能力。
6,观察者(Observer)模式 :多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
7,中介者(Mediator)模式 :定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
8,迭代器(Iterator)模式 :提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
9,访问者(Visitor)模式 :在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
10,备忘录(Memento)模式 :在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
11,解释器(Interpreter)模式 :提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
创建型设计模式之单例模式✳—01
- 为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
- 单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
定义
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
特点
- 1,单例类只有一个实例对象;
- 2,该单例对象必须由单例类自行创建;
- 3,单例类对外提供一个访问该单例的全局访问点;
结构
单例模式的主要角色如下。
- 单例类:包含一个实例且能自行创建这个实例的类。
- 访问类:使用单例的类。
实现
第一种方式:懒汉式单例(线程不安全)
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。线程不安全。代码如下:
/**
* @author yeming.gao
* @Description: 懒汉式单例模式(线程不安全)
* @date 2020/5/14 17:41
*/
public class LazySingletonThreadUnSafe {
private static LazySingletonThreadUnSafe instance;
/**
* 私有化构造方法
*/
private LazySingletonThreadUnSafe() {
}
public static LazySingletonThreadUnSafe getInstance() {
//getInstance 方法前加同步
if (instance == null) {
instance = new LazySingletonThreadUnSafe();
}
return instance;
}
}
第二种方式:懒汉式单例(线程安全)
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。线程安全。代码如下:
/**
* @author yeming.gao
* @Description: 懒汉式单例模式(线程安全);不是绝对的安全
* @date 2020/5/14 17:41
*/
public class LazySingletonThreadSafe {
private static LazySingletonThreadSafe instance;
/**
* 私有化构造方法
*/
private LazySingletonThreadSafe() {
}
public static synchronized LazySingletonThreadSafe getInstance() {
//getInstance 方法前加同步
if (instance == null) {
instance = new LazySingletonThreadSafe();
}
return instance;
}
}
第三种方式:饿汉式单例(线程安全)
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
/**
* @author yeming.gao
* @Description: 饿汉式单例(线程安全)
* @date 2020/5/14 17:47
*/
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
第四种方式:饿汉式单例-变种(线程安全)
该模式的特点是类实例化过程中由静态代码块去创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
/**
* @author yeming.gao
* @Description: 饿汉式单例-变种(线程安全)
* @date 2020/5/14 17:47
*/
public class HungrySingletonVariety {
private static final HungrySingletonVariety INSTANCE;
static {
INSTANCE = new HungrySingletonVariety();
}
private HungrySingletonVariety() {
}
public static HungrySingletonVariety getInstance() {
return INSTANCE;
}
}
第五种方式:静态内部类单例(线程安全)
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要SingletonStaticInnerClass类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是SingletonStaticInnerClass类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在SingletonStaticInnerClass类加载时就实例化,因为我不能确保SingletonStaticInnerClass类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
/**
* @author yeming.gao
* @Description: 静态内部类单例(线程安全)
* @date 2020/5/14 17:47
*/
public class SingletonStaticInnerClass {
private static class SingletonHolder {
private static final SingletonStaticInnerClass INSTANCE = new SingletonStaticInnerClass();
}
private SingletonStaticInnerClass() {
}
public static SingletonStaticInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
第六种方式:枚举单例(线程安全)
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
/**
* @author yeming.gao
* @Description: 枚举单例(线程安全);可以有效的方式反射及反序列化重新实例化对象
* @date 2020/5/14 17:47
*/
public enum SingletonEnum {
INSTANCE;
public void whateverMethod() {
}
}
第七种方式:懒汉式单例-双重校验锁(线程安全)
与volatile配合使用,保证绝对的线程安全。
/**
* @author yeming.gao
* @Description: 懒汉式单例模式-双重校验锁(线程安全);
* @date 2020/5/14 17:41
*/
public class LazySingletonDoubleLock {
private static volatile LazySingletonDoubleLock instance;
/**
* 私有化构造方法
*/
private LazySingletonDoubleLock() {
}
public static LazySingletonDoubleLock getInstance() {
if (instance == null) {
synchronized (LazySingletonDoubleLock.class) {
if (instance == null) {
instance = new LazySingletonDoubleLock();
}
}
}
return instance;
}
}
注意:如果instance不用volatile关键字进行修饰;那么这里说的线程不是绝对的安全,主要是由于Java的自带的优化指令重排序导致的在对象实例化过程中不安全。想要绝对安全可以给对象instance加上volatile关键字进行修饰。
问题:为什么推荐使用枚举实现单例?
当通过反射,或者序列化去实例化对象的时候;还是会出现对象不一致的情况。而枚举可以完美避免这种情况(枚举不能被反射,或者反序列化)。例如下面代码:
public class LazySingletonThreadUnSafe implements Serializable {
private static LazySingletonThreadUnSafe instance;
/**
* 私有化构造方法
*/
private LazySingletonThreadUnSafe() {
}
public static LazySingletonThreadUnSafe getInstance() {
//getInstance 方法前加同步
if (instance == null) {
instance = new LazySingletonThreadUnSafe();
}
return instance;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, IOException, ClassNotFoundException {
System.out.println("通过反射攻击单例模式情况下------->");
LazySingletonThreadUnSafe lazySingletonThreadUnSafe01 = LazySingletonThreadUnSafe.getInstance();
Constructor<LazySingletonThreadUnSafe> constructor = LazySingletonThreadUnSafe.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingletonThreadUnSafe sReflection = constructor.newInstance();
System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:" + (lazySingletonThreadUnSafe01 == sReflection));
System.out.println("通过序列化攻击单例模式情况下------->");
LazySingletonThreadUnSafe lazySingletonThreadUnSafe0102 = LazySingletonThreadUnSafe.getInstance();
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(lazySingletonThreadUnSafe0102);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bais);
LazySingletonThreadUnSafe serialize = (LazySingletonThreadUnSafe) in.readObject();
System.out.println("通过序列化攻击单例模式情况下,实例化两个实例是否相同:" + (lazySingletonThreadUnSafe0102 == serialize));
}
}
代码输出如下:
通过反射攻击单例模式情况下------->
通过反射攻击单例模式情况下,实例化两个实例是否相同:false
通过序列化攻击单例模式情况下------->
通过序列化攻击单例模式情况下,实例化两个实例是否相同:false
枚举不能被反射或者反序列化的原因
枚举总结
- 枚举是一个final类,继承了Enum;
- 枚举中定义的变量实际是常量;
- 枚举中默认的构造方法是私有的,自带名称和序号;
- 自定义枚举中的构造方法并且带有额外参数的时候,拼接到原有的构造方法中;
- 枚举中定义的多例,需要根据自定义的构造方法进行改变;
- 枚举如果想要增加变量:a.添加构造方法和变量;b.变量名通过构造方法初始化;c.原定义的枚举需要根据构造方法进行修改;
不能被反射,序列化原因是反射newInstance
及序列化readObject代码实现,对枚举类型都不支持
应用场景
- 1,在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 2,当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
- 3,当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
扩展
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArmyList 中,客户需要时可随机获取
/**
* @author yeming.gao
* @Description: 单例模式中的-多例
* @date 2020/5/14 17:59
*/
public class MultitcmSingleton {
private static List<MultitcmSingleton> list = new ArrayList<>();
private MultitcmSingleton(int n) {
}
static {
for (int i = 0; i < 10; i++) {
list.add(new MultitcmSingleton(i));
}
}
public static MultitcmSingleton getInstance() {
//getInstance 方法前加同步
int value = (int) (Math.random() * 10);
return list.get(value);
}
}
创建型设计模式之原型模式—02
- 存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
- 原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。
定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
结构
原型模式包含以下主要角色
- 1,抽象原型类:规定了具体原型对象必须实现的接口。
- 2,具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 3,访问类:使用具体原型类中的 clone() 方法来复制新的对象。
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
实现
/**
* @author yeming.gao
* @Description: 原型模式
* @date 2020/5/14 18:44
*/
public class Prototype {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1 = new Realizetype();
Realizetype obj2 = (Realizetype) obj1.clone();
System.out.println("obj1 == obj2 ? " + (obj1 == obj2));
}
}
/**
* 原型模式的测试类
*/
class Realizetype implements Cloneable {
Realizetype() {
System.out.println("具体原型创建成功!");
}
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return super.clone();
}
}
程序的运行结果如下
具体原型创建成功!
具体原型复制成功!
obj1 == obj2 ? false
示例
用原型模式除了可以生成相同的对象,还可以生成相似的对象
【例】用原型模式生成“三好学生”奖状
分析:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,同样可以用原型模式创建,然后再做简单修改就可以了。UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 原型模式示例:同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,
* 属于相似对象的复制,同样可以用原型模式创建,然后再做简单
* 修改就可以了
* @date 2020/5/15 9:42
*/
public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException {
citation obj1 = new citation("张三", "同学:在2016学年第一学期中表现优秀,被评为三好学生。", "韶关学院");
obj1.display();
citation obj2 = (citation) obj1.clone();
obj2.setName("李四");
obj2.display();
citation obj3 = (citation) obj1.clone();
obj3.setName("王五");
obj3.display();
}
}
/**
* 奖状类
*/
class citation implements Cloneable {
private String name;
private String info;
private String college;
citation(String name, String info, String college) {
this.name = name;
this.info = info;
this.college = college;
System.out.println("奖状创建成功!");
}
void setName(String name) {
this.name = name;
}
String getName() {
return (this.name);
}
void display() {
System.out.println(name + info + college);
}
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("奖状拷贝成功!");
return super.clone();
}
}
程序运行结果如下:
奖状创建成功!
张三同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
奖状拷贝成功!
李四同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
奖状拷贝成功!
王五同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
应用场景
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 对象的创建过程比较麻烦,但复制比较简单的时候。
扩展
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。UML类图如下:
【例】用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,并计算其面积。分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们,UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 原型模式扩展:用带原型管理器的原型模式来生成包含“圆”和“正方形”等图形的原型,
* 并计算其面积。
* 分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,
* 所以需要用一个原型管理器来管理它们
* @date 2020/5/15 10:15
*/
public class ProtoTypeShape {
public static void main(String[] args) {
ProtoTypeManager pm = new ProtoTypeManager();
IShape obj1 = pm.getShape("Circle");
obj1.countArea();
IShape obj2 = pm.getShape("Square");
obj2.countArea();
}
}
interface IShape extends Cloneable {
/**
* 拷贝
* @return Object
*/
Object clone();
/**
* 计算面积
*/
void countArea();
}
class Circle implements IShape {
@Override
public Object clone() {
Circle w = null;
try {
w = (Circle) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝圆失败!");
}
return w;
}
@Override
public void countArea() {
int r = 0;
System.out.print("这是一个圆,请输入圆的半径:");
Scanner input = new Scanner(System.in);
r = input.nextInt();
System.out.println("该圆的面积=" + 3.1415 * r * r + "\n");
}
}
class Square implements IShape {
@Override
public Object clone() {
Square b = null;
try {
b = (Square) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("拷贝正方形失败!");
}
return b;
}
@Override
public void countArea() {
System.out.print("这是一个正方形,请输入它的边长:");
Scanner input = new Scanner(System.in);
int a = input.nextInt();
System.out.println("该正方形的面积=" + a * a + "\n");
}
}
class ProtoTypeManager {
private HashMap<String, IShape> ht = new HashMap<>();
public ProtoTypeManager() {
ht.put("Circle", new Circle());
ht.put("Square", new Square());
}
public void addshape(String key, IShape obj) {
ht.put(key, obj);
}
public IShape getShape(String key) {
IShape temp = ht.get(key);
return (IShape) temp.clone();
}
}
运行结果如下所示:
这是一个圆,请输入圆的半径:5
该圆的面积=78.53750000000001
这是一个正方形,请输入它的边长:5
该正方形的面积=25
创建型设计模式之工厂方法模式✳—03
- 如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”,它不属于 GoF 的 23 种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。
- “工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
定义
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
特点
优点
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
结构
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等4个要素构成。
抽象工厂(Abstract Factory)
:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。具体工厂(ConcreteFactory)
:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。抽象产品(Product)
:定义了产品的规范,描述了产品的主要特性和功能。具体产品(ConcreteProduct)
:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
实现
/**
* @author yeming.gao
* @Description: 工厂方法模式实现
* @date 2020/5/15 11:07
*/
public class FactoryMethod {
public static void main(String[] args) {
try {
IProduct a;
IAbstractFactory af;
af = (IAbstractFactory) ReadXML.getObject("src/main/resources/factorymethod_config.xml");
a = af.newProduct();
a.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
/**
* 抽象产品:提供了产品的接口
*/
interface IProduct {
void show();
}
/**
* 具体产品1:实现抽象产品中的抽象方法
*/
class ConcreteProduct1 implements IProduct {
@Override
public void show() {
System.out.println("具体产品1显示...");
}
}
/**
* 具体产品2:实现抽象产品中的抽象方法
*/
class ConcreteProduct2 implements IProduct {
@Override
public void show() {
System.out.println("具体产品2显示...");
}
}
/**
* 抽象工厂:提供了厂品的生成方法
*/
interface IAbstractFactory {
IProduct newProduct();
}
/**
* @author yeming.gao
* @Description: 具体工厂1:实现了厂品的生成方法
* @date 2020/5/26 16:40
*/
public class ConcreteFactory1 implements IAbstractFactory {
@Override
public IProduct newProduct() {
System.out.println("具体工厂1生成-->具体产品1...");
return new ConcreteProduct1();
}
}
/**
* @author yeming.gao
* @Description: 具体工厂2:实现了厂品的生成方法
* @date 2020/5/26 16:40
*/
public class ConcreteFactory2 implements IAbstractFactory {
@Override
public IProduct newProduct() {
System.out.println("具体工厂2生成-->具体产品2...");
return new ConcreteProduct2();
}
}
ReadXML.java
/**
* @author yeming.gao
* @Description: 从XML配置文件中提取具体类类名,通过反射返回一个实例对象
* @date 2020/5/15 11:12
*/
public class ReadXML {
public static Object getObject(String xmlPath) {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File(xmlPath));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
//System.out.println("新类名:"+cName);
//通过类名生成实例对象并将其返回
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
factorymethod_config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>com.yeming.design.pattern.creation_03_factorymethod.ConcreteFactory1</className>
</config>
程序运行结果如下
具体工厂1生成-->具体产品1...
具体产品1显示...
如果将 XML 配置文件中的 ConcreteFactory1 改为 ConcreteFactory2,则程序运行结果如下
具体工厂2生成-->具体产品2...
具体产品2显示...
应用场景
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
扩展
当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式,UML类图如下:
创建型设计模式之抽象工厂模式✳—04
- 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
- 抽象工厂模式的结构同工厂方法模式的结构相似,不同的是其产品的种类不止一个,所以创建产品的方法也不止一个。
定义
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
特点
使用抽象工厂模式一般要满足以下条件
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
优点 - 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
缺点
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
结构
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。
抽象工厂(Abstract Factory)
:提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。具体工厂(ConcreteFactory)
:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。抽象产品(Product)
:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。具体产品(ConcreteProduct)
:实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
实现
/**
* @author yeming.gao
* @Description: 抽象工厂模式实现
* @date 2020/5/15 14:31
*/
public class AbstractFactory {
public static void main(String[] args) {
try {
IProduct1 a;
IAbstractFactory af;
af = (IAbstractFactory) ReadXML.getObject("src/main/resources/abstractfactory_config.xml");
a = af.newProduct1();
a.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
/**
* 抽象产品1:提供了产品的接口
*/
interface IProduct1 {
void show();
}
/**
* 抽象产品2:提供了产品的接口
*/
interface IProduct2 {
void show();
}
/**
* 具体产品11:实现抽象产品中的抽象方法
*/
class ConcreteProduct11 implements IProduct1 {
@Override
public void show() {
System.out.println("具体产品11显示...");
}
}
/**
* 具体产品12:实现抽象产品中的抽象方法
*/
class ConcreteProduct12 implements IProduct1 {
@Override
public void show() {
System.out.println("具体产品12显示...");
}
}
/**
* 具体产品21:实现抽象产品中的抽象方法
*/
class ConcreteProduct21 implements IProduct2 {
@Override
public void show() {
System.out.println("具体产品21显示...");
}
}
/**
* 具体产品12:实现抽象产品中的抽象方法
*/
class ConcreteProduct22 implements IProduct2 {
@Override
public void show() {
System.out.println("具体产品22显示...");
}
}
/**
* 抽象工厂
*/
interface IAbstractFactory {
IProduct1 newProduct1();
IProduct2 newProduct2();
}
/**
* @author yeming.gao
* @Description: 具体工厂1:实现了厂品的生成方法
* @date 2020/5/26 17:05
*/
public class ConcreteFactory1 implements IAbstractFactory {
@Override
public IProduct1 newProduct1() {
System.out.println("具体工厂 1 生成-->具体产品 11...");
return new ConcreteProduct11();
}
@Override
public IProduct2 newProduct2() {
System.out.println("具体工厂 1 生成-->具体产品 12...");
return new ConcreteProduct21();
}
}
/**
* @author yeming.gao
* @Description: 具体工厂2:实现了厂品的生成方法
* @date 2020/5/26 17:05
*/
public class ConcreteFactory2 implements IAbstractFactory {
@Override
public IProduct1 newProduct1() {
System.out.println("具体工厂 2 生成-->具体产品 12...");
return new ConcreteProduct12();
}
@Override
public IProduct2 newProduct2() {
System.out.println("具体工厂 2 生成-->具体产品 22...");
return new ConcreteProduct22();
}
}
ReadXML.java
/**
* @author yeming.gao
* @Description: 从XML配置文件中提取具体类类名,并返回一个实例对象
* @date 2020/5/15 11:12
*/
public class ReadXML {
public static Object getObject(String xmlPath) {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File(xmlPath));
//获取包含类名的文本节点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
//System.out.println("新类名:"+cName);
//通过类名生成实例对象并将其返回
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
abstractfactory_config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>com.yeming.design.pattern.creation_04_abstractfactory.ConcreteFactory1</className>
</config>
程序运行结果如下
具体工厂 1 生成-->具体产品 11...
具体产品11显示...
如果将 XML 配置文件中的 ConcreteFactory1 改为 ConcreteFactory2,则程序运行结果如下
具体工厂 2 生成-->具体产品 12...
具体产品12显示...
应用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
扩展
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
创建型设计模式之建造者模式✳—05
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
定义
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
特点
优点
- 1,各个具体的建造者相互独立,有利于系统的扩展。
- 2,客户端不必知道产品内部组成的细节,便于控制细节风险。
缺点
- 1,产品的组成部分必须相同,这限制了其使用范围。
- 2,如果产品的内部变化复杂,该模式会增加很多的建造者类。
结构
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成
产品角色(Product)
:它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。抽象建造者(Builder)
:它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。具体建造者(Concrete Builder)
:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。指挥者(Director)
:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
实现
/**
* @author yeming.gao
* @Description: 建造者模式实现
* @date 2020/5/15 15:52
*/
public class Builder {
public static void main(String[] args) {
ABuilder builder1 = new ConcreteBuilder1();
Driector director1 = new Driector(builder1);
Product product1 = director1.construct();
product1.show();
ABuilder builder2 = new ConcreteBuilder2();
Driector director2 = new Driector(builder2);
Product product2 = director2.construct();
product2.show();
}
}
/**
* 产品
*/
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;
}
@Override
public String toString() {
return "partA = " + this.partA + ";partB = " + this.partB + ";partC = " + this.partC;
}
public void show() {
System.out.println("显示产品特性:" + this.toString());
}
}
/**
* 抽象建造者
*/
abstract class ABuilder {
protected Product product = new Product();
public abstract void builderPartA();
public abstract void builderPartB();
public abstract void builderPartC();
public Product getResult() {
return product;
}
}
/**
* 具体建造者1
*/
class ConcreteBuilder1 extends ABuilder {
@Override
public void builderPartA() {
product.setPartA("建造 Part1A");
}
@Override
public void builderPartB() {
product.setPartB("建造 Part1B");
}
@Override
public void builderPartC() {
product.setPartC("建造 Part1C");
}
}
/**
* 具体建造者2
*/
class ConcreteBuilder2 extends ABuilder {
@Override
public void builderPartA() {
product.setPartA("建造 Part2A");
}
@Override
public void builderPartB() {
product.setPartB("建造 Part2B");
}
@Override
public void builderPartC() {
product.setPartC("建造 Part2C");
}
}
/**
* 指挥者
*/
class Driector {
private ABuilder builder;
public Driector(ABuilder builder) {
this.builder = builder;
}
public Product construct() {
builder.builderPartA();
builder.builderPartB();
builder.builderPartC();
return builder.getResult();
}
}
程序运行结果如下
显示产品特性:partA = 建造 Part1A;partB = 建造 Part1B;partC = 建造 Part1C
显示产品特性:partA = 建造 Part12A;partB = 建造 Part12B;partC = 建造 Part12C
应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
扩展
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。
结构型设计模式之代理模式✳—06
定义
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
特点
优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
结构
代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问。
抽象主题(Subject)类
:通过接口或抽象类声明真实主题和代理对象实现的业务方法。真实主题(Real Subject)类
:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。代理(Proxy)类
:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
实现
/**
* @author yeming.gao
* @Description: 代理模式实现
* @date 2020/5/15 17:09
*/
public class Proxy {
public static void main(String[] args) {
ProxyMode proxyMode = new ProxyMode();
proxyMode.request();
}
}
/**
* 抽象主题
*/
interface ISubject {
void request();
}
/**
* 真实主题
*/
class RealSubject implements ISubject {
@Override
public void request() {
System.out.println("访问真实主题方法...");
}
}
/**
* 代理
*/
class ProxyMode implements ISubject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
private void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}
程序运行的结果如下:
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。
应用场景
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
扩展
在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。
- 1,真实主题与代理主题一一对应,增加真实主题也要增加代理。
- 2,设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP,UML类图如下:
结构型设计模式之适配器模式✳—07
- 在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
特点
优点
- 客户端通过适配器可以透明地调用目标接口。
- 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
- 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点
- 对类适配器来说,更换适配器的实现过程比较复杂。
结构
- 类适配器模式可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
- 对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
其结构如下: 目标(Target)接口
: 当前系统业务所期待的接口,它可以是抽象类或接口。适配者(Adaptee)类
:它是被访问和适配的现存组件库中的组件接口。适配器(Adapter)类
:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
实现
- 类适配器模式
/**
* @author yeming.gao
* @Description: 类适配器模式实现
* @date 2020/5/18 13:27
*/
public class ClassAdapter {
public static void main(String[] args) {
System.out.println("类适配器模式测试:");
ITarget target = new ClassAdapterImpl();
target.request();
}
}
/**
* 目标接口
*/
interface ITarget {
public void request();
}
/**
* 适配者接口
*/
class Adapter {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
/**
* 类适配器类
*/
class ClassAdapterImpl extends Adapter implements ITarget {
@Override
public void request() {
specificRequest();
}
}
程序的运行结果如下:
类适配器模式测试:
适配者中的业务代码被调用!
- 对象适配器模式
/**
* @author yeming.gao
* @Description: 对象适配器模式实现
* @date 2020/5/18 13:33
*/
public class ObjectAdapter {
public static void main(String[] args) {
System.out.println("对象适配器模式测试:");
Adapter adaptee = new Adapter();
ITarget target = new ObjectAdapterImpl(adaptee);
target.request();
}
}
/**
* 目标接口
*/
interface ITarget {
public void request();
}
/**
* 适配者接口
*/
class Adapter {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}
/**
* 对象适配器类
*/
class ObjectAdapterImpl implements ITarget {
private Adapter adaptee;
public ObjectAdapterImpl(Adapter adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
程序的运行结果如下:
对象适配器模式测试:
适配者中的业务代码被调用!
应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
扩展
适配器模式(Adapter)可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口,UML类图如下:
代码实现
/**
* @author yeming.gao
* @Description: 适配器模式的扩展:双向适配器模式
* @date 2020/5/18 14:03
*/
public class TwoWayAdapter {
public static void main(String[] args) {
System.out.println("目标通过双向适配器访问适配者:");
ITwoWayAdapter adapter = new AdapterRealizeImpl();
ITwoWayTarget target = new TwoWayAdapterImpl(adapter);
target.request();
System.out.println("-------------------");
System.out.println("适配者通过双向适配器访问目标:");
target = new TargetRealizeImpl();
adapter = new TwoWayAdapterImpl(target);
adapter.specificRequest();
}
}
/**
* 目标接口
*/
interface ITwoWayTarget {
public void request();
}
/**
* 适配者接口
*/
interface ITwoWayAdapter {
public void specificRequest();
}
/**
* 目标实现
*/
class TargetRealizeImpl implements ITwoWayTarget {
@Override
public void request() {
System.out.println("目标代码被调用!");
}
}
/**
* 适配者实现
*/
class AdapterRealizeImpl implements ITwoWayAdapter {
@Override
public void specificRequest() {
System.out.println("适配者代码被调用!");
}
}
/**
* 双向适配器
*/
class TwoWayAdapterImpl implements ITwoWayTarget, ITwoWayAdapter {
private ITwoWayTarget target;
private ITwoWayAdapter adaptee;
public TwoWayAdapterImpl(ITwoWayTarget target) {
this.target = target;
}
public TwoWayAdapterImpl(ITwoWayAdapter adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
@Override
public void specificRequest() {
target.request();
}
}
程序的运行结果如下:
目标通过双向适配器访问适配者:
适配者代码被调用!
-------------------
适配者通过双向适配器访问目标:
目标代码被调用!
结构型设计模式之桥接模式—08
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
定义
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
特点
优点
- 由于抽象与实现分离,所以扩展能力强;
- 其实现细节对客户透明。
缺点
- 由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。
结构
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
抽象化(Abstraction)角色
:定义抽象类,并包含一个对实现化对象的引用。扩展抽象化(Refined Abstraction)角色
:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。实现化(Implementor)角色
:定义实现化角色的接口,供扩展抽象化角色调用。具体实现化(Concrete Implementor)角色
:给出实现化角色接口的具体实现。
实现
/**
* @author yeming.gao
* @Description: 桥接模式实现
* @date 2020/5/18 14:32
*/
public class Bridge {
public static void main(String[] args) {
IImplementor impleA = new ConcreteImplementorA();
Abstraction absA = new RefinedAbstraction(impleA);
absA.operation();
IImplementor impleB = new ConcreteImplementorB();
Abstraction absB = new RefinedAbstraction(impleB);
absB.operation();
}
}
/**
* 实现化角色
*/
interface IImplementor {
public void operationImpl();
}
/**
* 具体实现化角色A
*/
class ConcreteImplementorA implements IImplementor {
@Override
public void operationImpl() {
System.out.println("具体实现化A(Concrete Implementor)角色被访问");
}
}
/**
* 具体实现化角色B
*/
class ConcreteImplementorB implements IImplementor {
@Override
public void operationImpl() {
System.out.println("具体实现化B(Concrete Implementor)角色被访问");
}
}
/**
* 抽象化角色
*/
abstract class Abstraction {
protected IImplementor imple;
protected Abstraction(IImplementor imple) {
this.imple = imple;
}
public abstract void operation();
}
/**
* 扩展抽象化角色
*/
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(IImplementor imple) {
super(imple);
}
@Override
public void operation() {
System.out.println("扩展抽象化(Refined Abstraction)角色被访问");
imple.operationImpl();
}
}
程序的运行结果如下:
扩展抽象化(Refined Abstraction)角色被访问
具体实现化A(Concrete Implementor)角色被访问
扩展抽象化(Refined Abstraction)角色被访问
具体实现化B(Concrete Implementor)角色被访问
应用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
扩展
在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,UML类图如下:
结构型设计模式之装饰模式—09
- 在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰模式来实现。
- 通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。
定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
特点
优点
- 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
- 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
缺点
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
结构
抽象构件(Component)角色
:定义一个抽象接口以规范准备接收附加责任的对象。具体构件(Concrete Component)角色
:实现抽象构件,通过装饰角色为其添加一些职责。抽象装饰(Decorator)角色
:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。具体装饰(ConcreteDecorator)角色
:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
实现
/**
* @author yeming.gao
* @Description: 装饰模式实现
* @date 2020/5/18 15:05
*/
public class Decorator {
public static void main(String[] args) {
IComponent p = new ConcreteComponent();
p.operation();
System.out.println("---------------------------------");
IComponent d1 = new ConcreteDecorator1(p);
d1.operation();
IComponent d2 = new ConcreteDecorator2(p);
d2.operation();
}
}
/**
* 抽象构件角色
*/
interface IComponent {
public void operation();
}
/**
* 具体构件角色
*/
class ConcreteComponent implements IComponent {
public ConcreteComponent() {
System.out.println("创建具体构件角色");
}
@Override
public void operation() {
System.out.println("调用具体构件角色的方法operation()");
}
}
/**
* 抽象装饰角色
*/
abstract class ADecorator implements IComponent {
private IComponent component;
public ADecorator(IComponent component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
/**
* 具体装饰角色1
*/
class ConcreteDecorator1 extends ADecorator {
public ConcreteDecorator1(IComponent component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunction();
}
public void addedFunction() {
System.out.println("为具体构件角色增加额外的功能addedFunction()");
}
}
/**
* 具体装饰角色2
*/
class ConcreteDecorator2 extends ADecorator {
public ConcreteDecorator2(IComponent component) {
super(component);
}
@Override
public void operation() {
super.operation();
subedFunction();
}
public void subedFunction() {
System.out.println("为具体构件角色增加额外的功能subedFunction()");
}
}
程序的运行结果如下:
创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能subedFunction()
应用场景
前面讲解了关于装饰模式的结构与特点,下面介绍其适用的应用场景,装饰模式通常在以下几种情况使用。
- 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
- 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
JDK应用
装饰模式在 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)如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件,UML类图如下:
- (2)如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并,UML类图如下:
结构型设计模式之外观模式—10
- 为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
- 外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。
定义
是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
特点
优点
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点
- 不能很好地限制客户使用子系统类。
- 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
结构
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。
外观(Facade)角色
:为多个子系统对外提供一个共同的接口。子系统(Sub System)角色
:实现系统的部分功能,客户可以通过外观角色访问它。客户(Client)角色
:通过一个外观角色访问各个子系统的功能。
实现
/**
* @author yeming.gao
* @Description: 外观模式实现
* @date 2020/5/18 17:47
*/
public class Facade {
public static void main(String[] args) {
FacadeRole f = new FacadeRole();
f.method();
}
}
/**
* 外观角色
*/
class FacadeRole {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
/**
* 子系统角色01
*/
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
/**
* 子系统角色02
*/
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
/**
* 子系统角色03
*/
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}
程序的运行结果如下:
子系统01的method1()被调用!
子系统02的method2()被调用!
子系统03的method3()被调用!
应用场景
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
扩展
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题,UML类图如下:
结构型设计模式之享元模式—11
- 把相同的部分提取出来共享,,目的为了节省大量的系统资源,这就是享元模式的产生背景。
- 享元模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分;
- 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的又大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
特点
优点
- 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
缺点
- 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
- 读取享元模式的外部状态会使得运行时间稍微变长。
结构
抽象享元角色(Flyweight)
:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。具体享元(Concrete Flyweight)角色
:实现抽象享元角色中所规定的接口。非享元(Unsharable Flyweight)角色
:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。享元工厂(Flyweight Factory)角色
:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
实现
/**
* @author yeming.gao
* @Description: 享元模式实现
* @date 2020/5/19 9:44
*/
public class Flyweight {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
IFlyweightRole f01 = factory.getFlyweight("a");
IFlyweightRole f02 = factory.getFlyweight("a");
IFlyweightRole f03 = factory.getFlyweight("a");
IFlyweightRole f11 = factory.getFlyweight("b");
IFlyweightRole 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 IFlyweightRole {
public void operation(UnsharedConcreteFlyweight state);
}
/**
* 具体享元角色
*/
class ConcreteFlyweight implements IFlyweightRole {
private String key;
ConcreteFlyweight(String key) {
this.key = key;
System.out.println("具体享元" + key + "被创建!");
}
@Override
public void operation(UnsharedConcreteFlyweight outState) {
System.out.print("具体享元" + key + "被调用,");
System.out.println("非享元信息是:" + outState.getInfo());
}
}
/**
* 享元工厂角色
*/
class FlyweightFactory {
private HashMap<String, IFlyweightRole> flyweights = new HashMap<>();
public IFlyweightRole getFlyweight(String key) {
IFlyweightRole 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) 单纯享元模式,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类,UML类图如下:
- (2) 复合享元模式,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享,UML类图如下:
结构型设计模式之组合模式✳—12
“部分-整体”的关系
定义
有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。
特点
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
结构
抽象构件(Component)角色
:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。树叶构件(Leaf)角色
:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。树枝构件(Composite)角色
:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
组合模式分为透明式的组合模式和安全式的组合模式
- (1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。UML类图如下:
- (2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。UML类图如下:
实现
假如要访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素
透明式组合模式实现
/**
* @author yeming.gao
* @Description: 透明式组合模式实现
* @date 2020/5/19 10:42
*/
public class TransparentComposite {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
/**
* 抽象构件
*/
interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
/**
* 树叶构件
*/
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void add(Component c) {
}
@Override
public void remove(Component c) {
}
@Override
public Component getChild(int i) {
return null;
}
@Override
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
/**
* 树枝构件
*/
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
@Override
public void add(Component c) {
children.add(c);
}
@Override
public void remove(Component c) {
children.remove(c);
}
@Override
public Component getChild(int i) {
return children.get(i);
}
@Override
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}
程序运行结果如下:
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
安全式组合模式实现
/**
* @author yeming.gao
* @Description: 安全式组合模式实现
* @date 2020/5/19 10:42
*/
public class SafeComposite {
public static void main(String[] args) {
Composite_safe c0 = new Composite_safe();
Composite_safe c1 = new Composite_safe();
Leaf_safe leaf1 = new Leaf_safe("1");
Leaf_safe leaf2 = new Leaf_safe("2");
Leaf_safe leaf3 = new Leaf_safe("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
/**
* 抽象构件
*/
interface Component_safe {
public void operation();
}
/**
* 树叶构件
*/
class Leaf_safe implements Component_safe {
private String name;
public Leaf_safe(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
/**
* 树枝构件
*/
class Composite_safe implements Component_safe {
private ArrayList<Component_safe> children = new ArrayList<>();
public void add(Component_safe c) {
children.add(c);
}
public void remove(Component_safe c) {
children.remove(c);
}
public Component_safe getChild(int i) {
return children.get(i);
}
@Override
public void operation() {
for (Object obj : children) {
((Component_safe) obj).operation();
}
}
}
程序运行结果如下:
树叶1:被访问!
树叶2:被访问!
树叶3:被访问!
示例
【例】用组合模式实现当用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能。
说明:假如李先生到韶关“天街e角”生活用品店购物,用 1 个红色小袋子装了 2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元);用 1 个白色小袋子装了 2 包韶关香藉(单价 68 元)和 3 包韶关红茶(单价 180 元);用 1 个中袋子装了前面的红色小袋子和 1 个景德镇瓷器(单价 380 元);用 1 个大袋子装了前面的中袋子、白色小袋子和 1 双李宁牌运动鞋(单价 198 元)。最后“大袋子”中的内容有:{1 双李宁牌运动鞋(单价 198 元)、白色小袋子{2 包韶关香菇(单价 68 元)、3 包韶关红茶(单价 180 元)}、中袋子{1 个景德镇瓷器(单价 380 元)、红色小袋子{2 包婺源特产(单价 7.9 元)、1 张婺源地图(单价 9.9 元)}}},现在要求编程显示李先生放在大袋子中的所有商品信息并计算要支付的总价。
实现
/**
* @author yeming.gao
* @Description: 安全式组合模式示例
* @date 2020/5/19 10:57
*/
public class ShoppingExample {
public static void main(String[] args) {
float s = 0;
Bags smallRedBag, smallWhiteBag, mediumBag, bigBag;
Goods sp;
smallRedBag = new Bags("红色小袋子");
smallWhiteBag = new Bags("白色小袋子");
mediumBag = new Bags("中袋子");
bigBag = new Bags("大袋子");
sp = new Goods("婺源特产", 2, 7.9f);
smallRedBag.add(sp);
sp = new Goods("婺源地图", 1, 9.9f);
smallRedBag.add(sp);
sp = new Goods("韶关香菇", 2, 68);
smallWhiteBag.add(sp);
sp = new Goods("韶关红茶", 3, 180);
smallWhiteBag.add(sp);
mediumBag.add(smallRedBag);
sp = new Goods("景德镇瓷器", 1, 380);
mediumBag.add(sp);
bigBag.add(mediumBag);
bigBag.add(smallWhiteBag);
sp = new Goods("李宁牌运动鞋", 1, 198);
bigBag.add(sp);
System.out.println("您选购的商品有:");
bigBag.show();
s = bigBag.calculation();
System.out.println("要支付的总价是:" + s + "元");
}
}
/**
* 抽象物品接口
*/
interface Articles {
public float calculation();
public void show();
}
/**
* 商品
*/
class Goods implements Articles {
private String name; //名字
private int quantity;//数量
private float unitPrice; //单价
public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
@Override
public float calculation() {
return quantity * unitPrice;
}
@Override
public void show() {
System.out.println(name + "(数量:" + quantity + ",单价:" + unitPrice + "元)");
}
}
/**
* 袋子
*/
class Bags implements Articles {
private String name;
private ArrayList<Articles> bags = new ArrayList<>();
public Bags(String name) {
this.name = name;
}
public void add(Articles c) {
bags.add(c);
}
public void remove(Articles c) {
bags.remove(c);
}
public Articles getChild(int t) {
return bags.get(t);
}
@Override
public float calculation() {
float s = 0;
for (Object obj : bags) {
s += ((Articles) obj).calculation();
}
return s;
}
@Override
public void show() {
for (Articles bag : bags) {
bag.show();
}
}
}
程序运行结果如下:
您选购的商品有:
婺源特产(数量:2,单价:7.9元)
婺源地图(数量:1,单价:9.9元)
景德镇瓷器(数量:1,单价:380.0元)
韶关香菇(数量:2,单价:68.0元)
韶关红茶(数量:3,单价:180.0元)
李宁牌运动鞋(数量:1,单价:198.0元)
要支付的总价是:1279.7元
应用场景
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
扩展
如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了,如 Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。UML类图如下:
行为型设计模式之模板方法模式—13
- 设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
- 模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
特点
优点
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
结构
抽象类(Abstract Class)
:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。- ① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
- ② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
- 抽象方法:在抽象类中申明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
具体子类(Concrete Class)
:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
实现
/**
* @author yeming.gao
* @Description: 模板方法模式实现
* @date 2020/5/19 14:24
*/
public class TemplateMethod {
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 {
@Override
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
@Override
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
}
程序的运行结果如下:
抽象类中的具体方法被调用...
抽象方法1的实现被调用...
抽象方法2的实现被调用...
应用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
扩展
在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 模板方法模式扩展
* @date 2020/5/19 14:33
*/
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 {
@Override
public void abstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
@Override
public void abstractMethod2() {
System.out.println("抽象方法2的实现被调用...");
}
@Override
public void hookMethod1() {
System.out.println("钩子方法1被重写...");
}
@Override
public boolean hookMethod2() {
return false;
}
}
程序的运行结果如下:
抽象方法1的实现被调用...
钩子方法1被重写...
抽象方法2的实现被调用...
行为型设计模式之策略模式✳—14
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
特点
优点
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
- 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点
- 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
- 策略模式造成很多的策略类。
结构
抽象策略(Strategy)类
:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。具体策略(Concrete Strategy)类
:实现了抽象策略定义的接口,提供具体的算法实现。环境(Context)类
:持有一个策略类的引用,最终给客户端调用。
实现
/**
* @author yeming.gao
* @Description: 策略模式实现
* @date 2020/5/19 15:20
*/
public class Strategy {
public static void main(String[] args) {
Context c = new Context();
IStrategy s = new ConcreteStrategyA();
c.setStrategy(s);
c.strategyMethod();
System.out.println("-----------------");
s = new ConcreteStrategyB();
c.setStrategy(s);
c.strategyMethod();
}
}
/**
* 抽象策略类
*/
interface IStrategy {
public void strategyMethod(); //策略方法
}
/**
* 具体策略类A
*/
class ConcreteStrategyA implements IStrategy {
@Override
public void strategyMethod() {
System.out.println("具体策略A的策略方法被访问!");
}
}
/**
* 具体策略类B
*/
class ConcreteStrategyB implements IStrategy {
@Override
public void strategyMethod() {
System.out.println("具体策略B的策略方法被访问!");
}
}
/**
* 环境类
*/
class Context {
private IStrategy strategy;
public IStrategy getStrategy() {
return strategy;
}
public void setStrategy(IStrategy strategy) {
this.strategy = strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
程序运行结果如下:
具体策略A的策略方法被访问!
-----------------
具体策略B的策略方法被访问!
应用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
- 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
扩展
在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度,UML类图如下:
行为型设计模式之命令模式—15
可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
特点
优点
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点
- 可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
结构
抽象命令类(Command)角色
:声明执行命令的接口,拥有执行命令的抽象方法 execute()。具体命令(Concrete Command)角色
:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。实现者/接收者(Receiver)角色
:执行命令功能的相关操作,是具体命令对象业务的真正实现者。调用者/请求者(Invoker)角色
:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
实现
/**
* @author yeming.gao
* @Description: 命令模式实现
* @date 2020/5/19 16:11
*/
public class Command {
public static void main(String[] args) {
ICommand cmd = new ConcreteCommand();
Invoker ir = new Invoker(cmd);
System.out.println("客户访问调用者的call()方法...");
ir.call();
}
}
/**
* 抽象命令
*/
interface ICommand {
public abstract void execute();
}
/**
* 调用者
*/
class Invoker {
private ICommand command;
public Invoker(ICommand command) {
this.command = command;
}
public void setCommand(ICommand command) {
this.command = command;
}
public void call() {
System.out.println("调用者执行命令command...");
command.execute();
}
}
/**
* 具体命令
*/
class ConcreteCommand implements ICommand {
private Receiver receiver;
ConcreteCommand() {
receiver = new Receiver();
}
@Override
public void execute() {
receiver.action();
}
}
/**
* 接收者
*/
class Receiver {
public void action() {
System.out.println("接收者的action()方法被调用...");
}
}
程序的运行结果如下:
客户访问调用者的call()方法...
调用者执行命令command...
接收者的action()方法被调用...
应用场景
- 当系统需要将请求调用者与请求接收者解耦时,命令模式使得调用者和接收者不直接交互。
- 当系统需要随机请求命令或经常增加或删除命令时,命令模式比较方便实现这些功能。
- 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
- 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
扩展
在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 命令模式的扩展
* @date 2020/5/19 16:29
*/
public class CompositeCommand {
public static void main(String[] args) {
AbstractCommand cmd1 = new ConcreteCommand1();
AbstractCommand cmd2 = new ConcreteCommand2();
CompositeInvoker ir = new CompositeInvoker();
ir.add(cmd1);
ir.add(cmd2);
System.out.println("客户访问调用者的execute()方法...");
ir.execute();
}
}
/**
* 抽象命令
*/
interface AbstractCommand {
public abstract void execute();
}
/**
* 树叶构件: 具体命令1
*/
class ConcreteCommand1 implements AbstractCommand {
private CompositeReceiver receiver;
ConcreteCommand1() {
receiver = new CompositeReceiver();
}
@Override
public void execute() {
receiver.action1();
}
}
/**
* 树叶构件: 具体命令2
*/
class ConcreteCommand2 implements AbstractCommand {
private CompositeReceiver receiver;
ConcreteCommand2() {
receiver = new CompositeReceiver();
}
@Override
public void execute() {
receiver.action2();
}
}
/**
* 树枝构件: 调用者
*/
class CompositeInvoker implements AbstractCommand {
private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>();
public void add(AbstractCommand c) {
children.add(c);
}
public void remove(AbstractCommand c) {
children.remove(c);
}
public AbstractCommand getChild(int i) {
return children.get(i);
}
@Override
public void execute() {
for (Object obj : children) {
((AbstractCommand) obj).execute();
}
}
}
/**
* 接收者
*/
class CompositeReceiver {
public void action1() {
System.out.println("接收者的action1()方法被调用...");
}
public void action2() {
System.out.println("接收者的action2()方法被调用...");
}
}
程序的运行结果如下:
客户访问调用者的execute()方法...
接收者的action1()方法被调用...
接收者的action2()方法被调用...
行为型设计模式之责任链模式✳—16
- 通常情况下,可以通过数据链表来实现职责链模式的数据结构。
- 注意:责任链模式也叫职责链模式。
- 在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
定义
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
特点
优点
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
- 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
结构
抽象处理者(Handler)角色
:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。具体处理者(Concrete Handler)角色
:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。客户类(Client)角色
:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
责任链
实现
/**
* @author yeming.gao
* @Description: 责任链模式实现
* @date 2020/5/20 19:25
*/
public class ChainOfResponsibility {
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 {
@Override
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 {
@Override
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负责处理该请求!
示例
【例】用责任链模式设计一个请假条审批模块。
分析:假如规定学生请假小于或等于 2 天,班主任可以批准;小于或等于 7 天,系主任可以批准;小于或等于 10 天,院长可以批准;其他情况不予批准;这个实例适合使用职责链模式实现。
首先,定义一个领导类(Leader),它是抽象处理者,包含了一个指向下一位领导的指针 next 和一个处理假条的抽象处理方法 handleRequest(int LeaveDays);然后,定义班主任类(ClassAdviser)、系主任类(DepartmentHead)和院长类(Dean),它们是抽象处理者的子类,是具体处理者,必须根据自己的权力去实现父类的 handleRequest(int LeaveDays) 方法,如果无权处理就将假条交给下一位具体处理者,直到最后;客户类负责创建处理链,并将假条交给链头的具体处理者(班主任)。UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 责任链模式实例
* @date 2020/5/20 19:56
*/
public class LeaveApproval {
public static void main(String[] args) {
//组装责任链
Leader teacher1 = new ClassAdviser();
Leader teacher2 = new DepartmentHead();
Leader teacher3 = new Dean();
//Leader teacher4=new DeanOfStudies();
teacher1.setNext(teacher2);
teacher2.setNext(teacher3);
//teacher3.setNext(teacher4);
//提交请求
System.out.print("小明请2天假 -->");
teacher1.handleRequest(2);
System.out.print("小明请5天假 -->");
teacher1.handleRequest(5);
System.out.print("小明请8天假 -->");
teacher1.handleRequest(8);
System.out.print("小明请20天假 -->");
teacher1.handleRequest(20);
}
}
/**
* 抽象处理者:领导类
*/
abstract class Leader {
private Leader next;
public void setNext(Leader next) {
this.next = next;
}
public Leader getNext() {
return next;
}
//处理请求的方法
public abstract void handleRequest(int LeaveDays);
}
/**
* 具体处理者1:班主任类
*/
class ClassAdviser extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 2) {
System.out.println("班主任批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
/**
* 具体处理者2:系主任类
*/
class DepartmentHead extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 7) {
System.out.println("系主任批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
/**
* 具体处理者3:院长类
*/
class Dean extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 10) {
System.out.println("院长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
/**
* 具体处理者4:教务处长类
*/
class DeanOfStudies extends Leader {
@Override
public void handleRequest(int LeaveDays) {
if (LeaveDays <= 20) {
System.out.println("教务处长批准您请假" + LeaveDays + "天。");
} else {
if (getNext() != null) {
getNext().handleRequest(LeaveDays);
} else {
System.out.println("请假天数太多,没有人批准该假条!");
}
}
}
}
程序运行结果如下:
小明请2天假 -->班主任批准您请假2天。
小明请5天假 -->系主任批准您请假5天。
小明请8天假 -->院长批准您请假8天。
小明请20天假 -->请假天数太多,没有人批准该假条!
应用场景
- 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
- 可动态指定一组对象处理请求,或添加新的处理者。
- 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
扩展
职责链模式存在以下两种情况:
- 纯的职责链模式 :一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
- 不纯的职责链模式 :允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
行为型设计模式之状态模式—17
- 状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。
- 状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
特点
优点
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
结构
环境(Context)角色
:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。抽象状态(State)角色
:定义一个接口,用以封装环境对象中的特定状态所对应的行为。具体状态(Concrete State)角色
:实现抽象状态所对应的行为。
实现
/**
* @author yeming.gao
* @Description: 状态模式实现
* @date 2020/5/21 9:53
*/
public class State {
public static void main(String[] args) {
Context context = new Context(); //创建环境
context.handle(); //处理请求
context.handle();
context.handle();
context.handle();
}
}
/**
* 环境类
*/
class Context {
private BaseState state;
//定义环境类的初始状态
public Context() {
this.state = new ConcreteStateA();
}
//设置新状态
public void setState(BaseState state) {
this.state = state;
}
//读取状态
public BaseState getState() {
return (state);
}
//对请求做处理
public void handle() {
state.handle(this);
}
}
/**
* 抽象状态类
*/
abstract class BaseState {
public abstract void handle(Context context);
}
/**
* 具体状态A类
*/
class ConcreteStateA extends BaseState {
@Override
public void handle(Context context) {
System.out.println("当前状态是 A.");
context.setState(new ConcreteStateB());
}
}
/**
* 具体状态B类
*/
class ConcreteStateB extends BaseState {
@Override
public void handle(Context context) {
System.out.println("当前状态是 B.");
context.setState(new ConcreteStateA());
}
}
程序运行结果如下:
当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.
示例
【例】用“状态模式”设计一个学生成绩的状态转换程序。
分析:本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。
首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态名属性和当前分数属性,以及加减分方法 addScore(intx) 和检查当前状态的抽象方法 checkState();然后,定义“不及格”状态类 LowState、“中等”状态类 MiddleState 和“优秀”状态类 HighState,它们是具体状态类,实现 checkState() 方法,负责检査自己的状态,并根据情况转换;最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法 add(int score),客户类通过该方法来改变成绩状态。UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 状态模式示例:设计一个学生成绩的状态转换程序。
* @date 2020/5/21 10:21
*/
public class ScoreStateTest {
public static void main(String[] args) {
ScoreContext account = new ScoreContext();
System.out.println("学生成绩状态测试:");
account.add(30);
account.add(40);
account.add(25);
account.add(-15);
account.add(-25);
}
}
/**
* 环境类
*/
class ScoreContext {
private AbstractState state;
ScoreContext() {
state = new LowState(this);
}
public void setState(AbstractState state) {
this.state = state;
}
public AbstractState getState() {
return state;
}
public void add(int score) {
state.addScore(score);
}
}
/**
* 抽象状态类
*/
abstract class AbstractState {
protected ScoreContext hj; //环境
protected String stateName; //状态名
protected int score; //分数
public abstract void checkState(); //检查当前状态
public void addScore(int x) {
score += x;
System.out.print("加上:" + x + "分,\t当前分数:" + score);
checkState();
System.out.println("分,\t当前状态:" + hj.getState().stateName);
}
}
/**
* 具体状态类:不及格
*/
class LowState extends AbstractState {
public LowState(ScoreContext h) {
hj = h;
stateName = "不及格";
score = 0;
}
public LowState(AbstractState state) {
hj = state.hj;
stateName = "不及格";
score = state.score;
}
@Override
public void checkState() {
if (score >= 90) {
hj.setState(new HighState(this));
} else if (score >= 60) {
hj.setState(new MiddleState(this));
}
}
}
/**
* 具体状态类:中等
*/
class MiddleState extends AbstractState {
public MiddleState(AbstractState state) {
hj = state.hj;
stateName = "中等";
score = state.score;
}
@Override
public void checkState() {
if (score < 60) {
hj.setState(new LowState(this));
} else if (score >= 90) {
hj.setState(new HighState(this));
}
}
}
/**
* 具体状态类:优秀
*/
class HighState extends AbstractState {
public HighState(AbstractState state) {
hj = state.hj;
stateName = "优秀";
score = state.score;
}
@Override
public void checkState() {
if (score < 60) {
hj.setState(new LowState(this));
} else if (score < 90) {
hj.setState(new MiddleState(this));
}
}
}
程序运行结果如下:
学生成绩状态测试:
加上:30分, 当前分数:30分, 当前状态:不及格
加上:40分, 当前分数:70分, 当前状态:中等
加上:25分, 当前分数:95分, 当前状态:优秀
加上:-15分, 当前分数:80分, 当前状态:中等
加上:-25分, 当前分数:55分, 当前状态:不及格
应用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
扩展
在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 状态模式扩展
* @date 2020/5/21 10:31
*/
public class FlyweightStatePattern {
public static void main(String[] args) {
ShareContext context = new ShareContext(); //创建环境
context.Handle(); //处理请求
context.Handle();
context.Handle();
context.Handle();
}
}
/**
* 环境类
*/
class ShareContext {
private ShareState state;
private HashMap<String, ShareState> stateSet = new HashMap<>();
public ShareContext() {
state = new ConcreteState1();
stateSet.put("1", state);
state = new ConcreteState2();
stateSet.put("2", state);
state = getState("1");
}
//设置新状态
public void setState(ShareState state) {
this.state = state;
}
//读取状态
public ShareState getState(String key) {
ShareState s = (ShareState) stateSet.get(key);
return s;
}
//对请求做处理
public void Handle() {
state.handle(this);
}
}
/**
* 抽象状态类
*/
abstract class ShareState {
public abstract void handle(ShareContext context);
}
/**
* 具体状态1类
*/
class ConcreteState1 extends ShareState {
@Override
public void handle(ShareContext context) {
System.out.println("当前状态是: 状态1");
context.setState(context.getState("2"));
}
}
/**
* 具体状态2类
*/
class ConcreteState2 extends ShareState {
@Override
public void handle(ShareContext context) {
System.out.println("当前状态是: 状态2");
context.setState(context.getState("1"));
}
}
程序运行结果如下:
当前状态是: 状态1
当前状态是: 状态2
当前状态是: 状态1
当前状态是: 状态2
行为型设计模式之观察者模式✳—18
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
特点
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 目标与观察者之间建立了一套触发机制。
缺点
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
结构
抽象主题(Subject)角色
:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。具体主题(Concrete Subject)角色
:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。抽象观察者(Observer)角色
:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。具体观察者(Concrete Observer)角色
:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
实现
/**
* @author yeming.gao
* @Description: 观察者模式实现
* @date 2020/5/21 10:51
*/
public class Observer {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
IObserver obs1 = new ConcreteObserver1();
IObserver obs2 = new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
/**
* 抽象观察者
*/
interface IObserver {
void response(); //反应
}
/**
* 抽象目标
*/
abstract class Subject {
protected List<IObserver> observers = new ArrayList<>();
//增加观察者方法
public void add(IObserver observer) {
observers.add(observer);
}
//删除观察者方法
public void remove(IObserver observer) {
observers.remove(observer);
}
public abstract void notifyObserver(); //通知观察者方法
}
/**
* 具体目标
*/
class ConcreteSubject extends Subject {
@Override
public void notifyObserver() {
System.out.println("具体目标发生改变...");
System.out.println("--------------");
for (Object obs : observers) {
((IObserver) obs).response();
}
}
}
/**
* 具体观察者1
*/
class ConcreteObserver1 implements IObserver {
@Override
public void response() {
System.out.println("具体观察者1作出反应!");
}
}
/**
* 具体观察者1
*/
class ConcreteObserver2 implements IObserver {
@Override
public void response() {
System.out.println("具体观察者2作出反应!");
}
}
程序运行结果如下:
具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!
示例
【例】利用观察者模式设计一个程序,分析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。
分析:当“人民币汇率”升值时,进口公司的进口产品成本降低且利润率提升,出口公司的出口产品收入降低且利润率降低;当“人民币汇率”贬值时,进口公司的进口产品成本提升且利润率降低,出口公司的出口产品收入提升且利润率提升。
这里的汇率(Rate)类是抽象目标类,它包含了保存观察者(Company)的 List 和增加/删除观察者的方法,以及有关汇率改变的抽象方法 change(int number);而人民币汇率(RMBrate)类是具体目标, 它实现了父类的 change(int number) 方法,即当人民币汇率发生改变时通过相关公司;公司(Company)类是抽象观察者,它定义了一个有关汇率反应的抽象方法 response(int number);进口公司(ImportCompany)类和出口公司(ExportCompany)类是具体观察者类,它们实现了父类的 response(int number) 方法,即当它们接收到汇率发生改变的通知时作为相应的反应。UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 观察者示例:利用观察者模式设计一个程序,分
* 析“人民币汇率”的升值或贬值对进口公司的进口产品成本或出口公司的出口产品收入以及公司的利润率的影响。
* @date 2020/5/21 11:11
*/
public class RMBrateTest {
public static void main(String[] args) {
Rate rate = new RMBrate();
Company watcher1 = new ImportCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
/**
* 抽象观察者:公司
*/
interface Company {
void response(int number);
}
/**
* 具体观察者1:进口公司
*/
class ImportCompany implements Company {
@Override
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了进口产品成本,提升了进口公司利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了进口产品成本,降低了进口公司利润率。");
}
}
}
/**
* 具体观察者2:出口公司
*/
class ExportCompany implements Company {
@Override
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
}
}
}
/**
* 抽象目标:汇率
*/
abstract class Rate {
protected List<Company> companys = new ArrayList<>();
//增加观察者方法
public void add(Company company) {
companys.add(company);
}
//删除观察者方法
public void remove(Company company) {
companys.remove(company);
}
public abstract void change(int number);
}
/**
* 具体目标:人民币汇率
*/
class RMBrate extends Rate {
@Override
public void change(int number) {
for (Company obs : companys) {
obs.response(number);
}
}
}
程序运行结果如下:
人民币汇率升值10个基点,降低了进口产品成本,提升了进口公司利润率。
人民币汇率升值10个基点,降低了出口产品收入,降低了出口公司的销售利润率。
人民币汇率贬值9个基点,提升了进口产品成本,降低了进口公司利润率。
人民币汇率贬值9个基点,提升了出口产品收入,提升了出口公司的销售利润率。
应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
扩展
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1. Observable类
Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
- 1,void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。、
- 2,void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
- 3,void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
2. Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
3. 示例:利用 Observable 类和 Observer 接口实现原油期货的观察者模式实例
分析:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。
实现
/**
* @author yeming.gao
* @Description: 观察者模式扩展
* @date 2020/5/21 11:20
*/
public class CrudeOilFutures {
public static void main(String[] args) {
OilFutures oil = new OilFutures();
Observer bull = new Bull(); //多方
Observer bear = new Bear(); //空方
oil.addObserver(bull);
oil.addObserver(bear);
oil.setPrice(10);
oil.setPrice(-8);
}
}
/**
* 具体目标类:原油期货
*/
class OilFutures extends Observable {
private float price;
public float getPrice() {
return this.price;
}
public void setPrice(float price) {
super.setChanged(); //设置内部标志位,注明数据发生变化
super.notifyObservers(price); //通知观察者价格改变了
this.price = price;
}
}
/**
* 具体观察者类:多方
*/
class Bull implements Observer {
@Override
public void update(Observable o, Object arg) {
Float price = ((Float) arg).floatValue();
if (price > 0) {
System.out.println("油价上涨" + price + "元,多方高兴了!");
} else {
System.out.println("油价下跌" + (-price) + "元,多方伤心了!");
}
}
}
/**
* 具体观察者类:空方
*/
class Bear implements Observer {
@Override
public void update(Observable o, Object arg) {
Float price = ((Float) arg).floatValue();
if (price > 0) {
System.out.println("油价上涨" + price + "元,空方伤心了!");
} else {
System.out.println("油价下跌" + (-price) + "元,空方高兴了!");
}
}
}
程序运行结果如下:
油价上涨10.0元,空方伤心了!
油价上涨10.0元,多方高兴了!
油价下跌8.0元,空方高兴了!
油价下跌8.0元,多方伤心了!
行为型设计模式之中介者模式—19
- 中介者模式,它将大大降低对象之间的耦合性,提高系统的灵活性。
- 中介者模式实现的关键是找出“中介者”
定义
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
特点
优点
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点
- 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
结构
抽象中介者(Mediator)角色
:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。具体中介者(ConcreteMediator)角色
:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。抽象同事类(Colleague)角色
:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。具体同事类(Concrete Colleague)角色
:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
实现
/**
* @author yeming.gao
* @Description: 中介者模式实现
* @date 2020/5/21 13:47
*/
public class Mediator {
public static void main(String[] args) {
BaseMediator 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 BaseMediator {
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl); //转发
}
/**
* 具体中介者
*/
class ConcreteMediator extends BaseMediator {
private List<Colleague> colleagues = new ArrayList<>();
@Override
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)) {
colleagues.add(colleague);
colleague.setMedium(this);
}
}
@Override
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
ob.receive();
}
}
}
}
/**
* 抽象同事类
*/
abstract class Colleague {
protected BaseMediator mediator;
public void setMedium(BaseMediator mediator) {
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}
/**
* 具体同事类1
*/
class ConcreteColleague1 extends Colleague {
@Override
public void receive() {
System.out.println("具体同事类1收到请求。");
}
@Override
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); //请中介者转发
}
}
/**
* 具体同事类2
*/
class ConcreteColleague2 extends Colleague {
@Override
public void receive() {
System.out.println("具体同事类2收到请求。");
}
@Override
public void send() {
System.out.println("具体同事类2发出请求。");
mediator.relay(this); //请中介者转发
}
}
程序的运行结果如下:
具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。
应用场景
- 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
扩展
在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
- 1,不定义中介者接口,把具体中介者对象实现成为单例。
- 2,同事对象不持有中介者,而是在需要的时直接获取中介者对象并调用。
实现
/**
* @author yeming.gao
* @Description: 中介模式扩展
* @date 2020/5/21 14:02
*/
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<>();
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)) {
ob.receive();
}
}
}
}
/**
* 抽象同事类
*/
interface SimpleColleague {
void receive();
void send();
}
/**
* 具体同事类1
*/
class SimpleConcreteColleague1 implements SimpleColleague {
SimpleConcreteColleague1() {
SimpleMediator smd = SimpleMediator.getMedium();
smd.register(this);
}
@Override
public void receive() {
System.out.println("具体同事类1:收到请求。");
}
@Override
public void send() {
SimpleMediator smd = SimpleMediator.getMedium();
System.out.println("具体同事类1:发出请求...");
smd.relay(this); //请中介者转发
}
}
/**
* 具体同事类2
*/
class SimpleConcreteColleague2 implements SimpleColleague {
SimpleConcreteColleague2() {
SimpleMediator smd = SimpleMediator.getMedium();
smd.register(this);
}
@Override
public void receive() {
System.out.println("具体同事类2:收到请求。");
}
@Override
public void send() {
SimpleMediator smd = SimpleMediator.getMedium();
System.out.println("具体同事类2:发出请求...");
smd.relay(this); //请中介者转发
}
}
程序的运行结果如下:
具体同事类1:发出请求...
具体同事类2:收到请求。
-----------------
具体同事类2:发出请求...
具体同事类1:收到请求。
行为型设计模式之迭代器模式—20
迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。
定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式。
特点
优点
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 遍历任务交由迭代器完成,这简化了聚合类。
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点
- 增加了类的个数,这在一定程度上增加了系统的复杂性。
结构
抽象聚合(Aggregate)角色
:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。具体聚合(ConcreteAggregate)角色
:实现抽象聚合类,返回一个具体迭代器的实例。抽象迭代器(Iterator)角色
:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。具体迭代器(Concretelterator)角色
:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
实现
/**
* @author yeming.gao
* @Description: 迭代器模式实现
* @date 2020/5/21 14:26
*/
public class Iterator {
public static void main(String[] args) {
Aggregate ag = new ConcreteAggregate();
ag.add("中山大学");
ag.add("华南理工");
ag.add("韶关学院");
System.out.print("聚合的内容有:");
IIterator 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 IIterator getIterator();
}
/**
* 抽象迭代器
*/
interface IIterator {
Object first();
Object next();
boolean hasNext();
}
/**
* 具体聚合
*/
class ConcreteAggregate implements Aggregate {
private List<Object> list = new ArrayList<>();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public IIterator getIterator() {
return (new ConcreteIterator(list));
}
}
/**
* 具体迭代器
*/
class ConcreteIterator implements IIterator {
private List<Object> list = null;
private int index = -1;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
@Override
public boolean hasNext() {
if (index < list.size() - 1) {
return true;
} else {
return false;
}
}
@Override
public Object first() {
index = 0;
Object obj = list.get(index);
return obj;
}
@Override
public Object next() {
Object obj = null;
if (this.hasNext()) {
obj = list.get(++index);
}
return obj;
}
}
程序运行结果如下:
聚合的内容有:中山大学 华南理工 韶关学院
First:中山大学
应用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
扩展
迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问,UML类图如下:
行为型设计模式之访问者模式—21
- 访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
- 访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类。
定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
特点
优点
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
结构
抽象访问者(Visitor)角色
:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。具体访问者(ConcreteVisitor)角色
:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。抽象元素(Element)角色
:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。具体元素(ConcreteElement)角色
:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。对象结构(Object Structure)角色
:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
实现
/**
* @author yeming.gao
* @Description: 访问者模式实现
* @date 2020/5/21 15:14
*/
public class Visitor {
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
IVisitor visitor = new ConcreteVisitorA();
os.accept(visitor);
System.out.println("------------------------");
visitor = new ConcreteVisitorB();
os.accept(visitor);
}
}
/**
* 抽象访问者
*/
interface IVisitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
/**
* 具体访问者A类
*/
class ConcreteVisitorA implements IVisitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}
/**
* 具体访问者B类
*/
class ConcreteVisitorB implements IVisitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者B访问-->" + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->" + element.operationB());
}
}
/**
* 抽象元素类
*/
interface Element {
void accept(IVisitor visitor);
}
/**
* 具体元素A类
*/
class ConcreteElementA implements Element {
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作。";
}
}
/**
* 具体元素B类
*/
class ConcreteElementB implements Element {
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作。";
}
}
/**
* 对象结构角色
*/
class ObjectStructure {
private List<Element> list = new ArrayList<>();
public void accept(IVisitor visitor) {
Iterator<Element> i = list.iterator();
while (i.hasNext()) {
i.next().accept(visitor);
}
}
public void add(Element element) {
list.add(element);
}
public void remove(Element element) {
list.remove(element);
}
}
程序的运行结果如下:
具体访问者A访问-->具体元素A的操作。
具体访问者A访问-->具体元素B的操作。
------------------------
具体访问者B访问-->具体元素A的操作。
具体访问者B访问-->具体元素B的操作。
应用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
扩展
访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。
- (1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。
- (2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,UML类图如下:
行为型设计模式之备忘录模式—22
- 备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
- 备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类。
定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
特点
优点
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点
- 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
结构
发起人(Originator)角色
:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。备忘录(Memento)角色
:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。管理者(Caretaker)角色
:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
实现
/**
* @author yeming.gao
* @Description: 备忘录模式实现
* @date 2020/5/21 15:44
*/
public class Memento {
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 MementoMode {
private String state;
public MementoMode(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 MementoMode createMemento() {
return new MementoMode(state);
}
public void restoreMemento(MementoMode m) {
this.setState(m.getState());
}
}
/**
* 管理者
*/
class Caretaker {
private MementoMode memento;
public void setMemento(MementoMode m) {
memento = m;
}
public MementoMode getMemento() {
return memento;
}
}
程序运行的结果如下:
初始状态:S0
新的状态:S1
恢复状态:S0
应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
扩展
在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,UML类图如下:
实现
/**
* @author yeming.gao
* @Description: 备忘录模式扩展
* @date 2020/5/21 15:56
*/
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());
}
@Override
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
行为型设计模式之解释器模式—23
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
1) 文法
文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下:
〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉大学生|英语
〈动词〉::=是|学习
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。
2) 句子
句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。
3) 语法树
语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。下图所示是“我是大学生”的语法树。
有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
定义
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
文法”指语言的语法规则,而“句子”是语言集中的元素。
特点
优点
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
结构
抽象表达式(Abstract Expression)角色
:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。终结符表达式(Terminal Expression)角色
:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。非终结符表达式(Nonterminal Expression)角色
:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。环境(Context)角色
:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。客户端(Client)
:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
实现
/**
* @author yeming.gao
* @Description: 解释器模式实现
* @date 2020/5/21 16:31
*/
public class Interpreter {
}
/**
* 抽象表达式类
*/
interface AbstractExpression {
public Object interpret(String info); //解释方法
}
/**
* 终结符表达式类
*/
class TerminalExpression_Interpreter implements AbstractExpression {
@Override
public Object interpret(String info) {
//对终结符表达式的处理
return null;
}
}
/**
* 非终结符表达式类
*/
class NonterminalExpression_Interpreter implements AbstractExpression {
private AbstractExpression exp1;
private AbstractExpression exp2;
@Override
public Object interpret(String info) {
//非对终结符表达式的处理
return null;
}
}
/**
* 环境类
*/
class Context_Interpreter {
private AbstractExpression exp;
public Context_Interpreter() {
//数据初始化
}
public void operation(String info) {
//调用相关表达式类的解释方法
}
}
示例
【例】用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。
说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。
<expression> ::= <city>的<person>
<city> ::= 韶关|广州
<person> ::= 老人|妇女|儿童
然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。
- 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
- 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
- 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
- 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。
实现
/**
* @author yeming.gao
* @Description: 解释器模式示例
* 文法规则
* <expression> ::= <city>的<person>
* <city> ::= 韶关|广州
* <person> ::= 老人|妇女|儿童
* @date 2020/5/21 16:42
*/
public class InterpreterPatternDemo {
public static void main(String[] args) {
Context bus = new Context();
bus.freeRide("韶关的老人");
bus.freeRide("韶关的年轻人");
bus.freeRide("广州的妇女");
bus.freeRide("广州的儿童");
bus.freeRide("山东的儿童");
}
}
/**
* 抽象表达式类
*/
interface Expression {
public boolean interpret(String info);
}
/**
* 终结符表达式类
*/
class TerminalExpression implements Expression {
private Set<String> set = new HashSet<>();
public TerminalExpression(String[] data) {
for (int i = 0; i < data.length; i++) {
set.add(data[i]);
}
}
@Override
public boolean interpret(String info) {
if (set.contains(info)) {
return true;
}
return false;
}
}
/**
* 非终结符表达式类
*/
class AndExpression implements Expression {
private Expression city = null;
private Expression person = null;
public AndExpression(Expression city, Expression person) {
this.city = city;
this.person = person;
}
@Override
public boolean interpret(String info) {
String s[] = info.split("的");
return city.interpret(s[0]) && person.interpret(s[1]);
}
}
/**
* 环境类
*/
class Context {
private String[] citys = {"韶关", "广州"};
private String[] persons = {"老人", "妇女", "儿童"};
private Expression cityPerson;
public Context() {
Expression city = new TerminalExpression(citys);
Expression person = new TerminalExpression(persons);
cityPerson = new AndExpression(city, person);
}
public void freeRide(String info) {
boolean ok = cityPerson.interpret(info);
if (ok) {
System.out.println("您是" + info + ",您本次乘车免费!");
} else {
System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
}
}
}
程序运行结果如下:
您是韶关的老人,您本次乘车免费!
韶关的年轻人,您不是免费人员,本次乘车扣费2元!
您是广州的妇女,您本次乘车免费!
您是广州的儿童,您本次乘车免费!
山东的儿童,您不是免费人员,本次乘车扣费2元!
应用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
扩展
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
示例
public class JepDemo {
public static void main(String[] args) throws JepException
{
Jep jep=new Jep();
//定义要计算的数据表达式
String 存款利息="本金*利率*时间";
//给相关变量赋值
jep.addVariable("本金",10000);
jep.addVariable("利率",0.038);
jep.addVariable("时间",2);
jep.parse(存款利息); //解析表达式
Object accrual=jep.evaluate(); //计算
System.out.println("存款利息:"+accrual);
}
}
程序运行结果如下:
存款利息:760.0