Java23种设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
创建型模式
单例模式
单例模式就是保证只能创建一个对象实例,怎么保证只有一个对象呢?
其实只需要三步就可以保证对象的唯一性
(1)不允许其他程序用new对象。
因为new就是开辟新的空间,在这里更改数据只是更改的所创建的对象的数据,如果可以new的话,每一次new都产生一个对象,这样肯定保证不了对象的唯一性。
(2)在该类中创建对象
因为不允许其他程序new对象,所以这里的对象需要在本类中new出来
(3)对外提供一个可以让其他程序获取该对象的方法
因为对象是在本类中创建的,所以需要提供一个方法让其它的类获取这个对象。
那么这三步怎么用代码实现呢?将上述三步转换成代码描述是这样的
(1)私有化该类的构造函数
(2)通过new在本类中创建一个本类对象
(3)定义一个公有的方法,将在该类中所创建的对象返回
单例模式有两种写法:饿汉式和懒汉式
饿汉式
优点:这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。
缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。
懒汉式
优点:按需加载,不会浪费内存资源
缺点:有线程安全
**双重检查锁定(Double-Checked Locking)**是一种在多线程环境中保证单例模式实例化的常用机制,但它存在一些问题,可能会触发一些不期望的行为。这些问题包括:
指令重排序:在某些情况下,JVM可能会对代码进行指令重排序,导致在多线程环境下初始化过程中的操作顺序被改变,从而可能返回一个尚未完全初始化的实例。
可见性问题:在旧版本的Java规范中,由于缓存一致性的问题,有可能导致一个线程看到的uniqueInstance不是最新的。
并发问题:尽管双重检查锁定可以在一定程度上解决并发性能问题,但是在某些情况下,仍然可能存在并发问题,例如在构造函数中可能出现的竞态条件。
因此,在使用双重检查锁定时,需要特别小心地处理这些问题,并且最好结合volatile关键字、静态内部类等其他机制来确保线程安全和正确性。在Java 5及之后的版本中,还可以考虑使用基于类初始化的解决方案,如使用静态内部类等方式来实现延迟初始化,以规避双重检查锁定机制可能带来的问题。
建造者模式
建造者模式 : 将 一个复杂对象 的 构建过程 与其 表示 分离 , 使得 同样的构建过程 , 可以 创建不同的表示 ;
用户只需要 指定 需要建造的类型 就可以 得到该类型对应的产品实例 , 不关心建造过程细节 ;
建造者模式就是 如何逐步构建包含多个组件的对象 , 相同的构建过程 , 可以创建不同的产品
建造者模式适用场景 :
- 结构复杂 : 对象 有 非常复杂的内部结构 , 有很多属性 ;
- 分离创建和使用 : 想把 复杂对象 的 创建 和 使用 分离 ;
建造者模式优点 :
- 封装性好 : 创建 和 使用 分离 ;
- 扩展性好 : 建造类之间 相互独立 , 在 一定程度上解耦 ;
建造者模式缺点 :
- 增加类数量 : 产生多余的 Builder 对象 ;
- 内部修改困难 : 如果 产品内部发生变化 , 建造者也要相应修改 ;
package builder2;
/**
* 学生类
*/
public class Student {
private String name;
private String age;
private String number;
private String address;
private String school;
public Student(StudentBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.number = builder.number;
this.address = builder.address;
this.school = builder.school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", number='" + number + '\'' +
", address='" + address + '\'' +
", school='" + school + '\'' +
'}';
}
/**
* 使用静态内部类作为创建者
* 将具体的业务类 及其 创建者 定义在一个类中
* 使用链式调用 , 每个 build 步骤都返回创建者本身
*/
public static class StudentBuilder {
private String name;
private String age;
private String number;
private String address;
private String school;
public StudentBuilder buildName(String name) {
this.name = name;
return this;
}
public StudentBuilder buildAge(String age) {
this.age = age;
return this;
}
public StudentBuilder buildNumber(String number) {
this.number = number;
return this;
}
public StudentBuilder buildAddress(String address) {
this.address = address;
return this;
}
public StudentBuilder buildSchool(String school) {
this.school = school;
return this;
}
public Student build() {
return new Student(this);
}
}
}
工厂模式
简单工厂模式的优缺点
简单工厂模式的优点:将对象的创建交给专门的工厂类负责,实现了对象的创建和对象的使用分离。
简单工厂模式的缺点:工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,
工厂方法模式的优缺点
工厂方法模式的优点:遵循了开闭原则,扩展性极强,可维护性高。
工厂方法模式的缺点:增加了类的数量,当有成千上万个类型的产品时,就需要有成千上万个工厂类来生产这些产品。
抽象工厂模式的优缺点
抽象工厂模式的优点:增加固定类型产品的不同具体工厂比较方便。例如现在女娲要创造一个绿皮肤的人,只需要再创建一个绿皮肤人的工厂继承抽象工厂就可以了。
抽象工厂模式的缺点:类图优点复杂,可读性没有工厂方法模式那么好。
原型模式
原型模式的基本概念是通过复制现有的对象来创建新的对象,而不是通过构造函数来实例化新对象。这种方式使得我们可以在运行时动态地创建和修改对象。原型模式主要包括以下几个角色:
- 原型接口(Prototype):定义了复制方法的接口,通常由
Cloneable
接口实现。 - 具体原型(ConcretePrototype):实现原型接口的类,实现具体的复制方法。
- 客户端(Client):使用具体原型类的实例来创建新的对象。
原型模式的应用场景
原型模式适用于以下几种场景:
- 当对象的构造过程消耗资源较多,但需要大量相似的对象时。
- 当需要在运行时动态地创建新对象,但不希望使用构造函数的方式。
- 当需要创建一个与现有对象具有相同状态的新对象,且这些状态可以独立修改。
1. 创建原型接口
由于Java中已经提供了Cloneable
接口,我们不需要再创建原型接口。Cloneable
接口是一个标记接口,没有具体方法,用于表示一个对象是可克隆的。
2. 创建具体原型类
创建一个表示图形的Shape
类,实现Cloneable
接口。
public class Shape implements Cloneable {
private String type;
private int x;
private int y;
public Shape(String type, int x, int y) {
this.type = type;
this.x = x;
this.y = y;
}
public Shape(Shape target) {
if (target != null) {
this.type = target.type;
this.x = target.x;
this.y = target.y;
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
return new Shape(this);
}
// Getter and Setter methods
}
在这个例子中,Shape
类有一个带参数的构造函数和一个复制构造函数。clone()
方法通过调用复制构造函数来创建一个新的Shape
对象。
3. 客户端使用具体原型类创建新对象
现在我们可以使用Shape
类的clone()
方法创建一个与现有对象具有相同状态的新对象。
public class PrototypeDemo {
public static void main(String[] args) {
Shape originalShape = new Shape("Circle", 10, 20);
System.out.println("Original Shape: " + originalShape);
try {
Shape clonedShape = (Shape) originalShape.clone();
System.out.println("Cloned Shape: " + clonedShape);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
运行这个示例程序,可以看到originalShape
和clonedShape
具有相同的属性,但它们是不同的对象。
结构型模式
适配器模式
适配器模式是一种结构型设计模式,它允许将不兼容的对象转换成可兼容的接口。主要目的是解决在不改变现有代码的情况下,使不兼容的接口之间能够正常工作,通过创建一个中间转换的适配器来将一个对象转换成我们所需要的接口。
角色组成
- 目标接口(target):需要适配的标准接口。
- 源对象(source):需要被适配的不兼容对象。
- 适配器对象(adapter):充当中间转换角色,该对象将源对象转换成目标接口。
优点:
安全可靠:封装了旧接口,对客户端透明,客户端代码无需修改。
提高复用性:可以复用不兼容的类;可以对不同的类无需修改,就可以进行组合。
扩展性好:在应用程序开发过程中,可以增加新的适配器和被适配对象。
缺点:
过多的适配器会导致系统结构复杂。
如果适配器没有实现好,可能会拖慢整个系统的性能。
滥用适配器模式会导致系统设计紊乱。
Adaptee(被适配对象)
public class Adaptee {
/**
* 需要被适配的功能
* 这里用插座转换举例
*/
public void commonThreeHoleSocket(){
System.out.println("hi,我是一个常见的三孔插座");
}
}
tatget(目标)
public interface Target {
/**
* 定义新插座的规范是新插座的形状应该是一个双头插座
*/
public void doubleHoleSocket();
}
Adapter(适配器)
public class Adapter extends Adaptee implements Target {
/**
* 采用继承的方式实现转换功能
*/
@Override
public void doubleHoleSocket() {
// 调用继承的方法,对其进行增强或处理
this.commonThreeHoleSocket();
System.out.println("==========开始转换==========");
System.out.println("oh,我变成了两孔插座");
}
}
Client(请求者)
public class Client {
public static void main(String[] args) {
Target newPlug = new Adapter();
// 对于使用者来说,它只需要知道适配器提供了一个两孔插头即可
newPlug.doubleHoleSocket();
System.out.println("这是一个两孔插头");
}
}
什么时候使用适配器模式
相当一部分人会认为“如果某个方法就是我们需要的方法,我们直接在程序中使用就可以了,为什么会需要适配器模式?”
实际上,我们在开发过程中经常会使用到的是已经由别人开发完成的类(众所周知,程序员的键盘上只需要保留ctrl+c+v三个按键),这些类通常经过了充分的测试,稳定性较高。使用适配器模式可以对这些现有类进行适配,当出现bug时,我们很明确的知道Bug不大可能出现在Adaptee(已有组件)中,只需要检查扮演Adapter角色的类即可。这样可以大大缩短检错时间(这下知道为啥同样的ctrl+c大佬的代码就更加稳定的原因了吗)
发布升级版本
在实际开发中,版本的升级是必然会经历的一个阶段,通常用户会希望新升级的版本需要对旧版本有足够的兼容性。然而对于开发者来说,却更希望能够抛弃旧版本,因为这会给他们带来更多的工作量。适配器模式则可以帮助开发者们在开发过程中维护旧版本,使用适配器模式使新版本和旧版本兼容,可以让开发者们更少的关注旧版本的细节,使其更加关注新版本的开发。
桥接模式
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。该模式用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系。
桥接模式中我们要明白名字中的桥接含义,会有那么座桥去连接两边进行相通,等到后面我的解释,大家可能会发现桥不一定连接两方可能n个地方;桥可能不存在,即桥和桥的某一边统一起来了,所以整体看上去是三部分构成,可能实际只有两部分,因为有一部分承担了两种角色。
模式特点
分离抽象接口及其实现部分(在继承中是针对实现类来操作的!而桥接模式中属性的定义是直接在接口类就定义了而没有到实现类才设置属性,这就是面向抽象编程!)
可以取代多层继承方案,极大地减少了子类的个数
提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则
模式缺点
会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就要针对抽象层进行设计与编程。
正确识别出系统中两个独立变化的维度并不是一件容易的事情。
//接口类Color
public interface Color
{
public void bepaint(String penType, String name);
}
//具体类
public class Red implements Color
{
public void bepaint(String penType, String name){
System.out.println(penType + "绿色的" + name);
}
}
...后面几个类大同小异就不展示了。
//抽象类Pen
public abstract Pen
{
protected Color color;
public void setColor(Color color){
this.color = color;
}
public abstract void draw(String name);
}
//具体类
public class BigPen extends Pen
{
public void draw(String name){
String penType = "大号毛笔绘制";
this.color.bepaint(penType, name);
}
}
...后面几个类大同小异就不展示了。
public class Client
{
public static void main(String args[])
{
Color color;
Pen pen;
//利用xml反射读取指定对象
color = (Color)XMLUtil.getBean("color");//看不懂你就理解成color = new Red();
pen= (Pen)XMLUtil.getBean("pen");//看不懂你就理解成pen = new BigPen();
pen.setColor(color);//把桥的另一边设置进来
pen.draw("鲜花");
}
}
装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地向一个对象添加额外的功能,而不需要修改其原始类。通过将对象包装在装饰器类中,你可以在不改变现有对象结构的情况下,逐步地添加功能。
装饰模式角色
Component(抽象组件)
定义了具体组件和装饰器的共同接口,可以是抽象类或接口。
ConcreteComponent(具体组件)
实现了抽象组件定义的接口,是被装饰的原始对象。
Decorator(抽象装饰器)
包含一个指向具体组件的引用,并实现了抽象组件定义的接口。
ConcreteDecorator(具体装饰器)
通过装饰器对具体组件进行扩展或修改,添加额外的功能。
在这里插入图片描述
工作流程
首先
定义一个抽象组件(Component),它声明了具体组件和装饰器共同的接口方法。
其次
创建一个具体组件(ConcreteComponent),它实现了抽象组件的接口方法,是被装饰的原始对象。
然后
创建一个抽象装饰器(Decorator),它也实现了抽象组件的接口方法,并包含一个指向具体组件的成员变量(通常为抽象组件类型),用于持有被装饰的对象。
最后
创建具体装饰器(ConcreteDecorator),它继承自抽象装饰器,并在装饰器的基础上添加了额外的功能。具体装饰器中通常会重写抽象组件的接口方法,以在调用前后进行额外的处理,然后再调用被装饰对象的相应方法。
在这里插入图片描述
Java代码实现
// Step 1: 定义抽象组件
interface Component {
void operation();
}
// Step 2: 创建具体组件
class ConcreteComponent implements Component {
public void operation() {
System.out.println("执行具体组件的操作");
}
}
// Step 3: 创建抽象装饰器
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
public void operation() {
component.operation();
}
}
// Step 4: 创建具体装饰器
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
// 在调用具体组件操作前进行额外处理
System.out.println("在调用具体组件操作前进行额外处理");
// 调用具体组件的操作
super.operation();
// 在调用具体组件操作后进行额外处理
System.out.println("在调用具体组件操作后进行额外处理");
}
}
// 使用装饰模式
public class Main {
public static void main(String[] args) {
// 创建具体组件对象
Component component = new ConcreteComponent();
// 创建具体装饰器对象,并将具体组件对象传入
Component decorator = new ConcreteDecorator(component);
// 调用装饰后的操作
decorator.operation();
}
}
复制
代码分析
Component 是抽象组件接口,ConcreteComponent 是具体组件类,实现了抽象组件接口的方法。Decorator 是抽象装饰器类,实现了抽象组件接口,并持有一个抽象组件类型的成员变量。ConcreteDecorator 是具体装饰器类,继承自抽象装饰器类,并重写了操作方法,在调用前后添加了额外处理。
在主函数中,先创建具体组件对象ConcreteComponent,然后将其传入具体装饰器对象ConcreteDecorator 的构造函数中,用装饰器包装具体组件。最后调用装饰后的操作,会按照一定的顺序执行额外处理和具体组件操作。
优缺点分析
优点
符合开闭原则
可以在不修改现有代码的情况下,通过新增装饰器类来扩展对象的功能。
可以动态地添加/删除功能
可以根据需要动态地添加或删除对象的功能,组合不同的装饰器实现不同的行为组合。
遵循单一职责原则
具体的组件类只负责核心功能,具体的装饰器类只关注附加的功能,各个类职责明确,可维护性高。
装饰器类与具体组件类独立
装饰器类与具体组件类之间是松耦合的关系,可以独立变化,增加或删除装饰器不会影响其他组件的行为。
缺点
可能产生过多的具体装饰器类
如果系统中有很多功能需要扩展,可能会导致产生大量的具体装饰器类,增加系统的复杂性。
装饰器与组件类的接口不一致
在装饰器模式中,装饰器类和具体组件类的接口不一致,导致客户端需要区分调用。
组合模式
组合模式 : 将 对象 组合成 树形结构 , 表示 " 部分-整体 " 层次结构 ;
组合模式 使 客户端 对 单个对象 和 组合对象 保持一致的 方式处理 ;
如 : 文件系统 , 根目录下有若干文件和目录 , 在二级目录下还有目录和文件 , 这种情况下 , 适合使用组合模式 ;
组合模式适用场景 :
- 忽略差异 : 希望 客户端 可以 忽略 组合对象 与 单个对象 的差异 ;
- 处理树形结构 ;
组合模式优点 :
定义层次 : 清楚地 定义 分层次 的 复杂对象 , 表示 对象 的 全部 或 部分 层次 ;
忽略层次 : 让 客户端 忽略 层次之间的差异 , 方便对 整个层次结构 进行控制 ;
简化客户端代码 ;
符合开闭原则 ;
组合模式缺点 :
限制类型复杂 : 限制类型时 , 比较复杂 ;
如 : 某个目录中只能包含文本文件 , 使用组合模式时 , 不能依赖类型系统 , 施加约束 , 它们都来自于节点的抽象层 ; 在这种情况下 , 必须通过在运行时进行类型检查 , 这样就变得比较复杂 ;
使设计变得更加抽象 ;
组合模式和访问者模式 : 两个模式经常结合起来使用 , 使用 访问者模式 , 访问 组合模式 中的 递归结构 ,
组合模式包含以下主要角色。
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。组抽象构件还声明访问和管理子类的接口;
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件,定义了组合内元素的行为。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件,但可能不具有树叶构件的某些行为。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
3、组合模式的实现
完成 大学——学院——专业的展示
定义抽象构件(Component)角色:
java复制代码public abstract class Component {
private String name;
private String des;
public Component(String name, String des) {
this.name = name;
this.des = des;
}
//增加
protected void add(Component component){
//抛出不支持操作的异常
throw new UnsupportedOperationException();
}
//移除
protected void remove(Component component){
//抛出不支持操作的异常
throw new UnsupportedOperationException();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
//抽象的打印方法
protected abstract void print();
}
定义树枝构件(Composite)角色 / 中间构件1:University 大学
java复制代码public class University extends Component{
//List存放的是学院的信息
List<Component> components = new ArrayList<>();
public University(String name, String des) {
super(name, des);
}
@Override
protected void add(Component component) {
components.add(component);
}
@Override
protected void remove(Component component) {
components.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
//打印University包含的学院的名字
@Override
protected void print() {
System.out.println("===============名称:"+getName()+"描述:"+getDes()+"===============");
for (Component coms : components){
coms.print();
}
}
}
定义树枝构件(Composite)角色 / 中间构件2:College 学院
java复制代码public class College extends Component{
//List存放的是专业的信息
List<Component> components = new ArrayList<>();
public College(String name, String des) {
super(name, des);
}
//实际业务中,University和College重写的add方法和remove方法可能不相同
@Override
protected void add(Component component) {
components.add(component);
}
@Override
protected void remove(Component component) {
components.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
//打印College包含的专业的名字
@Override
protected void print() {
System.out.println("===============名称:"+getName()+"描述:"+getDes()+"===============");
for (Component coms : components){
coms.print();
}
}
}
定义树叶构件(Leaf)角色:Department 专业
java复制代码public class Department extends Component{
public Department(String name, String des) {
super(name, des);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println("名称:"+getName()+"描述:"+getDes());
}
}
编写测试类进行测试:
java复制代码public class Client {
public static void main(String[] args) {
//创建大学
Component university = new University("清华大学", "非常好的大学");
//创建学院
Component college1 = new College("信息工程学院", "信息工程学院好多专业");
Component college2 = new College("建设工程学院", "建设工程学院好多专业");
//将学院添加到学校中
university.add(college1);
university.add(college2);
//创建专业,并把专业添加到学院中
college1.add(new Department("计算机科学与技术","计算机科学与技术好专业"));
college1.add(new Department("网络工程","网络工程好专业"));
college2.add(new Department("土木","土木好专业"));
college2.add(new Department("测绘","测绘好专业"));
university.print();
}
}
外观模式
外观模式(Facade Pattern):外部与子系统的通信通过一个统一的外观对象进行,为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。
一个客户类需要和多个业务类交互,有时候这些需要交互的业务类会作为一个整体出现,这时引入一个新的外观类(Facade)来负责和多个业务类【子系统(Subsystem)】进行交互,而客户类只需与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度。没有外观类,客户类需要和多个子系统之间进行复杂的交互,系统耦合度大。
二、情景假设
现在考察一个电源总开关的例子,以便进一步说明外观模式。为了使用方便,一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。通过该电源总开关可以同时控制上述所有电器设备,使用外观模式设计该系统。
外观模式其实主要针对的就是客户端,对于客户端与子系统交互,把错综复杂的情况进行封装,只留有几个“小开口透风”即可,既可以进行统一的操作,又便于客户端操作。
三、情景分析
关于上面情景的类图(具体分析在下面)
首先设计子系统
//子系统Light
public class Light
{
private Srtring position;
public Light(String postion){
this.position = position;
}
public void on(){
System.out.println(this.positioin + "灯打开!");
}
public void off(){
System.out.println(this.positioin + "灯关闭!");
}
}
//子系统Fan
public class Fan
{
public void on(){
System.out.println(this.positioin + "风扇打开!");
}
public void off(){
System.out.println(this.positioin + "风扇关闭!");
}
}
//子系统AirCondition
public class AirCondition
{
public void on(){
System.out.println(this.positioin + "空调打开!");
}
public void off(){
System.out.println(this.positioin + "空调关闭!");
}
}
//子系统TV
public class TV
{
public void on(){
System.out.println(this.positioin + "电视打开!");
}
public void off(){
System.out.println(this.positioin + "电视关闭!");
}
}
**外观类统一管理**
//外观类GeneralSwitchFacade
public interface GeneralSwitchFacade
{
private Light lights[] = new Light[4];
private Fan fan;
private AirCondition ac;
private TV tv;
}
public GeneralSwitchFacade()
{
lights[0] = new Light("左前");
lights[1] = new Light("右前");
lights[2] = new Light("左后");
lights[3] = new Light("右后");
fan = new Fan();
ac = new AirCondition();
}
public void on() {
lights[0].on();
lights[1].on();
lights[2].on();
lights[3].on();
fan.on();
ac.on();
tv.on();
}
public void off() {
lights[0].off();
lights[1].off();
lights[2].off();
lights[3].off();
fan.off();
ac.off();
tv.off();
}
接下来是客户端的代码:
public class Client
{
public static void main(String args[])
{
GeneralSwitchFacade gsf = new GeneralSwitchFacade();
gsf.on();
gsf.off();
}
}
主要包含的角色
Facade: 外观角色
SubSystem:子系统角色
模式分析
(1) 模式特点
通过引入一个新的外观角色来降低原有系统的复杂度,同时降低客户类与子系统的耦合度(所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统),使得子系统更加易用
它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
一个子系统的修改对其他子系统没有任何影响,而且子系统的内部变化也不会影响到外观对象
(2) 模式缺点
不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则
五、使用情景
要为访问一系列复杂的子系统提供一个简单入口
客户端程序与多个子系统之间存在很大的依赖性
在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而是通过外观类建立联系,降低层之间的耦合度
享元模式
一、定义
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
一个软件系统在运行时所创建的相同或相似对象数量太多,将导致运行代价过高,带来系统资源浪费、性能下降等问题,这时候可以利用享元模式,构建享元池,在池子中存放共享的对象,需要的时候可以通过工厂获取,甚至于取出对象时设置各自不同的外部状态,这样对外就会有不同的表现。
二、情景假设
很多网络设备都是支持共享的,如交换机、集线器等,多台终端计算机可以连接同一台网络设备,并通过该网络设备进行数据转发,如图所示,现用享元模式模拟共享网络设备的设计原理。
三、情景分析
关于上面情景的类图(具体分析在下面)
首先定义我们的网络设备比如交换机、集线器,当然我们还要定义一个抽象设备类
//抽象构件类
public abstract class NetworkDevice
{
public String getType();
public void use();
}
具体已有的两个设备实现类
//Switch交换机
public class Switch extends NetworkDevice{
private String type;
public Switch(String type){
this.type = type;
}
public String getType(){
return this.type;
}
public void use(){
System.out.println("交换机连接, type:" + this.type);
}
}
//Hub集线器
public class Hub extends NetworkDevice{
private String type;
public Hub(String type){
this.type = type;
}
public String getType(){
return this.type;
}
public void use(){
System.out.println("交换机连接, type:" + this.type);
}
}
定义一个工厂类,专门用来存储享元类,以及提供使用享元类的接口,其实就是相当于一个容器,作为共享池供大家使用。
//享元工厂类DeviceFactory
public class DeviceFactory {
private ArrayList devices = new ArrayList();
private int totalTerminal = 0;
public DeviceFactory(){
NetworkDevice nd1 = new Switch("Cisco-WS-C2950-24");
devices.add(nd1);
NetworkDevice nd2 = new Hub("TP-LINK-HF8M");
devices.add(nd2);
}
public NetworkDevice getNetworkDevice(String type){
if(type.equalsIgnoreCase("cisco")){
totalTerminal++;
return (NetworkDevice)devices.get(0);
} else if(type.equalsIgnoreCase("tp")){
totalTerminal++;
return (NetworkDevice)devices.get(1);
}else{
return null;
}
}
public int getTotalDevice(){
return devices.size();
}
public int getTotalTerminal(){
return totalTerminal;
}
}
接下来是客户端的代码:
public class Client
{
public static void main(String args[])
{
NetworkDevice nd1,nd2,nd3,nd4,nd5;
DeviceFactory df = new DeviceFactory();
nd1 = df.getNetworkDevice("cisco");
nd1.use();
nd2 = df.getNetworkDevice("cisco");
nd2.use();
nd3 = df.getNetworkDevice("cisco");
nd3.use();
nd4 = df.getNetworkDevice("tp");
nd4.use();
nd5 = df.getNetworkDevice("tp");
nd5.use();
System.out.println("总设备:" + df.getTotalDevice());
System.out.println("总量控制:" + df.getTotalTerminal());
}
}
可以看到我在客户端中定义五个设备,但其实总共也只使用了2个对象,因为在享元池中只存储了两个设备,并开放对外使用。
(1) 模式特点
- 可以减少内存中对象的数量,使得相同或者相似的对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
- 外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
(2) 模式缺点
- 使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
使用情景
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,在需要多次重复使用享元对象时才值得使用享元模式
享元模式和其他模式的联用
- 享元模式的享元工厂类(就是享元池)中通常有一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
- 在一个系统中,通常只有唯一一个享元工厂,因此享元工厂类可以使用单例模式进行设计。
- 如果只有一个具体构件类没有抽象构建类,那么可以将抽象装饰类作为具体构建类子类。
- 享元模式可以结合组合模式形成复合享元模式,统一对享元对象设置外部状态。
代理模式
1.概述
由于某些原因需要给一些对象提供一个代理来控制对该对象的访问。此时,访问对象不是直接访问目标对象而是通过代理对象作为中介进行访问。(类似于租房找中介,中介就相当于代理对象)
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期生成,而动态代理代理类在Java运行时动态生成。动态代理又有JDK代理 和CGLib代理两种
2.结构
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。(规定代理类代理目标对象的规则)
- 真实主题(Real Subject)类:实现抽象主题中的具体业务,是代理对象所代表的真实对象,即目标对象
- 代理(Proxy)类:提供与真实主题相同的接口,其内部含有对真实主题的引用,可以访问、控制、扩展真实主题的功能
3.静态代理
火车站售票例子
卖火车票的接口(抽象主题类)
public interface SellTickets {
//卖火车票的接口
/**
* 卖票
*/
void sell();
}
火车站类(真实主题类)
public class TrainStation implements SellTickets{
//火车站类
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
火车票代售点(代理类)
//代理类
public class ProxyPoint implements SellTickets{
/**
声明火车站类对象
*/
private TrainStation trainStation = new TrainStation();
@Override
public void sell() {
System.out.println("代售点收取服务费");
trainStation.sell();
}
}
客户类(访问类)
public class Client {
public static void main(String[] args) {
//创建代理对象
ProxyPoint proxyPoint = new ProxyPoint();
//买票
proxyPoint.sell();
}
}
运行结果:
4.动态代理
4.1JDK动态代理
Java中提供一个动态代理类Proxy,Proxy不是上述的代理对象的类,而是创建代理对象类的类,Proxy类中提供的静态方法newProxyInstance可以获取代理对象类(这是由于动态代理代理类在Java程序运行时动态生成这一特性决定的)
JDK动态代理实现
卖火车票的接口(抽象主题类)
public interface SellTickets {
//卖火车票的接口
/**
* 卖票
*/
void sell();
}
火车站类(真实主题类)
public class TrainStation implements SellTickets{
//火车站类
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
代理对象工厂类
/**
*
获取代理对象的工厂类
代理类也实现了对应的接口
*/
public class ProxyFactory {
//声明目标对象
private TrainStation trainStation = new TrainStation();
public SellTickets getProxyObject(){
//返回代理对象
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke方法执行了");
return null;
}
}
);
/**
* newProxyInstance方法中的三个参数
* ClassLoader loader : 类加载器,用于加载代理类,可以通过目标对象获得类加载器
* Class<?>[] interfaces : 代理类实现的接口的字节码对象
*InvocationHandler h :代理对象的调用处理程序
*/
return proxyObject;
}
}
客户类(访问类)
public class Client {
public static void main(String[] args) {
//获取代理对象
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//使用代理工厂对象获取代理对象
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
运行结果:
说明我们在使用代理工厂获取代理对象然后访问目标对象,其实是调用了invoke()方法
在明白以上所述的基础上,我们再来看invoke()方法
/**
* Object proxy :代理对象,和proxyObject是同一个对象,在invoke中基本不用
* Method method : 对接口中的方法 (sell()方法) 进行封装的method对象
* Object[] args : 调用方法 (sell()方法) 的实际参数, 本例中sell()方法无参数
*
*返回值 就是sell()方法的返回值 ,由于sell()方法没有返回值 因此就是返回null
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象对目标对象进行增强
System.out.println("代售点收取服务费....");
/**
*执行目标对象的方法 method就表示sell()方法
* 通过反射调用method对象中的invoke方法
* 参数表示 : trainStation要调用的sell方法所属类的实例对象 ,args发放的参数
*/
Object obj = method.invoke(trainStation, args);
return obj;
}
代理对象工厂类(完整代码)
/**
*
获取代理对象的工厂类
代理类也实现了对应的接口
*/
public class ProxyFactory {
//声明目标对象
private TrainStation trainStation = new TrainStation();
public SellTickets getProxyObject(){
//返回代理对象
/**
* newProxyInstance方法中的三个参数
* ClassLoader loader : 类加载器,用于加载代理类,可以通过目标对象获得类加载器
* Class<?>[] interfaces : 代理类实现的接口的字节码对象
*InvocationHandler h :代理对象的调用处理程序
*/
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
new InvocationHandler() {
/**
* Object proxy :代理对象,和proxyObject是同一个对象,在invoke中基本不用
* Method method : 对接口中的方法 (sell()方法) 进行封装的method对象
* Object[] args : 调用方法 (sell()方法) 的实际参数, 本例中sell()方法无参数
*
*返回值 就是sell()方法的返回值 ,由于sell()方法没有返回值 因此就是返回null
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理对象对目标对象进行增强
System.out.println("代售点收取服务费....");
/**
*执行目标对象的方法 method就表示sell()方法
* 通过反射调用method对象中的invoke方法
* 参数表示 : trainStation要调用的sell方法所属类的实例对象 ,args发放的参数
*/
Object obj = method.invoke(trainStation, args);
return obj;
}
}
);
return proxyObject;
}
}
客户端运行结果:
JDK动态代理底层实现
JDK动态代理代理类是在程序运行过程中动态的在内存中生成的类,我们通过阿里巴巴开源的Java诊断工具 (Arthas【阿尔萨斯】) 查看代理类的结构
public class Client {
public static void main(String[] args) {
//获取代理对象
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//使用代理工厂对象获取代理对象
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
//获取代理类名称
System.out.println(proxyObject.getClass());
//让程序一直执行,在内存中获取动态生成的代理类
while (true){
}
}
}
执行结果:
在arthas-boot.jar 包所在目录下启动cmd
使用第二种解决方案,直接复制粘贴执行
jad+类名 执行
获取 $Proxy0类
public final class $Proxy0
extends Proxy
implements SellTickets {
private static Method m0;
private static Method m1;
private static Method m2;
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.xue.jdk_proxy.SellTickets").getMethod("sell", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
- 代理类($Proxy0)实现了SellTickets ,说明了真实类和代理类实现同样的接口
- 代理类($Proxy0)将我们提供的匿名内部类对象传递给了父类
仅保留关键代码进行分析
public final class $Proxy0 extends Proxy
implements SellTickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
//将invocationHandler 赋值给父类Proxy中的属性 protected InvocationHandler h
super(invocationHandler);
}
static {
// 通过反射获取接口类中的sell方法,并赋值给 (method) m3
m3 = Class.forName("com.xue.jdk_proxy.SellTickets").getMethod("sell", new Class[0]);
}
}
public final void sell() {
//通过调用父类Proxy中的h 调用我们自己实现的匿名内部类InvocationHandler(子实现类)然后调用子实现类中的invoke()方法
this.h.invoke(this, m3, null);
}
}
整个执行流程:
1.在测试类中通过代理对象调用sell()方法
2.根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3.代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口中的子实现对象的invoke()方法
4.invoke()方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
注意:JDK动态代理要求必须定义接口,对接口进行代理
4.2CGLib动态代理
CGLib是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK动态代理提供了很好的补充
CGLib是第三方提供的包,需要导入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
CGlib动态代理实现
火车站类
public class TrainStation implements SellTickets {
//火车站类
@Override
public void sell() {
System.out.println("买票成功.....");
}
}
代理对象工厂类
public class ProxyFactory implements MethodInterceptor {
public TrainStation getProxyObject(){
//创建Enhancer对象,类似与JDK代理中的proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数 MethodInterceptor子实现类也就是 本类this
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法执行了...");
return null;
}
}
访问类
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();
//调用代理对象中的sell方法
proxyObject.sell();
}
}
运行结果:
说明代理对象调用了intercept方法
完善代理工厂类
public class ProxyFactory implements MethodInterceptor {
private TrainStation trainStation = new TrainStation();
public TrainStation getProxyObject(){
//创建Enhancer对象,类似与JDK代理中的proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数 MethodInterceptor子实现类也就是 本类this
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售点收取服务费...");
//要调用目标对象的方法
Object obj = method.invoke(trainStation,objects);
return null;
}
}
执行结果:
三种代理的对比
在JDK1.8及之后,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIE代理。
动态代理和静态代理:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我可以进集中处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
5.代理模式的优缺点及使用场景
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:
- 增加了系统的复杂度
使用场景:
- 远程(Remote)代理本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。(RPC思想,例如:dubbo)
- 防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。(VPN)
- 保护(Protect or Access)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
行为型模式
模板方法模式
一、模板方法模式基本介绍:
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
简单说,模板方法模式,定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤,这种类型的设计模式属于行为型模式。
二、模板方法模式
1.模板方法模式原理类图
对原理类图的说明:
- AbstractClass 抽象类, 类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现 其它的抽象方法 operation2,3,4
- ConcreteClass 实现抽象方法,假设是operation2,3,4, 以完成算法中特定子类的具体业务步骤
三、具体需求
1.豆浆制作
编写制作豆浆的程序,说明如下:
- 制作豆浆的流程:选材—>添加配料—>浸泡—>放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式
2. 模板方法模式方案
思路分析 - 类图
具体实现
// 抽象类,表示豆浆 SoyaMilk.java
public abstract class SoyaMilk {
// 模板方法:可以做成final,不让子类去覆盖
final void make() {
select();
addCondiment();
soak();
beat();
}
//选材料
void select() { System.out.println("第一步:选择新鲜的豆子"); }
//添加不同的配料:抽象方法,由子类具体实现
abstract void addCondiment();
//浸泡
void soak() { System.out.println("第三步:豆子和配料开始浸泡3H"); }
//榨汁
void beat() { System.out.println("第四步:豆子和配料放入豆浆机榨汁"); }
}
// RedBeanSoyaMilk.java
public class ReadBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiment() {
System.out.println("第二步:加入上好的红豆");
}
}
// PeanutSoyMilk.java
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiment() {
System.out.println("第二步:加入上好的花生");
}
}
// Client.java
public class Client {
public static void main(String[] args) {
System.out.println("=======制作红豆豆浆=======");
SoyaMilk redBeanSoyaMilk = new ReadBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("=======制作花生豆浆=======");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}
3.模板方法模式的钩子方法
在模板方法模式的父类中,可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造。代码实现如下:
// RedBeanSoyaMilk.java/PeanutSoyaMilk.java同上,略
//抽象类,表示豆浆,SoyaMilk
public abstract class SoyaMilk {
//模板方法:可以做成final,不让子类去覆盖
final void make() {
select();
if(customerWantCondiment()) {
addCondiment();
}
soak();
beat();
}
//1.选材料
void select() { System.out.println("第一步:选择新鲜的豆子"); }
//2.添加不同的配料:抽象方法,由子类具体实现
abstract void addCondiment();
//3.浸泡
void soak() { System.out.println("第三步:豆子和配料开始浸泡3H"); }
//4.榨汁
void beat() { System.out.println("第四步:豆子和配料放入豆浆机榨汁"); }
//钩子方法:决定是否需要添加配料
boolean customerWantCondiment() {
return true;//默认情况下是要加配料的
}
}
// PureSoyaMilk.java
public class PureSoyaMilk extends SoyaMilk {
@Override
void addCondiment() {
// 添加配料的方法 空实现 即可
}
@Override
boolean customerWantCondiment() {
return false;
}
}
// Client.java
public class Client {
public static void main(String[] args) {
System.out.println("=制作纯豆浆=");
SoyaMilk pureSoyMilk = new PureSoyaMilk();
pureSoyMilk.make();
}
}
四、注意事项和细节
- 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 好处:
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用;
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
注意:一般模板方法都加上 final 关键字, 防止子类重写模板方法
- 模板方法模式使用场景:
- 当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
- 统计某一段代码的执行时间也可以用模板方法模式:在前面打印出代码执行前的时间,后面再打印出代码执行后的时间,中间部分就是不同的执行代码
命令模式
一、什么是命令模式
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请 求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
在该类图中,我们看到三个角色:
- Receiver接受者角色:该角色就是干活的角色,命令传递到这里是应该被执行的
- Command命令角色:需要执行的所有命令都在这里声明
- Invoker调用者角色:接收到命令,并执行命令
二、命令模式的使用场景
使用时机:当需要先将一个函数登记上,然后再以后调用此函数时,就需要使用命令模式,其实这就是回调函数。
有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
例子:拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。 命令模式把客人订餐的请求封装成 command 对象,也就是订餐中的订单对象**。**这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系
三、命令模式的优缺点
优点:
- 类间解耦:调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command 抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
- 可扩展性: 如果要 扩展新命令 , 直接 定义 新的命令对象 即可 ; 如果要 执行一组命令 , 发送一组命令 给接收者 即可 ;
- 命令模式结合其他模式会更优秀:命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少 Command子类的膨胀问题。
缺点:
扩展命令 会 导致 类的数量增加 , 增加了 系统实现的复杂程度 ;
需要针对每个命令 , 都要开发一个与之对应的命令类 ;
四、命令模式 与 备忘录模式
命令模式 与 备忘录模式 : 可以通过 备忘录模式 保存 命令模式 的 命令 历史记录 , 可以获取 上一个命令 , 之前的某几个命令 等 ,
五、命令模式 代码示例
命令模式 的 核心 就是 将 命令 抽象成一个对象 ;
业务场景 :
游戏的 发布 和 关闭 , 使用命令模式实现 , 分别针对 发布 , 关闭 , 各自定义一个命令类 ;
1、命令接口
package command;
/**
* 命令接口
* 所有的命令都要实现该接口
*/
public interface Command {
/**
* 执行命令方法
*/
void execute();
}
2、发布命令类
package command;
/**
* 开放命令
* 实现 Command 接口
* 该类代表了一种命令
*/
public class OpenCommand implements Command{
private Game game;
public OpenCommand(Game game) {
this.game = game;
}
@Override
public void execute() {
this.game.open();
}
}
3、关闭命令类
package command;
/**
* 关闭命令
*/
public class CloseCommand implements Command {
private Game game;
public CloseCommand(Game game) {
this.game = game;
}
@Override
public void execute() {
this.game.close();
}
}
4、游戏类
package command;
/**
* 该类与命令执行的具体逻辑相关
*/
public class Game {
private String name;
public Game(String name) {
this.name = name;
}
public void open() {
System.out.println(this.name + " 开放");
}
public void close() {
System.out.println(this.name + " 关闭");
}
}
5、命令执行者类
package command;
import java.util.ArrayList;
/**
* 命令接收者
* 执行命令
*/
public class Manager {
/**
* 存放命令
*/
private ArrayList<Command> commands = new ArrayList<>();
/**
* 添加命令
* @param command
*/
public void addCommand(Command command) {
commands.add(command);
}
/**
* 执行命令
*/
public void executeCommand() {
for (Command command : commands) {
// 逐个遍历执行命令
command.execute();
}
// 命令执行完毕后 , 清空集合
commands.clear();
}
}
6、测试类
package command;
public class Main {
public static void main(String[] args) {
Game game = new Game("Game 01");
OpenCommand openCommand = new OpenCommand(game);
CloseCommand closeCommand = new CloseCommand(game);
// 发送命令
Manager manager = new Manager();
manager.addCommand(openCommand);
manager.addCommand(closeCommand);
// 执行命令
manager.executeCommand();
}
}
执行结果 :
六、总结
命令模式的意图是将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
命令模式主要解决的问题是在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合,这是命令模式的使用场景。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式
命令模式的实现过程通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。
迭代器模式
一、什么是迭代器模式
迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。
二、角色组成
- 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法,包括hashNext()和next()方法等,用于遍历聚合对象中的元素。
- 具体迭代器(Concrete Iterator):它是实现迭代器接口的具体实现类,负责具体的遍历逻辑。它保存了当前遍历的位置信息,并可以根据需要向前或向后遍历集合元素。
- 抽象聚合器(Aggregate): 一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
- 具体聚合器(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
三、 优缺点
优点:
- 简化了集合类的接口,使用者可以更加简单地遍历集合对象,而不需要了解集合内部结构和实现细节。
- 将集合和遍历操作解耦,使得我们可以更灵活地使用不同的迭代器来遍历同一个集合,根据需求选择不同的遍历方式。
- 满足开闭原则,如果需要增加新的遍历方式,只需实现一个新的具体迭代器即可,不需要修改原先聚合对象的代码。
缺点:
- 具体迭代器实现的算法对外不可见,因此不利于调试和维护。
- 对于某些小型、简单的集合对象来说,使用迭代器模式可能会显得过于复杂,增加了代码的复杂性。
四、应用场景
4.1 生活场景
- 遍历班级名单:假设你是一名班主任,你需要遍历班级名单来点名。班级名单可以看作是一个集合,每个学生名字可以看作是集合中的一个元素。使用迭代器模式,你可以通过迭代器对象逐个访问学生的名字,而不需要了解班级名单的具体实现细节。
- 遍历音乐播放列表:当我们在手机或电脑上播放音乐时,通常会创建一个播放列表。播放列表可以被视为一个集合,每首歌曲可以被视为集合中的一个元素。使用迭代器模式,我们可以通过迭代器对象逐个访问播放列表中的歌曲,进行播放、暂停或切歌等操作。
4.2 java场景
- 集合框架中的迭代器:在Java中,集合包括List、Set、Map等等,每个集合类中都提供了一个获取迭代器的方法,例如List提供的iterator()方法、Set提供的iterator()方法等等。通过获取对应的迭代器对象,可以对集合中的元素进行遍历和访问。
- JDBC中的ResultSet对象:在Java中,如果需要对数据库中的数据进行遍历和访问,可以使用JDBC操作数据库。JDBC中,查询结果集使用ResultSet对象来表示,通过使用ResultSet的next()方法,就可以像使用迭代器一样遍历和访问查询结果中的数据。
- 文件读取:在Java中,我们可以使用BufferedReader类来读取文本文件。BufferedReader类提供了一个方法readLine()来逐行读取文件内容。实际上,BufferedReader在内部使用了迭代器模式来逐行读取文本文件的内容。
五、代码实现
下面以班级名单为例,解释一下迭代器模式。
- 抽象迭代器:StudentIterator
- 具体迭代器:StudentListIterator
- 抽象聚合器:StudentAggregate
- 具体聚合器:ClassList
5.0 UML类图
5.1 Student——学生实体类
首先我们定义一个学生类,用来表示学生信息。
/**
* @author Created by njy on 2023/6/25
* 学生实体类
*/
@Data
public class Student {
private String name;
private Integer age;
public Student(String name,Integer age){
this.age=age;
this.name=name;
}
}
5.2 StudentIterator——抽象迭代器(Iterator)
接下来创建一个抽象迭代器(学生迭代器)并继承Iterator接口(java.util包下的Iterator)
import java.util.Iterator;
/**
* @author Created by njy on 2023/6/25
* 抽象迭代器(Iterator):学生迭代器
* 实现Iterator接口
* 负责定义访问和遍历元素的接口,例如提供hasNext()和next()方法。
*/
public interface StudentIterator extends Iterator<Student> {
}
5.3 StudentListIterator——具体迭代器(Concrete iterator)
在这个具体迭代器中,实现抽象迭代器,重写hashNext()和next()方法。
/**
* @author Created by njy on 2023/6/25
* 具体迭代器(Concrete iterator):
* 实现抽象迭代器定义的接口,负责实现对元素的访问和遍历。
*/
public class StudentListIterator implements StudentIterator{
private List<Student> students;
private int index;
public StudentListIterator(List<Student> students) {
this.students = students;
this.index = 0;
}
//检查是否还有下一个元素
@Override
public boolean hasNext() {
return (index < students.size());
}
//返回下一个元素
@Override
public Student next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Student student = students.get(index);
index++;
return student;
}
}
5.4 StudentAggregate——抽象聚合器(Aggregate)
定义一个抽象聚合器,并定义一个iterator()方法,用于创建具体的迭代器对象。
/**
* @author Created by njy on 2023/6/25
* 抽象聚合器(Aggregate):学生聚合器
* 提供创建迭代器的接口,例如可以定义一个iterator()方法。
*/
public interface StudentAggregate {
//用于创建具体的迭代器对象
StudentIterator iterator();
void add(Student student);
}
5.5 ClassList——具体聚合器(Concrete Aggregate)
实现抽象聚合器定义的接口,负责创建具体的迭代器对象。
/**
* @author Created by njy on 2023/6/25
* 具体聚合器(ConcreteAggregate):班级列表
* 实现抽象聚合器定义的接口,负责创建具体的迭代器对象,并返回该对象。
*/
public class ClassList implements StudentAggregate{
private List<Student> students = new ArrayList<>();
//创建迭代器对象
@Override
public StudentIterator iterator() {
return new StudentListIterator(students);
}
//向班级名单中添加学生信息
@Override
public void add(Student student) {
students.add(student);
}
}
5.6 testIterator
/**
* @author Created by njy on 2023/6/25
* 迭代器模式测试类
*/
@SpringBootTest
public class TestIterator {
@Test
void testIterator(){
ClassList classList = new ClassList();
// 添加学生信息
classList.add(new Student("张三", 18));
classList.add(new Student("李四", 19));
classList.add(new Student("王五", 20));
// 获取迭代器,遍历学生信息
StudentIterator iterator = classList.iterator();
while(iterator.hasNext()) {
Student student = iterator.next();
System.out.println("学生姓名:" + student.getName() + ",学生年龄:" + student.getAge());
}
}
}
六、总结
- 迭代器模式提供了一种统一的方式来遍历集合对象中的元素。
- 它将遍历操作封装到一个独立的迭代器对象中,使得我们可以按照特定的方式访问集合中的元素。
- 迭代器模式将集合对象和遍历操作分离开来,提高了代码的灵活性和可维护性。
- 使用迭代器模式可以让我们用相同的方式遍历不同类型的集合对象,而不需要了解集合的内部结构。
观察者模式
1.观察者模式模式简介
定义
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式结构图
在观察者模式中有如下角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
2.观察者模式简单实现
观察者模式这种发布-订阅的形式我们可以拿微信公众号来举例,假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。好了我们来看看用代码如何实现:
抽象观察者(Observer)
里面定义了一个更新的方法:
public interface Observer {
public void update(String message);
}123
具体观察者(ConcrereObserver)
微信用户是观察者,里面实现了更新的方法:
public class WeixinUser implements Observer {
// 微信用户名
private String name;
public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
抽象被观察者(Subject)
抽象主题,提供了attach、detach、notify三个方法:
public interface Subject {
/**
* 增加订阅者
* @param observer
*/
public void attach(Observer observer);
/**
* 删除订阅者
* @param observer
*/
public void detach(Observer observer);
/**
* 通知订阅者更新消息
*/
public void notify(String message);
}
具体被观察者(ConcreteSubject)
微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法:
public class SubscriptionSubject implements Subject {
//储存订阅公众号的微信用户
private List<Observer> weixinUserlist = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
weixinUserlist.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserlist.remove(observer);
}
@Override
public void notify(String message) {
for (Observer observer : weixinUserlist) {
observer.update(message);
}
}
客户端调用
public class Client {
public static void main(String[] args) {
SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
//创建微信用户
WeixinUser user1=new WeixinUser("杨影枫");
WeixinUser user2=new WeixinUser("月眉儿");
WeixinUser user3=new WeixinUser("紫轩");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("刘望舒的专栏更新了");
}
}
结果
杨影枫-刘望舒的专栏更新了
月眉儿-刘望舒的专栏更新了
紫轩-刘望舒的专栏更新了
3.使用观察者模式的场景和优缺点
使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
中介者模式
一、定义
中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
其实中介者更像是一个群一个班级。对于一个多对多的复杂关系中,为了避免各对象的直接调关联,就通过增加一个中介者,去帮我们负责处理及交接。如果直接关联,对于增加一个关系,或者关系中的一个对象改变了,其他与其有关联的也会随之改动,所以通过在两者之间添加一层,改动则各自改动,但是交互还是通过中介者。
中介者的作用:
中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。该中转作用属于中介者在结构上的支持。
协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。
二、情景假设
某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息,钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息。该聊天室可以对不雅字符进行过滤,如“日”等字符;还可以对发送的图片大小进行控制。用中介者模式设计该虚拟聊天室。
三、情景分析
关于上面情景的类图(具体分析在下面)
如果说我们直接采用单纯的交互关系: (1) 系统结构复杂且耦合度高:每一个会员都与多个其他会员之间产生相互关联和调用,若一个会员对象发生变化,需要跟踪与之有关联的其他所有会员并进行处理,系统组件之间呈现一种较为复杂的网状结构,会员之间的耦合度高。 (2) 组件的可重用性差:由于每一个会员和其他会员之间都具有很强的关联,若没有其他组件的支持,一个组件很难被另一个系统或模块重用,这些组件表现出来更像一个不可分割的整体,而在实际使用时,我们往往需要每一个会员都能够单独重用,而不是重用一个由多个会员组成的复杂结构。 (3) 系统的可扩展性差:如果在上述系统中增加一个新的会员类,则必须修改与之交互的其他会员类的源代码,将导致多个类的源代码需要修改,这违反了“开闭原则”,可扩展性和灵活性欠佳。
所以便采用中介者模式解决上面的问题,
首先定义抽象中介者类AbstractChatroom(抽象聊天室类)
public abstract class AbstractChatroom
{
public abstract void register(Member member);//添加用户进群统一的管理
public abstract void sendText(String, from, String to, String message);
public abstract void sendImage(String from, String to, String image);
}
抽象同事类Member(抽象会员类)
public abstract class Member{
protected AbstractChatroom chatroom;
protected String name;
public Member(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public AbstractChatroom getChatroom(){
return chatroom;
}
public void setChatroom(AbstractChatroom chatroom){
this.chatroom = chatroom;
}
public abstract void sendText(String to, String message);
public abstract void sendImage(String to, String image);
public void receiveText(String from, String message){
System.out.println(from + "发送文本给" + this.name + ",内容为:" + message);
}
public void receiveImage(String from, String image){
System.out.println(from + "发送图片给" + this.name + ",内容为:" + image);
}
}
具体中介者类ChatGroup(具体聊天室类)
public class ChatGroup extends AstractChatroom
{
private Hashtable members = new Hashtable();
public void register(Member member){//加入成员到群中
if(!members.contains(member)){
members.put(member.getName(), member);
member.setChatroom(this);//每个成员也保存这个群聊
}
}
public abstract void sendText(String, from, String to, String message){
Member member = (Member)members.get(to);
String newMessage = message;
newMessage = message.replaceAll("日", "*");
member.receiveText(from, newMessage);
}
public abstract void sendImage(String from, String to, String image){
Member member = (Member)members.get(to);
if(image.length>5){
System.out.println( "图片太大,发送失败");
}
member.receiveImage(from, image);
}
}
具体同事类CommonMember(普通会员类)
public class CommonMember extends Member{
public CommonMember(String name){
super(name);
}
public void sendText(String to, String message){
System.out.println("普通会员发送消息:");
chatroom.sendText(name, to ,message);
}
public void sendImage(String to, String image){
System.out.println("普通会员不能发送图片");
}
}
具体同事类 DiamondMember(砖石会员类)
public class DiamondMember extends Member{
public DiamondMember(String name){
super(name);
}
public void sendText(String to, String message){
System.out.println("砖石会员发送消息:");
chatroom.sendText(name, to ,message);
}
public void sendImage(String to, String image){
System.out.println("砖石会员发送图片:");
chatroom.sendText(name, to ,image);
}
}
接下来是客户端的代码:
public class Client
{
public static void main(String[] args) {
AbstractChatroom happyChat = new ChatGroup();
Member m1,m2,m3,m4,m5;
m1 = new DiamondMember("张三");
m2 = new DiamondMember("李四");
m3 = new CommonMember("王五");
m4 = new CommonMember("小芳");
m5 = new CommonMember("小红");
happyChat.register(m1);
happyChat.register(m2);
happyChat.register(m3);
happyChat.register(m4);
happyChat.register(m5);
m1.sendText("李四", "李四,你好");
m2.sendText("张三", "张三,你好");
m1.sendText("李四", "今天天气不错,有日");
m2.sendImage("张三", "一个很大很大的太阳");
m2.sendImage("张三", "太阳");
m3.sendText("小芳", "还有问题吗?");
m3.sendText("小红", "还有问题吗?");
m4.sendText("王五", "没有了,谢谢");
m5.sendText("王五", "我也没有了");
m5.sendImage("王五", "谢谢");
}
}
以上就是中介者模式的具体实现。其实就是将各成员与其他成员联系拖到了中介中去实现,其中中介者还可以对内容进行分析处理。
四、 模式分析
模式类图:
- Mediator: 抽象中介者
- ConcreteMediator: 具体中介者
- Colleague: 抽象同事类
- ConcreteColleague: 具体同事类
(1) 模式特点
- 简化了对象之间的交互。
- 将各同事解耦。
- 减少子类生成。
- 可以简化各同事类的设计和实现。
(2) 模式缺点
- 在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
五、使用情景
- 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的中介者类
- 我们所常知道的MVC模式中,controller就是我们所谓的中介者。
六、拓展与延申
- 中介者模式与迪米特法则
在中介者模式中,通过创造出一个中介者对象,将系统中有关的对象所引用的其他对象数目减少到最少,使得一个对象与其同事之间的相互作用被这个对象与中介者对象之间的相互作用所取代。因此,中介者模式就是迪米特法则的一个典型应用。 - 外观模式和中介者模式有什么区别呢?
这里留个作业叭,大家可以对比学习一哈
备忘录模式
什么是备忘录模式
备忘录模式(Memento Pattern)又称之为快照模式(Snapshop Pattern)或者令牌模式(Token Pattern),是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样我们就可以在需要的时候将该对象恢复到原先保存的状态了,备忘录模式属于行为型模式。
备忘录模式可以为我们提供一种“后悔药”的机制,它通过存储系统中各个历史状态的快照,使得我们可以在任一时刻将系统回滚到历史状态。
好了,装逼时刻又到了:Talk is cheap,Show you the code,先看一个非常简单的例子。
备忘录模式示例
下面我们就以常见的富文本编辑器来编辑文章的功能为例来写一个简单的示例来看看备忘录模式是如何实现的。
1、首先建立一个类来保存最新文章信息:
package com.zwx.design.pattern.memento;
import java.util.Date;
public class ArticleText {
private String title;
private String content;
private Date createTime;
public ArticleText(String title, String content, Date createTime) {
this.title = title;
this.content = content;
this.createTime = createTime;
}
public ArticleMemento saveToMemento(){
ArticleMemento articleMemento = new
ArticleMemento(this.title,this.content,this.createTime);
return articleMemento;
}
public void getArticleFromMemento(ArticleMemento articleMemento){
this.title = articleMemento.getTitle();
this.content = articleMemento.getContent();
this.createTime = articleMemento.getCreateTime();
}
}
注意:这里面除了getter/setter方法之外还有一个方法用来备份,一个方法用来还原。
2、建立一个类用来保存历史数据,这个类的信息必须要和原始类一样,否则无法完全备份:
package com.zwx.design.pattern.memento;
import java.util.Date;
public class ArticleMemento {
private String title;
private String content;
private Date createTime;
public ArticleMemento(String title, String content, Date createTime) {
this.title = title;
this.content = content;
this.createTime = createTime;
}
}
3、然后还需要一个类来管理历史快照信息:
package com.zwx.design.pattern.memento;
import java.util.ArrayList;
import java.util.List;
public class ArticleCaretaker {
private final List<ArticleMemento> list = new ArrayList<>();
public ArticleMemento getArticle(int index){
return list.get(index);
}
public void setArticle(ArticleMemento articleMemento){
list.add(articleMemento);
}
}
4、最后来一个测试类测试一下:
package com.zwx.design.pattern.memento;
import java.util.Date;
public class TestMemento {
public static void main(String[] args) {
ArticleCaretaker articleCaretaker = new ArticleCaretaker();
ArticleText articleText = new ArticleText("标题1","内容1",new Date());
ArticleMemento articleMemento = articleText.saveToMemento();
articleCaretaker.setArticle(articleMemento);//备忘1次
articleText = new ArticleText("标题2","内容2",new Date());
System.out.println(String.format("修改后的标题为【%s】,内容为【%s】",articleText.getTitle(),articleText.getContent()));
articleText.getArticleFromMemento(articleCaretaker.getArticle(0));
System.out.println(String.format("还原后的标题为【%s】,内容为【%s】",articleText.getTitle(),articleText.getContent()));
}
}
输出结果如下:
修改后的标题为【标题2】,内容为【内容2】
还原后的标题为【标题1】,内容为【内容1】
这就是一个备忘录模式的示例,这个设计模式应该来说还是非常简单的。
备忘录模式角色
从上面示例中,我们可以得出迭代器模式主要有3个角色:
- 发起人角色(Originator):负责创建一个备忘录,记录自身需要保存的状态,而且需要具备状态的回滚功能。
- 备忘录角色(Memento):用于存储Originator角色的内部状态,且可以防止Originator以外的对象进行访问。
- 备忘录管理员角色(Caretaker):负责存储,管理备忘录功能。且其本身应该无法对备忘录的内容进行访问。
备忘录模式应用场景
备忘录模式作为一个程序员应该没有没使用过的,像比如svn,git等都是提供了历史版本的管理功能,这些就是备忘录模式的体现,像还有富文本的编辑之类的都可以使用备忘录模式来进行实现,还有服务器的快照功能也可以是我们回退到历史状态。备忘录模式一般用于以下场景:
- 1、需要保存历史快照的场景
- 2、希望在对象之外保存状态,且除了自己其他对象无法访问状态的具体保存内容
备忘录模式优缺点
优点:
- 1、简化了发起人的的职责,将状态的存储和获取进行了隔离,而且客户端无需关心状态的保存细节。
缺点: - 1、消耗资源,如果每个快照的内容都非常大,会消耗大量内存。
总结
备忘录模式是一个简单的非常容易让人彻底忽略的设计模式。本文主要介绍了备忘录模式的基本使用,并通过一个简单的示例来帮助更好的理解备忘录模式。
解释器模式
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。
定义与特点
解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
解释器模式是一种类行为型模式,其主要优点如下。
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
解释器模式的主要缺点如下。
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
结构与实现
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
1) 文法
文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。
〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉7大学生I筱霞I英语
〈动词〉::=是|学习
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。
2) 句子
句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。
3) 语法树
语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。图 1 所示是“我是大学生”的语法树。
有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
结构
解释器模式包含以下主要角色。
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
解释器模式的结构图如图 2 所示。
实现
解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树,其代码结构如下:
package com.design.pattern.behaviorPattern.interpreterPattern;
public class InterpreterPattern {
}
//抽象表达式
interface AbstractExpression{
public Object interpret(String info);//解释方法
}
//终结符表达式类
class TerminalExpression implements AbstractExpression
{
@Override
public Object interpret(String info) {
//对终结符表达式的处理
return null;
}
}
//非终结符表达式类
class NonterminalExpression implements AbstractExpression{
private AbstractExpression expression1;
private AbstractExpression expression2;
@Override
public Object interpret(String info) {
//非对终结符表达式的处理
return null;
}
}
//环境类
class Context{
private AbstractExpression expression;
public Context(){
//数据初始化
}
public void operation(String info){
//调用相关表达式类的解释方法
}
}
应用实例
【例1】用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。
说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。
<expression> ::= <city>的<person>
<city> ::= 韶关|广州
<person> ::= 老人|妇女|儿童
然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。
- 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。
- 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(Stringinfo),用来判断被分析的字符串是否是集合中的终结符。
- 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。
- 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如图 3 所示。
package com.design.pattern.behaviorPattern.interpreterPattern;
import java.util.HashSet;
import java.util.Set;
public class InterpreterPatternDemo {
public static void main(String[] args) {
Content bus = new Content();
bus.freeRide("韶关的老人");
bus.freeRide("韶关的年轻人");
bus.freeRide("广州的妇女");
bus.freeRide("广州的儿童");
bus.freeRide("山东的儿童");
}
}
//抽象表达式类
interface IExpression
{
public boolean interpret(String info);
}
//终结符表达式类
class TerminalExp implements IExpression{
private Set<String> set = new HashSet<>();
public TerminalExp(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 IExpression{
private IExpression city = null;
private IExpression person = null;
public AndExpression(IExpression city, IExpression 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 Content{
private String[] cities = {"韶关","广州"};
private String[] persons = {"老人","妇女","儿童"};
private IExpression cityPerson;
public Content() {
IExpression city = new TerminalExp(cities);
IExpression person = new TerminalExp(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 或 Jep 等来设计。
模式的扩展
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
使用前先导入JEP的maven依赖:
<dependency>
<groupId>jep</groupId>
<artifactId>jep</artifactId>
<version>2.24</version>
</dependency>
12345
下面以计算存款利息为例来介绍。存款利息的计算公式是:本金x利率x时间=利息,其相关代码如下:
package com.design.pattern.behaviorPattern.interpreterPattern;
import org.nfunk.jep.JEP;
public class JepDemo {
public static void main(String[] args) {
JEP jep = new JEP();
String lx = "本金*利率*时间";//定义表达式
jep.addVariable("本金",10000);
jep.addVariable("利率",0.041);
jep.addVariable("时间",2);
jep.parseExpression(lx);
System.out.println("存款利息:"+jep.getValue());
}
}
程序运行结果如下:
存款利息:820.0
状态模式
在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。如人的情绪有高兴的时候和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。
定义与特点
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式是一种对象行为型模式,其主要优点如下。
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
状态模式的主要缺点如下。
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
结构与实现
状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。
结构
状态模式包含以下主要角色。
- 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
其结构图如图 1 所示。
实现
状态模式的实现代码如下:
package com.design.pattern.behaviorPattern.StatePattern;
public class StatePatternClient {
public static void main(String[] args) {
Context context = new Context();
context.handle();
context.handle();
context.handle();
context.handle();
}
}
//抽象状态类
abstract class State{
public abstract void handle(Context context);
}
//具体状态类
class ConcreteStateA extends State{
@Override
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());
}
}
//环境类
class Context{
private State state;
public Context() {
this.state = new ConcreteStateA();
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//对请求做处理
public void handle(){
state.handle(this);
}
}
应用实例
【例1】用“状态模式”设计一个学生成绩的状态转换程序。
分析:本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。
首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态名属性和当前分数属性,以及加减分方法 addScore(intx) 和检查当前状态的抽象方法 checkState();然后,定义“不及格”状态类 LowState、“中等”状态类 MiddleState 和“优秀”状态类 HighState,它们是具体状态类,实现 checkState() 方法,负责检査自己的状态,并根据情况转换;最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法 add(int score),客户类通过该方法来改变成绩状态。图 2 所示是其结构图。
程序代码如下:
package com.design.pattern.behaviorPattern.StatePattern;
public class ScoreStateTest {
public static void main(String[] args) {
ScoreContext context = new ScoreContext();
System.out.println("学生成绩状态测试:");
context.add(30);
context.add(40);
context.add(25);
context.add(-15);
context.add(-25);
}
}
//环境类
class ScoreContext{
private AbstractState state;
public ScoreContext() {
state = new LowState(this);
}
public AbstractState getState() {
return state;
}
public void setState(AbstractState state) {
this.state = state;
}
public void add(int score){
state.addScore(score);
}
}
abstract class AbstractState {
protected ScoreContext context;
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当前状态:"+context.getState().stateName);
}
}
//具体状态类:不及格
class LowState extends AbstractState{
public LowState(ScoreContext scoreContext){
context = scoreContext;
stateName = "不及格";
score = 0;
}
public LowState(AbstractState state) {
context = state.context;
stateName = "不及格";
score = state.score;
}
@Override
public void checkState() {
if (score >= 90){
context.setState(new HighState(this));
}else if (score >= 60){
context.setState(new MiddleState(this));
}
}
}
//具体状态类:中等
class MiddleState extends AbstractState{
public MiddleState(AbstractState state) {
context = state.context;
stateName = "中等";
score = state.score;
}
@Override
public void checkState() {
if (score < 60){
context.setState(new LowState(this));
}else if (score >= 90){
context.setState(new HighState(this));
}
}
}
//具体状态类:优秀
class HighState extends AbstractState{
public HighState(AbstractState state) {
context = state.context;
stateName = "优秀";
score = state.score;
}
@Override
public void checkState() {
if (score < 60){
context.setState(new LowState(this));
}else if (score < 90){
context.setState(new MiddleState(this));
}
}
}
运行结果如下:
学生成绩状态测试:
加上:30分, 当前分数:30分, 当前状态:不及格
加上:40分, 当前分数:70分, 当前状态:中等
加上:25分, 当前分数:95分, 当前状态:优秀
加上:-15分, 当前分数:80分, 当前状态:中等
加上:-25分, 当前分数:55分, 当前状态:不及格
【例2】用“状态模式”设计一个多线程的状态转换程序。
分析:多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图 3 所示。
现在先定义一个抽象状态类(TheadState),然后为图 3 所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,图 4 所示是线程状态转换程序的结构图。
程序代码如下:
package com.design.pattern.behaviorPattern.StatePattern;
public class ThreadStateTest {
public static void main(String[] args) {
ThreadContext context = new ThreadContext();
context.start();
context.getCPU();
context.suspend();
context.resume();
context.getCPU();
context.stop();
}
}
//环境类
class ThreadContext{
private ThreadState state;
public ThreadContext() {
state = new New();
}
public ThreadState getState() {
return state;
}
public void setState(ThreadState state) {
this.state = state;
}
public void start(){
((New)state).start(this);
}
public void getCPU()
{
((Runnable) state).getCPU(this);
}
public void suspend()
{
((Running) state).suspend(this);
}
public void stop()
{
((Running) state).stop(this);
}
public void resume()
{
((Blocked) state).resume(this);
}
}
abstract class ThreadState {
protected String stateName;
}
//具体状态类:新建状态
class New extends ThreadState{
public New() {
stateName = "新建状态";
System.out.println("当前线程处于:新建状态");
}
public void start(ThreadContext context){
System.out.println("调用start方法-->");
if (stateName.equals("新建状态")){
context.setState(new Runnable());
}else {
System.out.println("当前线程不是新建状态,不能调用start()方法.");
}
}
}
//具体状态类:就绪状态
class Runnable extends ThreadState{
public Runnable()
{
stateName="就绪状态";
System.out.println("当前线程处于:就绪状态.");
}
public void getCPU(ThreadContext context){
System.out.println("获得CPU时间-->");
if (stateName.equals("就绪状态")){
context.setState(new Running());
}else {
System.out.println("当前线程不是就绪状态,不能获取CPU.");
}
}
}
//具体状态类:运行状态
class Running extends ThreadState{
public Running() {
stateName = "运行状态";
System.out.println("当前线程处于:运行状态.");
}
public void suspend(ThreadContext context){
System.out.println("调用suspend()方法-->");
if (stateName.equals("运行状态")){
context.setState(new Blocked());
}else {
System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
}
}
public void stop(ThreadContext context){
System.out.println("调用stop()方法-->");
if (stateName.equals("运行状态")){
context.setState(new Dead());
}else {
System.out.println("当前线程不是运行状态,不能调用stop()方法.");
}
}
}
//具体状态类:阻塞状态
class Blocked extends ThreadState{
public Blocked() {
stateName = "阻塞状态";
System.out.println("当前线程处于:阻塞状态.");
}
public void resume(ThreadContext hj)
{
System.out.print("调用resume()方法-->");
if(stateName.equals("阻塞状态"))
{
hj.setState(new Runnable());
}
else
{
System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
}
}
}
//具体状态类:死亡状态
class Dead extends ThreadState
{
public Dead()
{
stateName="死亡状态";
System.out.println("当前线程处于:死亡状态.");
}
}
运行结果如下:
当前线程处于:新建状态
调用start方法–>
当前线程处于:就绪状态.
获得CPU时间–>
当前线程处于:运行状态.
调用suspend()方法–>
当前线程处于:阻塞状态.
调用resume()方法–>当前线程处于:就绪状态.
获得CPU时间–>
当前线程处于:运行状态.
调用stop()方法–>
当前线程处于:死亡状态.
应用场景
通常在以下情况下可以考虑使用状态模式。
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
模式的扩展
在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构图如图 5 所示。
分析:共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态,当需要某种状态时可以从中获取,其程序代码如下:
package com.design.pattern.behaviorPattern.StatePattern;
import java.util.HashMap;
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> stateMap = new HashMap<>();
public ShareContext() {
state = new ConcreteState1();
stateMap.put("1",state);
state = new ConcreteState2();
stateMap.put("2",state);
state = getState("1");
}
public ShareState getState(String key) {
ShareState shareState = stateMap.get(key);
return shareState;
}
public void setState(ShareState state) {
this.state = state;
}
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
{
public void handle(ShareContext context)
{
System.out.println("当前状态是: 状态2");
context.setState(context.getState("1"));
}
}
策略模式
策略模式的定义:定义算法族,分别封装起来,让他们之间可以互相替换,此模式的变化独立于算法的使用者。在策略模式中,一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式。策略模式的类图示例如下:
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象。策略对象改变context对象的执行算法。
2、策略模式的特性
(1)意图:
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
(2)主要解决:
在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
(3)何时使用:
一个系统有许多许多类,而区分它们的只是他们直接的行为。
(4)如何解决:
将这些算法封装成一个一个的类,任意地替换。
(4)关键代码:
实现同一个接口。
3、策略模式的优缺点及应用场景
(1)优点:
1)算法可以自由切换。
2)避免使用多重条件判断。
3)扩展性良好。
(2)缺点:
1)策略类会增多。
2)所有策略类都需要对外暴露。
(3)使用场景:
1)如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2)一个系统需要动态地在几种算法中选择一种。
3)如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
(4)注意事项:
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
4、策略模式的应用实例
假设场景:假设在开发一款僵尸大战的游戏,有两种僵尸:普通僵尸和旗手僵尸,初始时他们的方法都一样:一步一步走,咬。但是后续随着僵尸种类的增加,会有多种僵尸,而且他们的方法实现也不一样,考虑如下:
4.1、原始方法实现示例
以下为传统直接实现的方式实现上述场景,我们每次要增加一个僵尸类时,都需要继承或重写一个类,类与类之间相互耦合,代码也逐渐变得难以理解,维护起来很麻烦。
package designpatterns.strategy.v1;
/**
* 〈一句话功能简述〉<br>
* 〈僵尸大战〉
*
* @author Jianf
* @create 2020/8/18
* @since 1.0.0
*/
public class ZombieTest {
public static void main(String[] args) {
AbstractZombie normalZombie = new NormalZombie();
AbstractZombie flagZombie = new FlagZombie();
normalZombie.display();
normalZombie.move();
normalZombie.attack();
System.out.println("-----------------------------");
flagZombie.display();
flagZombie.move();
flagZombie.attack();
}
}
//僵尸抽象类
abstract class AbstractZombie{
public abstract void display();
public void attack(){
System.out.println("咬。");
}
public void move(){
System.out.println("一步一步移动。。");
}
}
//普通僵尸类
class NormalZombie extends AbstractZombie{
@Override
public void display() {
System.out.println("我是普通僵尸");
}
}
class FlagZombie extends AbstractZombie{
@Override
public void display() {
System.out.println("我是旗手僵尸。。");
}
}
//假设后来又多了一些种类的僵尸,他们的这些行为不全部一样
//大头僵尸
class BigHeadZombie extends AbstractZombie{
@Override
public void display() {
System.out.println("我是大头僵尸。");
}
@Override
public void attack() {
//...
System.out.println("头撞。");
}
}
class XxxZombie extends BigHeadZombie{
@Override
public void move() {
System.out.println("xxx...");
}
}
//按照上面的逻辑,假设增加很多种类的僵尸,每次都需要继承或重写另一个僵尸类以实现其功能,代码将变得复杂难读
4.2 使用策略模式实现示例:
下面示例为使用策略模式来实现:我们每次新增一个僵尸类时,重写方法定义它的表现和行为,也可以在运行时通过动态修改其行为对象来实现对其行为的修改。各个僵尸类之间不耦合,具体实现算法可以相互替换。
package designpatterns.strategy.v2;
/**
* 〈一句话功能简述〉<br>
* 〈策略模式〉
*
* @author Jianf
* @create 2020/8/18
* @since 1.0.0
*/
public class StrategyTest {
public static void main(String[] args) {
//测试示例
Zombie zombie = new NormalZombie();
zombie.display();
zombie.attack();
zombie.move();
//更换攻击方式:传递算法,使用算法。。
zombie.setAttackable(new HitAttack());
zombie.attack();
}
}
//将共同特征进行抽象
interface Moveable{
void move();
}
interface Attackable{
void attack();
}
//抽象僵尸
abstract class Zombie{
abstract public void display();
Moveable moveable;
Attackable attackable;
abstract void move();
abstract void attack();
public Zombie() {
}
public Zombie(Moveable moveable, Attackable attackable) {
this.moveable = moveable;
this.attackable = attackable;
}
}
//普通僵尸
class NormalZombie extends Zombie{
public NormalZombie() {
super(new StepByStepMove(),new BiteAttack());
}
public NormalZombie(Moveable moveable, Attackable attackable) {
super(moveable, attackable);
}
@Override
public void display() {
System.out.println("我是普通僵尸。。");
}
@Override
void move() {
moveable.move();
}
@Override
void attack() {
attackable.attack();
}
}
//普通僵尸的行为
class StepByStepMove implements Moveable{
@Override
public void move() {
System.out.println("一步一步移动...");
}
}
class BiteAttack implements Attackable{
@Override
public void attack() {
System.out.println("咬");
}
}
//其他的攻击行为
class HitAttack implements Attackable{
@Override
public void attack() {
System.out.println("打。。");
}
}
//旗手僵尸
class FlageZombie extends Zombie{
public FlageZombie() {
super(new StepByStepMove(),new BiteAttack());
}
public FlageZombie(Moveable moveable, Attackable attackable) {
super(moveable, attackable);
}
@Override
public void display() {
System.out.println("我是旗手僵尸。。");
}
@Override
void move() {
moveable.move();
}
@Override
void attack() {
attackable.attack();
}
5、策略模式在JDK/Spring框架中的经典应用
(1) JDK中的Arrays类的sort()方法可以动态地传入比较器,不同的比较器可以有不同的实现算法,通过传入不同的比较器实现算法的替换,而该变化独立于算法的使用者,该方法源码如下:
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
我们传入的比较器只需要继承自Comparator接口并在其方法compare等中实现我们的比较逻辑即可。
(2) org.springframework.beans.factory.support.InstantiationStrategy接口,源码如下:
/**
* Interface responsible for creating instances corresponding to a root bean definition.
*
* <p>This is pulled out into a strategy as various approaches are possible,
* including using CGLIB to create subclasses on the fly to support Method Injection.
*
*/
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, Method factoryMethod, Object... args)
throws BeansException;
}
spring中该接口负责创建对应于根bean定义实例。创建方法可能多种,包括使用CGLIB来动态创建子类,以支持方法注入==>该接口被定义成策略。
访问者模式
访问者模式 : 封装 作用于 某种 数据结构 的 各元素 操作 , 数据结构指的是 List , Set , Map 等 ;
在 不改变 元素类 的前提下 , 定义 作用于 元素 的操作 ;
访问者模式类型 : 行为型 ;
二、访问者模式 适用场景
访问者模式 适用场景 :
- List , Set , Map 等 数据结构 中 , 包含 很多类型的对象 ;
- 数据结构 与 数据操作 分离 ;
三、访问者模式 优缺点
访问者模式 优点 : 访问者模式 增加新的操作 很容易 , 只需要增加一个新的 访问者 即可 ; 将相关的行为 , 封装到一个 访问者 中 ;
访问者模式 缺点 :
- 增加 新 数据结构 比较困难 ;
- 元素变更 比较困难 ; 如 为 被访问 的对象 增加 / 减少 一些属性 , 相应的 访问者 也需要进行修改 ;
四、访问者模式 与 迭代器模式
访问者模式 与 迭代器模式 都是在 某种 数据结构 ( List / Set / Map ) 上进行处理 ;
访问者模式 主要用于 对 保存在 数据结构 中元素 , 进行某种特定处理 , 重点是 处理 ;
迭代器模式 主要作用就是 遍历 数据结构 中的元素 , 重点是 遍历 ;
五、代码示例
游戏 分为 免费游戏 和 收费游戏 , 玩家 分为 免费玩家 和 付费玩家 ;
不同的玩家 , 访问不同的游戏 , 各自有不同的效果 ;
被访问者 , 都继承自同一个父类 , 可以放在一个集合中 ;
访问者 , 都实现同一个接口 , 针对每个类型的被访问者 , 都有一个对应的重载方法 ;
如 : 玩家访问者 , 对免费游戏有一个方法 , 对收费游戏也有一个方法 ;
该设计模式不常用 , 如果遇到 数据加载 与 数据操作 分离的情况 , 可以考虑使用 访问者模式 ;
大部分情况下 , 用不到 , 但是一旦需要用到的时候 , 访问者模式是唯一的解决方案 ;
1、Game 父类 ( 被访问者 )
package visitor;
/**
* 游戏父类 , 所有的游戏都要继承该类
* VipGame 和 FreeGame 都继承 Game , 这两个都是 Game
*
*/
public abstract class Game {
/**
* 游戏名称
*/
private String mGameName;}
/**
* 当有访问者要访问游戏时 , 将访问者传入该方法
* 用于判定访问者是否有权限访问游戏
* @param visitor
*/
public abstract void accept(IVisitor visitor);
}
2、VipGame 收费游戏 ( 被访问者 )
package visitor;
public class VipVisitor implements IVisitor {
/**
* 访问免费游戏 , 打印游戏名称
* @param freeGame
*/
@Override
public void visit(FreeGame freeGame) {
System.out.println("我是 VIP , 可以访问免费游戏 : " + freeGame.getGameName());
}
/**
* 访问收费游戏 , 打印游戏名称和价格
* @param vipGame
*/
@Override
public void visit(VipGame vipGame) {
System.out.println("我是 VIP , 可以访问收费游戏 : " + vipGame.getGameName() + " , 价格 : " + vipGame.getmVipPrice());
}
}
3、FreeGame 免费游戏 ( 被访问者 )
package visitor;
/**
* 免费游戏
*/
public class FreeGame extends Game {
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
4、IVisitor 访问者接口
package visitor;
/**
* 该接口中声明了两个重载方法
* 访问 FreeGame 的 visit 方法
* 访问 VipGame 的 visit 方法
*
* 在 Game 中调用 visit 方法 , 将自身传递到 visit 方法作为参数
* 分别调用不同的重载方法
*
* 访问者可以扩展很多了 , 可以定义若干实现了 IVisitor 接口的访问者
* 每个 Visistor 访问者 , 访问不同的游戏 , 有不同的表现 , 如 :
* 白嫖访问者 , 不花钱 , 只能玩免费游戏 , 不能玩收费游戏
* 付费访问者 , 购买 VIP , 可以同时玩免费和收费游戏
*
* 对于免费游戏来说 , 传入任何访问者都可以访问 ;
* 对于收费游戏来说 , 传入付费访问者, 才可以访问 ;
*
* 相同的 Visitor 对不同的数据产生不同的行为
* 不同的 Visitor 对相同的数据产生不同的行为
*/
public interface IVisitor {
/**
* 访问免费游戏
* @param freeGame
*/
void visit(FreeGame freeGame);
/**
* 访问收费游戏
* @param vipGame
*/
void visit(VipGame vipGame);
}
5、VipVisitor 付费玩家
package visitor;
public class VipVisitor implements IVisitor {
/**
* 访问免费游戏 , 打印游戏名称
* @param freeGame
*/
@Override
public void visit(FreeGame freeGame) {
System.out.println("我是 VIP , 可以访问免费游戏 : " + freeGame.getGameName());
}
/**
* 访问收费游戏 , 打印游戏名称和价格
* @param vipGame
*/
@Override
public void visit(VipGame vipGame) {
System.out.println("我是 VIP , 可以访问收费游戏 : " + vipGame.getGameName() + " , 价格 : " + vipGame.getmVipPrice());
}
}
6、测试类
package visitor;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 创建存放 Game 的集合
ArrayList<Game> games = new ArrayList<>();
// 创建免费游戏
FreeGame freeGame = new FreeGame();
freeGame.setGameName("超级马里奥");
games.add(freeGame);
// 创建收费游戏
VipGame vipGame = new VipGame();
vipGame.setGameName("绝地求生");
vipGame.setmVipPrice(88);
games.add(vipGame);
// Vip 访问者访问免费和收费游戏
for (Game game : games) {
game.accept(new VipVisitor());
}
}
}
运行结果 :