概述
设计模式
参考:bilibili尚硅谷教程
一、七大设计原则
设计模式目的:
1)代码重用性(即:相同功能的代码,不用多次编写)
2)可读性(即:编程规范性,便于其他程序员的阅读和理解)
3)可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
4)可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
5)使程序呈现高内聚,低耦合的特性
核心思想:
1)找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
2)针对接口编程,而不是针对实现编程。
3)为了交互对象之间的松耦合设计而努力
1.单一职责原则(各行其职,类级别上、方法级别上遵守单一职责原则)
1.1基本介绍
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。
1.2单一职责原则注意事项和细节
1降低类的复杂度,一个类只负责一项职责。
2提高类的可读性,可维护性
3降低变更引起的风险
4通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中
方法数量足够少,可以在方法级别保持单一职责原则
2接口隔离原则
2.1基本介绍
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
2.2 类图
A通过Interface1依赖B:B继承Interface1接口,类A的方法传入这个接口
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
类A通过接口 Interfacel依赖类B,类C通过接口Interfacel依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
按隔离原则应当这样处理:
将接口Interface1拆分为独立的几个接口(这里我们拆分成3个接口),类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
接口1
interface Interface1 {
void operation1();
}
/接口2
interface Interface2 {
void operation2();
void operation3();
}
//接口3
interface Interface3 {
void operation4();
void operation5();
}
class B implements Interface1, Interface2{
public void operation1() {
System.out.println("B实现了operation1");
}
public void operation2(){
System.out.println("B实现了operation2");
}
public void operation3({
System.out.println("B实现了operation3");
}
}
class A {//A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法
public void depend 1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation3();
}
}
3依赖倒转(倒置)原则(接口或抽象类的价值在于设计)
3.1基本介绍
依赖倒转原则(Dependence Inversion Principle)是指:
1)高层模块不应该依赖低层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖倒转(倒置)的中心思想是面向接口编程
4)依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
3.2依赖关系传递的三种方式和应用案例
1)接口传递
interface IOpenAndClose {
public void open(ITV tv); //抽象方法,接收接口
}
interface ITV {//ITV接口
public void play();
}
class ChangHong implements ITV {
@Override
public void play() {
//TODO Auto-generated method stubSystem.out.println("长虹电视机,打开");
}
}
//实现接口
class OpenAndClose implements IOpenAndClose{
public void open(ITV tv){
tv.play();
}
}
2)构造方法传递
interface IOpenAndClose {
public void open();//抽象方法
}
interface ITV {//ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose{
public ITV tv;//成员
public OpenAndClose(ITV tv){//构造器
this.tv = tv;
}
public void open(){
this.tv.play();
}
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
3) setter方式传递
interface IOpenAndClose {
public void open();//抽象方法
public void setTv(ITV tv);
}
interface ITV {//ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose{
private ITV tv;
public void setTv(ITV tv){
this.tv = tv;
}
public void open() {
this.tv.play();
}
class ChangHong implements ITV{
@Override
public void play() {
//TODO Auto-generated method stub
System.out. println("长虹电视机,打开");}
}
}
ITV changHong=new ChangHong();
openAndclose openAndClose = new openAndClose();
openAndclose.setTv(changHong);
openAndClose.open();
4里氏替换原则(OO中继承性的思考)
4.1 OO中的继承性的思考和说明
- 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
3)问题提出:在编程中,如何正确的使用继承=>里氏替换原则
4.2基本介绍
如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序Р在所有的对象o1都代换成o2时,程序Р的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
如果出现A类继承B类,但是大量重写了B类的方法,可通过A类B类共同继承一个基类来改善耦合性。
由A继承B改为:A/B继承Base,如果A想继续使用B的方法,就采用聚合,组合,依赖的方式:
5开闭原则
5.1基本介绍
1.开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
2.一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方:使用方式指某个对象的方法被使用的地方)。用抽象构建框架,用实现扩展细节。
3.当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
4.编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
public class Ocp {
public static void main(String[] args) {
//使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//这是一个用于绘图的类[使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1) {
drawRectangle(s);
} else if (s.m_type == 2) {
drawCircle(s);
} else if (s.m_type == 3) {
drawTriangle(s);
}
}
//绘制矩形
public void drawRectangle(Shape r) {
System.out.println("绘制矩形");
}
//绘制圆形
public void drawCircle(Shape r) {
System.out.println("绘制圆形");
}
//绘制三角形
public void drawTriangle(Shape r) {
System.out.println("绘制三角形");
}
}
//Shape类,基类
class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
//新增画三角形
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
以上,增加一个绘制三角形的类,GraphicEditor使用方进行修改,违反了开闭原则对扩展开放(对提供方),对修改关闭
比如我们这时要新增加一个图形种类三角形,我们需要做如下修改,修改的地方较多
思路:把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要修;满足了开闭原则
public class Ocp {
public static void main(String[] args) {
//使用看看存在的问题
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//这是一个用于绘图的类「使用方]
class GraphicEditor {
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
s.draw();
}
}
//Shape类,基类
abstract class Shape {
public abstract void draw();//抽象方法
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
//新增画三角形
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
6迪米特法则(直接朋友针对类来定义)
6.1基本介绍
1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越大
3) 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
4)迪米特法则还有个更简单的定义:只与直接的朋友通信
5)直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
import java.util.ArrayList;
import java.util.List;
public class Demeter {
public static void main(String[] args) {
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();//输出学院的员工id和―学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理学院员工的管理类
class CollegeManager {
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {//这里我们增加了10个员工到list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id=" + i);
list.add(emp);
}
return list;
}
}
//学校管理类
//分析SchoolManager类的直接朋友类有哪些Employee、CollegeManagerl/CollegeEmployee不是直接朋友而是一个陌生类,这样违背了迪米特法则
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
//这里我们增加了5个员工到list
Employee emp = new Employee();
emp.setId("学校总部员工id=" + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
// 分析问题
//1.这里的 CollegeEmployee不是SchoolManager 的直接朋友l/2.CollegeEmployee是以局部变量方式出现在SchoolManager
//违反了迪米特法则
// 获取到学院员工
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println(" ------------学院员工------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("-- - 学校总部员工-- - ");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
改进:
import java.util.ArrayList;
import java.util.List;
public class Demeter {
public static void main(String[] args) {
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();//输出学院的员工id和―学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//学院的员工类
class CollegeEmployee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
//管理学院员工的管理类
class CollegeManager {
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {//这里我们增加了10个员工到list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id=" + i);
list.add(emp);
}
return list;
}
//输出学院员工的信息
public void printEmployee() {
//获取到学院员工
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("------------学院员工--------- ");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
//分析SchoolManager类的直接朋友类有哪些Employee、CollegeManagerl/CollegeEmployee不是直接朋友而是一个陌生类,这样违背了迪米特法则
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
//这里我们增加了5个员工到list
Employee emp = new Employee();
emp.setId("学校总部员工id=" + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析问题
//1.将输出学院的员工方法,封装到CollegeManager
sub.printEmployee();
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("-- - 学校总部员工-- - ");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
7合成复用原则
原则是尽量使用合成/聚合的方式,而不是使用继承
如果B类要使用A类的方法:尽量使用合成/聚合的方式,而不是使用继承
依赖/合成:把A当成参数传进B的方法
聚合:把A置为B的属性,通过set方法传入
组合:把A置为B的属性,在创建B的对象的时候A的对象也被new出来了
二、前置知识
1.UML类图
2.类与类之间的关系
依赖、泛化(继承)、实现、关联、聚合与组合。
2.1类图-依赖关系(Dependence)
只要是在类中用到了对方,那么他们之间就存在依赖关系。
1)类中用到了对方
2)如果是类的成员属性
3)如果是方法的返回类型
4)是方法接收的参数类型
5)方法中使用到
2.2类图—泛化关系(generalization)
泛化关系实际上就是继承关系,他是依赖关系的特例
1)泛化关系实际上就是继承关系
2)如果A类继承了B类,我们就说A和B存在泛化关系
2.3类图—实现关系(Implementation)
实现关系实际上就是A类实现B接口,他是依赖关系的特例
2.4类图—关联关系(Association)
关联关系实际上就是类与类之间的联系,他是依赖关系的特例
单向一对一关系:
双向一对一关系:
2.5类图—聚合关系(Aggregation)
聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。
2.6类图—组合关系(Composition)
组合关系:也是整体与部分的关系,但是整体与部分不可以分开。
再看一个案例:在程序中我们定义实体:Person与 IDCard、Head,那么Head和 Person就是组合,IDCard和Person就是聚合。
3.设计模式简介
设计模式分为三种类型,共23种
1)创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
3)行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、
解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。
注意:不同的书籍上对分类和名称略有差别
三、设计模式
1.单例模式(类的实例唯一)
1.1简介
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。
单例模式有八种方式:
1)饿汉式(静态常量)
2)饿汉式(静态代码块)
3)懒汉式(线程不安全)
4)懒汉式(线程安全,同步方法)
5)懒汉式(线程安全,同步代码块)
6)双重检查
7)静态内部类
8)枚举
1.2饿汉式(静态常量)
饿汉式(静态常量)应用实例步骤如下:
1)构造器私有化(防止 new )
2)类的内部创建对象
3)向外暴露一个静态的公共方法。(getInstance)
package com.fuhong.designpattern.singleton;
public class SingletonTest01 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance==instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
//饿汉式(静态变量)
static class Singleton {
//1.构造器私有化,外部能new
private Singleton() {
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
}
优缺点说明:
1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
2)缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则
会造成内存的浪费
3)这种方式基于classloder 机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大
多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到 lazy loading的效果
4)结论:这种单例模式可用,可能造成内存浪费
1.3饿汉式(静态代码块)
package com.fuhong.designpattern.singleton;
public class SingletonTest02 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);//true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//饿汉式(静态变量)
class Singleton {
//1.构造器私有化,外部能new
private Singleton() {
}
private final static Singleton instance;
static {//在静态代码块中,创建单例对象
instance = new Singleton();
}
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点说明:
1)这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执
行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
2)结论:这种单例模式可用,但是可能造成内存浪费
1.4懒汉式(线程不安全)
package com.fuhong.designpattern.singleton;
public class SingletonTest03 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
static class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,当使用到该方法时,才去创建instance
// 即懒汉式
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}
优缺点说明:
1)起到了Lazy Loading 的效果,但是只能在单线程下使用。
2) 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过
了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
3)结论:在实际开发中,不要使用这种方式.
1.5懒汉式(线程安全,同步方法)
package com.fuhong.designpattern.singleton;
public class SingletonTest04 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
//懒汉式(线程安全,同步方法)
static class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题//即懒汉式
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
}
优缺点说明:
1)解决了线程安全问题
2)效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行
一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
3)结论:在实际开发中,不推荐使用这种方式
1.6懒汉式(线程安全,同步代码块)
package com.fuhong.designpattern.singleton;
public class SingletonTest05 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
//懒汉式(线程不安全,同步方法)
static class Singleton {
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题//即懒汉式
public static Singleton getInstance() {
if (instance == null) {
synchronized ("1") {
instance = new Singleton();
}
}
return instance;
}
}
}
不使用
1.7双重检查
package com.fuhong.designpattern.singleton;
public class SingletonTest06 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
//懒汉式(线程安全,同步方法)
static class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题/同时保证了效率,推荐使用
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
}
优缺点说明:
- Doubl-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if(singletonnull)检查,这
样就可以保证线程安全了。
2)这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton-null),直接return实例化对象,也避
免的反复进行方法同步.
3)线程安全;延迟加载;效率较高
4)结论:在实际开发中,推荐使用这种单例设计模式
1.8静态内部类
package com.fuhong.designpattern.singleton;
public class SingletonTest07 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println(" instance.hashCode=" + instance.hashCode());
System.out.println(" instance2.hashCode=" + instance2.hashCode());
}
//静态内部类完成,推荐使用
static class Singleton {
//构造器私有化
private Singleton() {
}
//在外面的类被装载时,静态内部类不会被装载,getInstance()时静态内部类被装载,且只会被装载一次 线程安全
//写一个静态内部类,该类中有一个静态属性Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
}
优缺点说明:
1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程。2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才
会装载SingletonInstance类,从而完成Singleton的实例化。
3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行
初始化时,别的线程是无法进入的。
4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高5)结论:推荐使用.
1.9枚举
package com.fuhong.designpattern.singleton;
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
//使用枚举,可以实现单例,推荐
enum Singleton {
INSTANCE;//属性
public void sayOK() {
System.out.println("ok~");
}
}
}
优缺点说明:
- 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
2)这种方式是Effective Java作者Josh Bloch提倡的方式
3)结论:推荐使用
1.10 JDK源码单例模式使用案例
1.11单例使用注意事项
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,
使用单例模式可以提高系统性能
2)当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
3)单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级
对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
2.工厂模式(对象的统一管理)
如果产品种类很多,抽象工厂,实现产品簇管理。否则简单工厂。
2.1简单工厂
2.1.1未使用工厂模式伪代码
//披萨的抽象类,供所有的披萨继承
public abstract class Pizza {
public abstract void prepare();
public void bake() {
System.out.println("烤焙");
}
public void cut() {
System.out.println("切片");
}
public void box() {
System.out.println("装盒");
}
}
/**
* 具体的披萨01
*/
public class ChessPizza extends Pizza{
@Override
public void prepare() {
System.out.println("ChessPizza准备");
}
}
/**
* 具体的披萨02
*/
public class GreekPizza extends Pizza{
@Override
public void prepare() {
System.out.println("GreekPizza准备");
}
}
/**
* 披萨订购
*/
public class OrderPizza {
public OrderPizza() {
Pizza pizza = null;
String orderType; //订购披萨的类型
do {
orderType = "getType()";//这里应该是订购输入类型
if (orderType.equals("greek")) {
pizza = new GreekPizza();
} else if (orderType.equals("cheese")) {
pizza = new ChessPizza();
} else {
break;
}
//输出pizza 制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
}
分析:
1)优点是比较好理解,简单易操作。
2)缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
3)比如我们这时要新增加一个Pizza的种类(Pepper披萨),我们需要做如下修改.如果我们增加一个Pizza类,只要是订购Pizza的代码都需要修改.
4)改进的思路分析
分析:修改代码可以接受,但是如果我们在其它的地方也有创建 Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有多处。
思路:把创建Pizza对象封装到一个类中,这样我们有新的 Pizza种类时,只需要修改该类就可,其它有创建到Pizza对象的代码就不需要修改了----简单工厂模式
缺陷总结:如果增加一种披萨的种类,所有的披萨订购类都会发生修改
2.2简单工厂基本介绍
1)简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
2)简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
3)在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式
2.3使用简单工厂
/**
*披萨订购类
*/
public class OrderPizza {
//定义一个简单工厂对象
SimpleFactory simpleFactory;
Pizza pizza = null;
//构造器
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}
public void setFactory(SimpleFactory simpleFactory) {
String orderType = "xxx";//用户输入的
this.simpleFactory = simpleFactory;//设置简单工厂对象
do {
pizza = this.simpleFactory.createPizza(orderType);
//输出pizza
if (pizza != null) {//订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败");
break;
}
} while (true);
}
}
//简单工厂类
public class SimpleFactory {
//更加orderType返回对应的Pizza 对象
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
} else if (orderType.equals("chess")) {
pizza = new ChessPizza();
}
return pizza;
}
}
总结:使用简单工厂,将披萨的种类进行封装,对于新增的披萨种类只需对工厂类进行修改;另:可考虑注册模式,将披萨的种类注册进工厂
2.2工厂方法模式
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪pizza、北京的胡椒pizza或者是伦敦的奶酪pizza、伦敦的胡椒pizza。
思路1:
使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory等等.从当前这个案例来说,也是可以的,但是考虑到项目的规模,以及软件的可维护性、可扩展性并不是特别好
思路2:
使用工厂方法模式
2.2.1工厂方法模式介绍
1)工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
2)工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
注:createPizza()方法定义为抽象方法,分别下沉至BJOrderPizza/LDOrderPizza进行实现
2.2.2代码
public abstract class Pizza {
public abstract void prepare();
public void bake() {
System.out.println("拷贝");
}
public void cut() {
System.out.println("切片");
}
public void box() {
System.out.println("装盒");
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class BJPepperPizza extends Pizza {
@Override
public void prepare() {
System.out.println("BJPepperPizza 准备");
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class BJCheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("BJCheesePizza 准备");
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
System.out.println("LDCheesePizza 准备");
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class LDPepperPizza extends Pizza {
@Override
public void prepare() {
System.out.println("LDPepperPizza 准备");
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
/**
* 披萨订购类
*/
public abstract class OrderPizzaMethodFactory {
//定义一个抽象方法,createPizza,让各个工厂子类自己实现
abstract Pizza createPizza(String orderType);
public OrderPizzaMethodFactory() {
Pizza pizza=null;
do {
//orderType依靠用户传入 getType方法用户传入
pizza = createPizza("");//抽象方法,由工厂子类完成
//输出pizza
if (pizza != null) {//订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购披萨失败");
break;
}
} while (true);
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class BJOrderPizza extends OrderPizzaMethodFactory {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new BJCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class LDOrderPizza extends OrderPizzaMethodFactory {
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
if (orderType.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (orderType.equals("pepper")) {
pizza = new LDPepperPizza();
}
//TODO Auto-generated method stub
return pizza;
}
}
2.3抽象工厂模式(产品簇概念)
北京披萨和伦敦披萨可以看成是产品等级,不同的披萨可以看成是不同的产品。
2.3.1基本介绍
1)抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
2)抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
3)从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
4)将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
2.3.2代码
Pizza类BJCheessPizza类BJPepperPizza类LDCheessPizza类LDPepperPizza类代码使用工厂方法模式
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
//一个抽象工厂模式的抽象层(接口)
public interface AbsFactory {
//让下面的工厂子类来具体实现
public Pizza createPizza(String orderType);
}
import com.fuhong.designpattern.factory.simpleFactory.methodFactory.BJCheesePizza;
import com.fuhong.designpattern.factory.simpleFactory.methodFactory.BJPepperPizza;
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
//这是工厂子类
public class BJFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("~使用的是抽象工厂模式~");
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new BJCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
import com.fuhong.designpattern.factory.simpleFactory.methodFactory.LDCheesePizza;
import com.fuhong.designpattern.factory.simpleFactory.methodFactory.LDPepperPizza;
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
//这是工厂子类
public class LDFactory implements AbsFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("~使用的是抽象工厂模式~");
Pizza pizza = null;
if ("cheese".equals(orderType)) {
pizza = new LDCheesePizza();
} else if ("pepper".equals(orderType)) {
pizza = new LDPepperPizza();
}
return pizza;
}
}
import com.fuhong.designpattern.factory.simpleFactory.old.Pizza;
public class OrderPizza {
AbsFactory factory;
//构造器
public OrderPizza(AbsFactory factory) {
setFactory(factory);
}
private void setFactory(AbsFactory factory) {
Pizza pizza = null;
//用户输入
String orderType = "";
// 用户输入
this.factory = factory;
do {
//factory可能是北京的工厂子类,也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null) {
// 订购ok
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("订购失败");
break;
}
} while (true);
}
}
2.4工厂模式在JDK-Calendar应用的源码分析(略)
2.5工厂模式小结
1)工厂模式的意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项
目的扩展和维护性。
2)三种工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)
3)设计模式的依赖抽象原则
创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
不要让类继承具体类,而是继承抽象类或者是实现interface(接口),不要覆盖基类中已经实现的方法。
3.原型模式(拷贝原型,创建新对象)
3.1传统代码
public class Client {
public static void main(String[] args) {
Sheep sheep = new Sheep(1, "yang1", "red");
Sheep sheep1 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
//.....
System.out.println(sheep);
//.....
}
}
传统的方式的优缺点
1)优点是比较好理解,简单易操作。
2)在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
3)总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
4)改进的思路分析
思路:Java 中 Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力==>原型模式
3.2原型模式基本介绍
1)原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
2)原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
3)工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
原理结构图说明
- Prototype:原型类,声明一个克隆自己的接口
- ConcretePrototype:具体的原型类,实现一个克隆自己的操作
- Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
3.3原型模式代码(重写clone)
import com.fuhong.designpattern.prototype.old.Sheep;
public class newClient {
public static void main(String[] args) {
Sheep sheep = new Sheep(1, "yang1", "red");
Sheep sheep1 = (Sheep)sheep.clone();
System.out.println(sheep1);
}
}
public class Sheep implements Cloneable {
private int age;
private String name;
private String color;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Sheep(int age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + ''' +
", color='" + color + ''' +
'}';
}
@Override
public Object clone() {
Sheep sheep=null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
3.4深入讨论-浅拷贝和深拷贝
3.4.1浅拷贝的介绍
- 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
3)浅拷贝是使用默认的 clone()方法来实现sheep =(Sheep) super.clone();
3.4.2深拷贝基本介绍
1)复制对象的所有基本数据类型的成员变量值
2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
3)深拷贝实现方式1:重写clone方法来实现深拷贝
4)深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)
通过clone的方式实现深拷贝(单独处理引用数据类型)多层引用???
通过序列化的方式实现深拷贝(多个引用数据类型页一起拷贝了,推荐使用)
import java.io.*;
public class DeepProtoType implements Serializable, Cloneable {
public String name;//String 属性
public DeepProtoType deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//深拷贝–方式1使用clone方法
@Override
protected Object clone() {
Object deep = null;
DeepProtoType deepProtoType = null;
//这里完成对基本数据类型(属性)和 String 的克隆
try {
deep = super.clone();
//对引用类型的属性,进行单独处理
deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepProtoType) deepCloneableTarget.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return deepProtoType;
}
//深拷贝–方式2通过对象的序列化实现(推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);//当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (DeepProtoType) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
assert bos != null;
bos.close();
assert oos != null;
oos.close();
assert bis != null;
bis.close();
assert ois != null;
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
3.5原型模式注意事项
1)创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
2)不用重新初始化对象,而是动态地获得对象运行时的状态
3) 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码4)在实现深克隆的时候可能需要比较复杂的代码
5)缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则
4.建造者模式(对象构建,产品和产品建造过程解耦)
4.1传统代码
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
}
}
public abstract class AbstractHouse {
//打地基
public abstract void buildBasic();
//砌墙
public abstract void buildWalls();
//封顶
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
public class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println("普通房子打地基");
}
@Override
public void buildWalls() {
System.out.println("普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("普通房子封顶");
}
}
传统方式的问题分析:
1)优点是比较好理解,简单易操作。
2)设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好.也就是说,这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了。
3)解决方案:将产品和产品建造过程解耦==>建造者模式.
4.2建造者模式基本介绍
基本介绍:
1)建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
2)建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色
1) Product(产品角色):一个具体的产品对象。
2) Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类。
3) ConcreateBuilder(具体建造者):实现接口,构建和装配各个部件。
4)Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
4.3建造者模式代码
类图:
产品:
//产品->Product
public class House {
private String baise;
private String wall;
private String roofed;
public String getBaise() {
return baise;
}
public void setBaise(String baise) {
this.baise = baise;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
抽象的建造者:
//抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子好,将产品(房子)返回
public House buildHouse() {
return house;
}
}
**ConcreateBuilder:**完成操作,完成产品属性
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
house.setBaise("普通房子打地基5米");
System.out.println("普通房子打地基5米");
}
@Override
public void buildWalls() {
System.out.println("普通房子砌墙10cm ");
}
@Override
public void roofed() {
System.out.println("普通房子屋顶 ");
}
}
public class HighHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼的打地基100米");
}
@Override
public void buildWalls() {
System.out.println("高楼的砌墙20cm ");
}
@Override
public void roofed() {
System.out.println("高楼的透明屋顶");
}
}
Director:具体的过程抽给指挥者,然后返回产品
public class HouseDirector {
HouseBuilder houseBuilder = null;
//构造器传入houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//通过setter传入 houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls ();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
调用传入具体的产品:
public class Client {
public static void main(String[] args) {
//盖普通房子
CommonHouse commonHouse = new CommonHouse();//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonHouse);
//完成盖房子,返回产品(普通房子)
House house = houseDirector.constructHouse();
// System.out.println("输出流程");
System.out.println("-盖高楼 - ");
HighBuilding highBuilding = new HighBuilding();//重置建造者
houseDirector.setHouseBuilder(highBuilding);//完成盖房子,返回产品(高楼)
houseDirector.constructHouse();
}
}
4.4建造者模式在JDK应用
源码中建造者模式角色分析:
1.Appendable接口定义了多个append方法(抽象方法),即 Appendable为抽象建造者,定义了抽象方法
2.AbstractStringBuider 实现了Appendable 接口方法,这里的 AbstractStringBuilder已经是建造者,只是不能实例化
3.StringBuilder即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由AbstractStringBuilder 完成,而StringBuilder继承了AbstractStringBuilder
4.5建造者模式的注意事项和细节
1)客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2)每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
3)可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰.也更方便使用程序来控制创建过程
4) 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
5)建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6) 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
7)抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建(不同类的对象),一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品(同一个类的不同对象:具体体现在不同的属性)
5.适配器模式(兼容类)
5.1基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本
因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
2)适配器模式属于结构型模式
3)主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
5.2工作原理
1)适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
2)从用户的角度看不到被适配者,是解耦的
3)用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
4)用户收到反馈结果,感觉只是和目标接口交互
5.3类适配器模式
5.3.1类适配器模式介绍
基本介绍:Adapter类,通过继承src类,实现dst类接口,完成src->dst 的适配。
5.3.2类适配器模式应用实例
1)应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的目dst(即目标)是5V直流电
2)思路分析(类图)
5.3.3类适配器代码
public class Client {
public static void main(String[] args) {
System.out.println(" =—-类适配器模式=——=");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V) {
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5V,可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V,不能充电~~");
}
}
}
//适配接口
public interface IVoltage5V {
public int output5V();
}
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到220V电压
int srcV = output220V();
int dstV = srcV / 44;//转成5v
return dstV;
}
}
5.3.4类适配器模式注意事项和细节
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性;
- src类的方法在Adapter 中都会暴露出来,也增加了使用的成本。
3)由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter 的灵活性增强了。
5.4对象适配器模式
5.4.1对象适配器模式介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现 dst类接口,完成src->dst的适配
2)根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。3)对象适配器模式是适配器模式常用的一种
5.4.2对象适配器模式应用实例
1)应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成。
2)思路分析(类图):只需修改适配器即可,如下:
5.4.2代码
public class Client {
public static void main(String[] args) {
System.out.println(" =—-对象适配器模式=——=");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
public class Phone {
//充电
public void charging(IVoltage5V iVoltage5V) {
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5V,可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V,不能充电~~");
}
}
}
//被适配的类
public class Voltage220V {
//输出220V的电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
//适配接口
public interface IVoltage5V {
public int output5V();
}
public class VoltageAdapter implements IVoltage5V {
private final Voltage220V voltage220V;
//通过构造传入
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
int src = voltage220V.output220V();//获取220V电压
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
5.4.3对象适配器模式注意事项和细节
1)对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
2)使用成本更低,更灵活。
5.5接口适配器模式
5.5.1接口适配器模式介绍
1)一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
2)核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
3)适用于一个接口不想使用其所有的方法的情况。
5.5.2代码
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们需要使用接口方法aOverride
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}
}
//在AbsAdapter我们将Interface4 的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
//默认实现
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
5.6适配器模式在SpringMVC框架应用的源码剖析
- SpringMvc中的 HandlerAdapter,就使用了适配器模式
- SpringMVC处理请求的流程回顾(略)
3)使用HandlerAdapter的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller就得修改原来的代码,这样违背了OCP原则。
4)代码分析+Debug 源码
DispatcherServlet类doDispatch方法:
1.将request交给processedRequest
2.通过processedRequest拿到mappedHandler
3.通过mappedHandler,遍历所有的Adapter得到具体controller对应的HandlerAdapter
4.通过适配器调用具体controller的handle方法,返回modelAndView
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request); //1 将request交给processedRequest
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest); //2.通过processedRequest拿到mappedHandler
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//3.通过mappedHandler,遍历所有的Adapter得到具体controller对应的HandlerAdapter
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//4.通过适配器调用具体controller的handle方法
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
DispatcherServlet类doDispatch方法:
1.遍历所有handlerAdapters,得到具体controller对应的HandlerAdapter
2.HandlerAdapter接口的supports体现接口适配器模式,具体的适配器去实现
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {//1.遍历所有handlerAdapters,得到具体controller对应的HandlerAdapter
if (adapter.supports(handler)) {//2.HandlerAdapter接口的supports体现接口适配器模式,具体的适配器去实现
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
HandlerAdapter接口:
具体的实现类去实现方法:体现接口适配器模式
boolean supports(Object handler);
类图:
5.7适配器模式的注意事项和细节
1)三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
2)类适配器:以类给到,在 Adapter 里,就是将src当做类,继承
对象适配器:以对象给到,在 Adapter 里,将src 作为一个对象,持有
接口适配器:以接口给到,在Adapter 里,将src 作为一个接口,实现
- Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
4)实际开发中,实现起来不拘泥于以上三种经典形式
6.桥接模式(抽象与行为实现分离)
6.1手机操作问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),
6.2传统解决
传统方案解决手机操作问题分析
1)扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
2)违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
3)解决方案-使用桥接模式
6.3桥接模式(Bridge)-基本介绍
1)桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
2)是一种结构型设计模式
3) Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
说明:
1)Client类:桥接模式的调用者
2)抽象类(Abstraction) :维护了Implementor 即它的实现类ConcretelmplementorA.,二者是聚合关系, Abstractio充当桥接类
3)RefinedAbstraction:是 Abstraction抽象类的子类
- Implementor:行为实现类的接口
- ConcretelmplementorA/B :行为的具体实现类
6)从UML图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系
6.4桥接模式解决手机操作问题
使用桥接模式改进传统方式,让程序具有搞好的扩展性,利用程序维护
1)应用实例说明(和前面要求一样)
2)使用桥接模式对应的类图
理解:如果需要新增品牌或者样式,实现起来容易。new FoldedPhone(new XiaoMi()):FoldedPhone调用抽象父类的构造方法,抽象父类组合品牌接口,phone1.open()实际调用的组合品牌过后的open,调用了品牌的open.
public class Client {
public static void main(String[] args) {
//获取折叠式手机(样式+品牌)
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("++++++++++++++++++++++++");
Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();
System.out.println("++++++++++++++++++++++++++++++");
UpRightPhone phone3 = new UpRightPhone(new XiaoMi());
phone3.open();
phone3.call();
phone3.close();
System.out.println("++++++++++++++++++++++++++");
UpRightPhone phone4 = new UpRightPhone(new Vivo());
phone4.open();
phone4.call();
phone4.close();
}
}
接口,组合于抽象类:
//接口
public interface Brand {
void open();
void close();
void call();
}
抽象类:充当桥接类
public abstract class Phone {
//组合品牌
private Brand brand;
//构造器
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void open() {
this.brand.open(); //组合品牌的open
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}
}
手机样式:折叠继承抽象类
//折叠手机
public class FoldedPhone extends Phone {
//构造器
public FoldedPhone(Brand brand) {
super(brand);//调用抽象父类的构造
}
public void open() {
super.open();//调用抽象父类的open
System.out.println("折叠样式手机");
}
public void close() {
super.close();
System.out.println("折叠样式手机");
}
public void call() {
super.call();
System.out.println("折叠样式手机");
}
}
手机样式:直立继承抽象类
public class UpRightPhone extends Phone {
//构造器
public UpRightPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("直立样式手机");
}
public void close() {
super.close();
System.out.println("直立样式手机 ");
}
public void call() {
super.call();
System.out.println("直立样式手机 ");
}
}
品牌:vivo实现Brand
public class Vivo implements Brand {
@Override
public void open() {
System.out.println(" Vivo手机开机 ");
}
@Override
public void close() {
System.out.println(" Vivo手机关机");
}
@Override
public void call() {
System.out.println(" Vivo手机打电话");
}
}
品牌:小米实现Brand
public class XiaoMi implements Brand {
@Override
public void open() {
System.out.println("小米手机开机 ");
}
@Override
public void close() {
System.out.println("小米手机关机 ");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
6.5桥接模式在JDBC的源码剖析
桥接模式在JDBC的源码剖析
l) Jdbc 的 Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
2)代码分析+Debug源码
6.6桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
2)对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
3)桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
4)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
5)桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有一定的局限性,
即需
要有这样的应用场景。
桥接模式其它应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
常见的应用场景:
1 ) -JDBC驱动程序
- -银行转账系统
转账分类:网上转账,柜台转账,AMT转账
转账用户类型:普通用户,银卡用户,金卡用户…
3)-消息管理
消息类型:即时消息,延时消息
消息分类:手机短信,邮件消息,QQ消息…
7.装饰者设计模式(新功能附加到对象上)
7.1星巴克咖啡订单项目(咖啡馆):
1)咖啡种类/单品咖啡: Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
2)调料:Milk、Soy(豆浆)、Chocolate
3)要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
4)使用oo的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合,计算费用。
7.2传统方案1
方案问题分析:
1)Drink 是一个抽象类,表示饮料
2) des就是对咖啡的描述,比如咖啡的名字
3) cost()方法就是计算费用,Drink类中做成一个抽象方法.
- Decaf 就是单品咖啡,继承 Drink,并实现cost
- Espress && Milk 就是单品咖啡+调料,这个组合很多
6)问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
7.3传统方案2
1)前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
2)说明: milk,soy,chocolate可以设计为Boolean,表示是否要添加相应的调料.
问题分析:
1)方案2可以控制类的数量,不至于造成很多的类
2)在增加或者删除调料种类时,代码的维护量很大
3)考虑到用户可以添加多份调料时,可以将hasMilk返回一个对应int
4)考虑使用装饰者模式
7.4装饰者模式介绍
定义:装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
原理:
1)装饰者模式就像打包一个快递
主体:比如:陶瓷、衣服(Component)ll被装饰者
包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
- Component主体:比如类似前面的 Drink
- ConcreteComponent和 Decorator
ConcreteComponent:具体的主体,比如前面的各个单品咖啡 - Decorator:装饰者,比如各调料.
在如图的Component 与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。
类图:
例:
装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack:
Client:
public class CoffeeBar {
public static void main(String[] args) {
//装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
//1、点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2.order 加入一份牛奶
order = new Milk(order);
System.out.println(" order加入一份牛奶费用=" + order.cost());
System.out.println(" order 加入一份牛奶描述=" + order.getDes());
// 3. order加入一份巧克力
order = new Chocolate(order);
System.out.println(" order 加入一份牛奶加入一份巧克力费用=" + order.cost());
System.out.println(" order加入一份牛奶加入一份巧克力描述=" + order.getDes());
System.out.println(" ++++++++++++++++++++++++");
Drink order2 = new DeCaf();
System.out.println(" order2无因咖啡―费用=" + order2.cost());
System.out.println(" order2无因咖啡描述=" + order2.getDes());
order2 = new Milk(order2);
System.out.println(" order2无因咖啡加入一份牛奶费用=" + order2.cost());
System.out.println(" order2无因咖啡加入一份牛奶描述=" + order2.getDes());
}
}
超类:
public abstract class Drink {
public String des; //描述
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
//计算费用的抽象方法子类来实现
public abstract float cost();
}
被装饰者基类:
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
装饰者基类:
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) {//组合
this.obj = obj;
}
@Override
public float cost() {
// getPrice自己价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
obj.getDes();//输出被装饰者的信息
return des + " " + getPrice() + "&& " + obj.getDes();
}
}
被装饰者具体类DeCaf:
public class DeCaf extends Coffee {
public DeCaf() {
setDes("无因咖啡");
setPrice(1.0f);
}
}
被装饰者具体类Espresso:
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.00f);
}
}
被装饰者具体类LongBlack:
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
被装饰者具体类ShortBlack:
public class ShortBlack extends Coffee {
public ShortBlack() {
setDes(" shortblack ");
setPrice(4.0f);
}
}
装饰者具体类Soy:
public class Soy extends Decorator {
public Soy(Drink obj) {
super(obj);
setDes("豆浆");
setPrice(1.5f);
}
}
装饰者具体类Milk:
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes("牛奶 ");
setPrice(2.0f);
}
}
装饰者具体类Chocolate:
//具体的 Decorator,这里就是调味品
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力");
setPrice(3.0f);//调味品的价格
}
}
7.5装饰者模式在JDK应用的源码分析
Java的IO结构,FilterInputStream就是一个装饰者
说明:
1 . InputStream是抽象类,类似我们前面讲的 Drink
2.FileInputStream是 InputStream子类,类似我们前面的 DeCaf, LongBlack
3.FilterInputStream是 InputStream子类:类似我们前面的Decorator修饰者
4.DataInputStream是FilterInputStream子类,具体的修饰者,类似前面的 Milk, Soy 等
5.FilterInputStream类有protected volatile InputStream in;即含被装饰者 装饰者中包含被装饰者
6.分析得出在jdk 的io体系中,就是使用装饰者模式
8.组合模式(树形结构)
8.1背景:
看一个学校院系展示需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
传统方案:
传统方案解决学校院系展示存在的问题分析:
1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
2)实际上我们的要求是﹔在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
3)解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。→组合模式
8.2组合模式介绍
- 组合模式(Composite Pattem),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
2)组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
3)这种类型的设计模式属于结构型模式。
4)组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vlslYswk-1630137889551)(images/037组合模式介绍.png)]
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件, Component可以是抽象类或者接口
- Leaf:在组合中表示叶子节点,叶子节点没有子节点
3)Composite :非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作,比如增加(add),删除。
8.3组合模式解决学校院系展示的应用实例应用实例要求
1)编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
2)思路分析和图解(类图)
解释:University 内组合List 实际为College实现 University 对 College的管理,College 内组合List 实际为Department实现 College 对 Department的管理
客户端:
public class Client {
public static void main(String[] args) {
//从大到小创建对象学校
OrganizationComponent university = new University("清华大学", "中国顶级大学");
//创建学院
OrganizationComponent computerCollege = new College("计算机学院", "计算机学院");
OrganizationComponent infoEngineercollege = new College("信息工程学院", "信息工程学院");
//创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", "软件工程不错"));
computerCollege.add(new Department("网络工程", "网络工程不错"));
computerCollege.add(new Department("计算机科学与技术", "计算机科学与技术是老牌的专业"));
infoEngineercollege.add(new Department("通信工程", "通信工程不好学"));
infoEngineercollege.add(new Department("信息工程", "信息工程好学"));
//将学院加入到学校
university.add(computerCollege);
university.add(infoEngineercollege);
university.print();
//infoEngineercollege.print();
}
}
OrganizationComponent:可以为抽象类,接口,类
public abstract class OrganizationComponent {
private String name;//名字
private String des;//说明
protected void add(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
//默认实现
throw new UnsupportedOperationException();
}
//构造器
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
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;
}
//方法print,做成抽象的,子类都需要实现
protected abstract void print();
}
College
import java.util.ArrayList;
import java.util.List;
public class College extends OrganizationComponent {
//List中存放的 Department
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
//构造器
public College(String name, String des) {
super(name, des);
}
//重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
//将来实际业务中,Colleage 的 add和University add不一定完全一样
organizationComponents.add(organizationComponent);
}
//重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
//print方法,就是输出
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");//遍历organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
University:
import java.util.ArrayList;
import java.util.List;
//University就是Composite ,可以管理College
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
//构造器
public University(String name, String des) {
super(name, des);
}
//重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
//重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法,就是输出 University包含的学院
@Override
protected void print() {
System.out.println("---------" + getName() + "--------------");//遍历organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
Department:
public class Department extends OrganizationComponent {
//没有集合
public Department(String name, String des) {
super(name, des);
}
//add , remove就不用写了,因为他是叶子节点
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println(getName());
}
}
8.4组合模式HashMap应用
说明:
1.Map就是—个抽象的拘建(类似我们的Component)
2.HashMap是一个中间的构建(Composite),实现/继承了相关方法1put, putall
3.Node是 HashMap的静态内部类,类似Leaf叶子节点,这里就没有put,putall1static class Node<K,V> implements Map.Entry<K,V>
8.5组合模式的注意事项和细节
1)简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
2)具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
3)方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
4)需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式.
5) 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
9.外观模式(抽一个更高的类,组合不同子系统,对子系统统一管理)
9.1影院管理项目
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:直接用遥控器:统筹各设备开关
开爆米花机,放下屏幕开投影仪开音响开DVD,选dvd去,拿爆米花,调暗灯光播放观影结束后,关闭各种设备
传统方式解决影院管理问题分析
1)在ClientTest 的 main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
2)不利于在ClientTest 中,去维护对子系统的操作
3)解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法ready, play, pause, end ),用来访问子系统中的一群接口
4)也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节==>外观模式
9.2外观模式基本介绍基本介绍
1)外观模式(Facade),也叫“过程模式“外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
2)外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
对类图说明(分类外观模式的角色)
1)外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
2)调用者(Client):外观接口的调用者
3)子系统的集合:指模块或者子系统,处理Facade对象指派的任务,他是功能的实际提供者
9.3外观模式解决影院管理
1)外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在pc上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
2)外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
3)示意图说明
家庭影院类图:
9.4外观模式代码
客户端
public class Client {
public static void main(String[] args) {
//这里直接调用。。很麻烦
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
外观类:
public class HomeTheaterFacade {
//定义各个子系统对象
private Popcorn popcorn;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
//构造器
public HomeTheaterFacade() {
super();
this.popcorn = Popcorn.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}
//操作分成4步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
dVDPlayer.on();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
screen.up();
projector.off();
dVDPlayer.off();
}
}
子系统1:
public class DVDPlayer {
//使用单例模式,使用饿汉式
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstanc() {
return instance;
}
private DVDPlayer() {
}
public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}
public void play() {
System.out.println(" dvd is playing ");
}
//....
public void pause() {
System.out.println(" dvd pause ..");
}
}
子系统2:
public class Popcorn {
private static Popcorn instance = new Popcorn();
private Popcorn() {
}
public static Popcorn getInstance() {
return instance;
}
public void on() {
System.out.println(" popcorn on ");
}
public void off() {
System.out.println(" popcorn ff ");
}
public void pop() {
System.out.println(" popcorn is poping ");
}
}
子系统3:
public class Projector {
private static Projector instance = new Projector();
private Projector() {
}
public static Projector getInstance() {
return instance;
}
public void on() {
System.out.println(" Projector on ");
}
public void off() {
System.out.println(" Projector ff ");
}
public void focus() {
System.out.println(" Projector is Projector ");
}
//...
}
子系统4:
public class Screen {
private static Screen instance = new Screen();
private Screen() {
}
public static Screen getInstance() {
return instance;
}
public void up() {
System.out.println(" Screen up ");
}
public void down() {
System.out.println(" Screen down ");
}
}
9.5 jdk源码应用
9.6外观模式细节
1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
2)外观模式对客户端与子系统的耦合关系–解耦,让子系统内部的模块更易维护和扩展
3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次
4)当系统需要进行分层设计时,可以考虑使用Facade模式
5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,维护为目的。
10.享元模式(内部状态和外部状态分开,内部状态共享)
10.1展示网站项目需求案例
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
1)有客户要求以新闻的形式发布
2)有客户人要求以博客的形式发布
3)有客户希望以微信公众号的形式发布
传统方案问题分析:
1)需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
2)解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
3)对于代码来说,由于是一份实例,维护和扩展都更加容易
4)上面的解决思路就可以使用享元模式来解决
10.2享元模式基本介绍
基本介绍
1)享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
2)常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
3)享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
4)享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
对类图的说明
对原理图的说明-即(模式的角色及职责)
-
FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态和内部状态(后面介绍)的接口或实现
-
ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
-
UnSharedConcreteFlyWeight是不可共享的角色,一般不会出现在享元工厂。
4)FlyWeightFactory享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法
10.3内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
1)享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
2)内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
3)外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
4)举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
10.4享元模式解决网站展现项目
类图:
10.5享元模式代码
客户端:
public class Client {
public static void main(String[] args) {
//创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
//客户要一个以新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
//客户要一个以博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User(" jack"));
//客户要一个以博客形式发布的网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User(" smith"));
//客户要一个以博客形式发布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
}
享元抽象类:
public abstract class WebSite {
public abstract void use(User user);//抽象方法
}
享元具体类:type:内部状态
//具体网站
public class ConcreteWebSite extends WebSite {
//共享的部分,内部状态
private String type = "";//网站发布的形式(类型)
//构造器
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + "在使用中..使用者是" + user.getName());
}
}
享元工厂:
import java.util.HashMap;
//网站工厂类,根据需要返回压一个网站
public class WebSiteFactory {
//集合,充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
//根据网站的类型,返回一个网站,如果没有就创建一个网站,并放入到池中,并返回
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
//就创建一个网站,并放入到池中
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
//获取网站分类的总数(池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
Uesr:外部状态,使用网站的人不同
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
10.6享元模式JDK源码应用
Integer.valueOf():如果i的值在-128 - 127之间,运用享元模式返回同一个对象。否则返回new 的一个新对象。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
10.7享元模式的注意事项和细节
1)在享元模式这样理解,“享”就表示共享,“元”表示对象
2)系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
3)用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
4)享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
5)享元模式提高了系统的复杂度。需要分离出内部状态和外部状态
6)使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
7)享元模式经典的应用场景是需要缓冲池的场景,比如String常量池、数据库连接池
11.代理模式(Proxy)(在目标对象的基础上,增强额外功能)
JDK动态代理:反射调用目标类的方法,生成的代理类实现的接口和目标类一致,通过执行代理类方法调用目标类,实现对目标类方法的增强
CGLib动态代理:构建的目标类子类对象,通过执行代理类方法调用目标类,实现对目标类方法的增强
11.1代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处
是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
2)被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
3)代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
4)代理模式示意图
11.2静态代理
11.2.1 静态代码模式的基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
11.2.2应用实例具体要求
1)定义一个接口:ITeacherDao
2)目标对象TeacherDAO实现接口ITeacherDAO
3)使用静态代理方式,就需要在代理对象TeacherDAOProxy 中也实现ITeacherDAO
4)调用的时候通过调用代理对象的方法来调用目标对象.
5)特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
11.2.3静态代理代码
客户端:
public class Client {
public static void main(String[] args) {
//l创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
//创建代理对象,同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
//通过代理对象,调用到被代理对象的方法
//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
接口:
//接口
public interface ITeacherDao {
void teach();//授课的方法
}
目标类:
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中。 。 。 。。");
}
}
代理类:
//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao target;//目标对象,通过接口来聚合
//构造器
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理完成某些操作。。。。。");//方法target.teach();
System.out.println("提交。。。。。");//方法
}
}
静态代理优缺点
1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
3)一旦接口增加方法,目标对象与代理对象都要维护
11.3 jdk动态代理(代理类:反射调用目标类的方法,生成的代理类实现的接口和目标类一致)
11.3.1动态代理模式的基本介绍
1)代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
3)动态代理也叫做:JDK代理、接口代理
JDK中生成代理对象的API
1)代理类所在包:java.lang.reflect.Proxy
2)JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)
11.3.2动态代理应用实例
应用实例要求:
将前面的静态代理改进成动态代理模式(即:JDK代理模式)
11.3.3JDK动态代理代码(目标类实现接口)
客户端:
public class Client {
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象,可以转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
//proxyInstance=class jdk.proxy1.$Proxy0
System.out.println( "proxyInstance=" + proxyInstance.getClass());
//proxyInstance=com.fuhong.designpattern.proxy.JDKProxy.TeacherDao@1a86f2f1
//System.out.println( "proxyInstance=" + proxyInstance);
//通过代理对象,调用目标对象的方法,并进行了方法增强
proxyInstance.sayHello("tom");
proxyInstance.teach();
}
}
jdk代理工厂:
//1.ClassLoader loader :指定当前目标对象使用的类加载器,获取加载器的方法固定
//2.Class<?>[]interfaces:目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h:事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactory {
//维护一个目标对象, Object
Object target;
//构造器,对target进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象生成一个代理对象
public Object getProxyInstance() {
//1.ClassLoader loader :指定当前目标对象使用的类加载器,获取加载器的方法固定
//2.Class<?>[]interfaces:目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h:事情处理,执行目标对象的方法时,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理开始 方法前置增强");
Object invoke = method.invoke(target, args);//反射机制调用目标类方法,invoke返回值
System.out.println("JDK动态代理结束 方法后置增强");
return invoke;
}
});
}
}
目标类接口:
//接口
public interface ITeacherDao {
void teach();
//授课方法
void sayHello(String name);
}
目标类:
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中.... ");
}
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
11.4CGLib动态代理(代理类:构建的目标类子类对象,基于方法拦截器实现)
11.4.1 Cglib代理模式的基本介绍
1)静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
2) Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展.有些书也将Cglib代理归属到动态代理。
3) Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如 Spring AOP,实现方法拦截
4)在AOP编程中如何选择代理模式:
1.目标对象需要实现接口,用JDK代理
2.目标对象不需要实现接口,用Cglib 代理
5) Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
11.4.2 Cglib代理模式实现步骤
1)需要引入cglib 的jar文件
2)在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException:
3)目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
11.4.3CGLib动态代理代码
客户端:proxyInstance.teach():执行代理对象的方法,触发intecept方法,从而实现对目标对象的调用
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao target = new TeacherDao();
//给目标对象,创建代理对象,可以转成ITeacherDao
TeacherDao proxyInstance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
//proxyInstance=class jdk.proxy1.$Proxy0
System.out.println( "proxyInstance=" + proxyInstance.getClass());
//proxyInstance=com.fuhong.designpattern.proxy.JDKProxy.TeacherDao@1a86f2f1
//System.out.println( "proxyInstance=" + proxyInstance);
//通过代理对象,调用目标对象的方法,并进行了方法增强
//执行代理对象的方法,触发intecept方法,从而实现对目标对象的调用
proxyInstance.teach();
}
}
拦截器类:注意参数解释
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//ProxyFactory 更是一个拦截器,拦截传入的目标类进行增强代理
public class ProxyFactory implements MethodInterceptor {
//维护一个目标对象
private Object target;
//构造器,传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();//通过创建的enhancer对象来构建代理对象
enhancer.setSuperclass(target.getClass());//把目标类设置为父类
enhancer.setCallback(this); //设置拦截器
return enhancer.create();//创建子类代理对象返回
}
/**
* 拦截传入的目标类方法进行增强
* @param o 代理对象本身
* @param method 具体调用的方法
* @param objects 调用方法的参数
* @param methodProxy MethodProxy methodProxy是当前代理对象执行的方法的一个代理,也就是MethodProxy methodProxy是第二个参数Method method的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理模式~~开始");
System.out.println("target目标对象与参数o是否相等:"+(target==o));
//class com.fuhong.designpattern.proxy.CGLibProxy.TeacherDao$$EnhancerByCGLIB$$fa63df7c
System.out.println(o.getClass());
Object invoke = method.invoke(target, objects);//传入参数必须为目标类对象target,o为代理类对象
System.out.println("Cglib代理模式~~提交");
return invoke;
}
}
接口:
public class TeacherDao {
public String teach() {
System.out.println("老师授课中 ,我是cglib 代理,不需要实现接口");
return "hello";
}
}
11.5几种常见的代理模式介绍—几种变体
1)防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
2)缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
3)远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
4)同步代理:主要使用在多线程编程中,完成多线程间同步工作
12、模板方法模式(模板方法里面定义大致流程,具体实现可默认实现可子类具体实现)
12.1模板方法模式基本介绍基本介绍
1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
2)简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
3)这种类型的设计模式属于行为型模式。
对原理类图的说明-即(模板方法模式的角色及职责)
- AbstractClass 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法 operationr2,3,4
- ConcreteClass实现抽象方法operationr2,3,4,以完成算法中特点子类的步骤
12.2模板方法模式解决豆浆制作问题
1)应用实例要求
1.编写制作豆浆的程序,说明如下:
制作豆浆的流程选材—>添加配料—>浸泡—>放到豆浆机打碎通过添加不同的配料,可以制作出不同口味的豆浆
选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的(红豆、花生豆浆。)
2)思路分析和图解(类图)
12.3模板方法代码
模板抽象类:
//抽象类,表示豆浆
public abstract class SoyaMilk {
//模板方法, make,模板方法可以做成final ,不让子类去覆盖.
final void make() {
select();
addCondiments();
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆");
}
//添加不同的配料,抽象方法,子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步,黄豆和配料开始浸泡,需要3小时");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎");
}
}
花生豆浆:
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("加入上好的花生 ");
}
}
红豆豆浆:
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("加入上好的红豆 ");
}
}
客户端:
public class Client {
public static void main(String[] args) {
//制作红豆豆浆
System.out.println(" ----制作红豆豆浆----");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("----制作花生豆浆----");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}
12.4模板方法模式的钩子方法(钩子方法,子类实现,如果子类重写,就调用子类,子类不从写 啥也不干)
- 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
2)还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
新增一个钩子方法:boolean去判断 子类空实现也行
//抽象类,表示豆浆
public abstract class SoyaMilkImprove {
//模板方法, make,模板方法可以做成final ,不让子类去覆盖.
final void make() {
select();
if(customerWantCondiments()){
addCondiments();
}
soak();
beat();
}
//选材料
void select() {
System.out.println("第一步:选择好的新鲜黄豆");
}
//添加不同的配料,抽象方法,子类具体实现
abstract void addCondiments();
//浸泡
void soak() {
System.out.println("第三步,黄豆和配料开始浸泡,需要3小时");
}
void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机去打碎");
}
//钩子方法,决定是否需要添加配料
boolean customerWantCondiments() {
return true;
}
}
12.5模板方法模式在 Spring框架应用的源码分析
- Spring IOC容器初始化时运用到的模板方法模式
2)代码分析+角色分析+说明类图
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
//模板方法
void refresh() throws BeansException, IllegalStateException;
}
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
//模板方法
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
//这个方法里面定义了抽象方法,由子类实现
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
//钩子方法,子类实现,如果子类重写,就调用子类,子类不从写 啥也不干
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
//钩子方法,子类空实现,不干事儿
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
}
类图:
12.6模板方法模式的注意事项和细节
1)基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
4)该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5)一般模板方法都加上 final关键字,防止子类重写模板方法.
6)模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
13.命令模式(发起请求的对象和执行请求的对象解耦)
13.1简介
1)命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
2)命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
3)在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
4)通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象
13.2命令模式的原理类图
对原理类图的说明-即(命名模式的角色及职责)
-
Invoker是调用者角色
-
Command:是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
-
Receiver:接受者角色,知道如何实施和执行一个请求相关的操作
4)ConcreteCommand:将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
13.3命令模式解决智能生活项目
13.4实现代码
命令抽象类:
public interface Command {
//执行动作(操作)
public void execute();
//撤销动作(操作)
public void undo();
}
电灯关命令类:
public class LightOffCommand implements Command {
//聚合LightReceiver
LightReceiver light;
//构造器
public LightOffCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
//调用接收者的方法
light.off();
}
@Override
public void undo() {
//调用接收者的方法
light.on();
}
}
电灯开命令类:
public class LightOnCommand implements Command {
//聚合LightReceiver
LightReceiver light;
//构造器
public LightOnCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
//调用接收者的方法
light.on();
}
@Override
public void undo() {
//调用接收者的方法
light.off();
}
}
电灯命令接受类,聚合于命令类:
public class LightReceiver {
public void on() {
System.out.println("电灯打开了..");
}
public void off() {
System.out.println("电灯关闭了..");
}
}
空命令类:
/**
* 没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,对象什么都不做其实,这样是一种设计模式,可以省掉对空判断
*/
public class NoCommand implements Command {
@Override
public void execute() {
}
@Override
public void undo() {
}
}
电视关命令类:
public class TVOffCommand implements Command {
//聚合TVReceiver
TVReceiver tv;
//构造器
public TVOffCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
//调用接收者的方法
tv.off();
}
@Override
public void undo() {
//调用接收者的方法
tv.on();
}
}
电视开命令类:
public class TVOnCommand implements Command {
//聚合TVReceiver
TVReceiver tv;
//构造器
public TVOnCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
//调用接收者的方法
tv.on();
}
@Override
public void undo() {
//调用接收者的方法
tv.off();
}
}
电视命令接受类:
public class TVReceiver {
public void on() {
System.out.println("电视机打开了..");
}
public void off() {
System.out.println("电视机关闭了.. ");
}
}
远程控制类:用数组记录开关
public class RemoteController {
//开按钮的命令数组
Command[] onCommands;
Command[] offCommands;
//执行撤销的命令
Command undoCommand;
//构造器,完成对按钮初始化
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
// 给我们的按钮设置你需要的命令
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
//按下开按钮
public void onButtonWasPushed(int no) {
// no 0找到你按下的开的按钮,并调用对应方法
onCommands[no].execute();
//记录这次的操作,用于撤销
undoCommand = onCommands[no];
}
//按下关按钮
public void offButtonWasPushed(int no) {
// no 0
//找到你按下的关的按钮,并调用对应方法
offCommands[no].execute();
//记录这次的操作,用于撤销
undoCommand = offCommands[no];
}
//按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
}
客户端:
public class Client {
public static void main(String[] args) {
//使用命令设计模式,完成通过遥控器,对电灯的操作
//创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//需要一个遥控器
RemoteController remoteController = new RemoteController();
//给我们的遥控器设置命令,比如 no=0是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("--------按下灯的开按钮----------");
remoteController.onButtonWasPushed(0);
System.out.println("-------按下灯的关按钮------.----");
remoteController.offButtonWasPushed(0);
System.out.println(" --------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
System.out.println("-—=三--===使用遥控器操作电视机———-—三自自——");
TVReceiver tvReceiver = new TVReceiver();
TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);
//给我们的遥控器设置命令,比如 no=1是电视机的开和关的操作
remoteController.setCommand(1, tvOnCommand, tvOffCommand);
System.out.println("--------按下电视机的开按钮--- ");
remoteController.onButtonWasPushed(1);
System.out.println(" --------按下电视机的关按钮----------");
remoteController.offButtonWasPushed(1);
System.out.println("-------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
}
}
13.5命令模式在 Spring框架JdbcTemplate应用的源码分析
- Spring 框架的JdbcTemplate就使用到了命令模式
2)代码分析
StatementCallback接口 ,类似命令接口(Command)
class QueryStatementCallback implements StatementCallback, SqIProvider ,匿名内部类,实现了命令接口同时也充当命令接收者命令调用者是JdbcTemplate,其中 execute(StatementCallback action)方法中,调用action.doInStatement方法. 不同的实现StatementCallback 接口的对象,对应不同的doInStatemnt 实现逻辑
●另外实现 StatementCallback命令接口的子类还有QueryStatementCallback、
13.6命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
2)容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
3)容易实现对请求的撤销和重做
4)命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
5)空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
6)命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制
14、访问者模式(解决数据结构和操作耦合性问题,Element在这种模式下是不应该改变的,改变的是Active)
将Active 放进Person,在Person中反向去访问Active,双分派.
本例:人的种类只分为两种,男女;在操作类中将男女结果写出来。SUCCESS 与FAIL实现active,MAN,WOMAN调用active的中man/women的getResult方法需要把自己传进去。得到的就是传入对象的操作结果。
这样:对人的扩展局限,但对状态的扩展符合ocp
14.1完成测评系统需求
将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)
传统方式的问题分析:
1)如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则,不利于维护
2)扩展性不好,比如增加了新的人员类型,或者管理方法,都不好做
3)引出我们会使用新的设计模式–访问者模式
14.2 访问者模式基本介绍
- 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
2)主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
3)访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
4)访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
对原理类图的说明-即(访问者模式的角色及职责)
-
Visitor是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
2)ConcreteVisitor :是一个具体的访问值实现每个有Visitor声明的操作,是每个操作实现的部分. -
ObjectStructure 能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
-
Element定义一个accept方法,接收一个访问者对象
5)ConcreteElement为具体元素,实现了accept方法
14.3访问者模式应用实例
应用实例要求
- 将人分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同
的种类,比如成功、失败等),请使用访问者模式来说实现
访问者抽象类:成功,失败,待定
public abstract class Action {
//得到男性的测评
public abstract void getManResult(Man man);
//得到女的测评
public abstract void getWomanResult(Woman woman);
}
状态:成功,person调用传入对象
public class Success extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男人给的评价该歌手很成功!");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女人给的评价该歌手很成功!");
}
}
状态:失败,person调用传入对象
public class Fail extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男人给的评价该歌手失败!");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女人给的评价该歌手失败!");
}
}
状态:等待,person调用传入对象
public class Wait extends Action {
@Override
public void getManResult(Man man) {
System.out.println("男人给的评价是该歌手待定..");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println("女人给的评价是该歌手待定..");
}
}
访问者访问的抽象类:
public abstract class Person {
//提供一个方法,让访问者可以访问
public abstract void accept(Action action);
}
男人类:
public class Man extends Person {
@Override
public void accept(Action action) {
action.getManResult(this);
}
}
女人类:
//说明
//1.这里我们使用到了双分派,即首先在客户端程序中,将具体状态作为参数传递Woman 中(第一次分派)
// 2.然后Woman类调用作为参数的"具体方法”中方法getWomanResult,同时将自己(this)作为参数
// 传入,完成第二次的分派
public class Woman extends Person {
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
处理数据的类:
import java.util.LinkedList;
import java.util.List;
//数据结构,管理很多人(Man , Woman)
public class ObjectStructure {
//维护了一个集合
private List<Person> persons = new LinkedList();
//增加到list
public void attach(Person p) {
persons.add(p);
}
//移除
public void detach(Person p) {
persons.remove(p);
}
//显示测评情况
public void display(Action action) {
for (Person p : persons) {
p.accept(action);
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//成功
Success success = new Success();
objectStructure.display(success);
System.out.println("++++++++++++++++++++++++++");
Fail fail = new Fail();
objectStructure.display(fail);
System.out.println("-—--——=给的是待定的测评==--—-——");
Wait wait = new Wait();
objectStructure.display(wait);
}
}
以上述实例为例,假设我们要添加一个Wait 的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码。
14.4访问者模式的注意事项和细节
优点:
1)访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
2)访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
缺点
1)具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难
2)违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
3) 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的
15、迭代器模式(客户端与遍历方式解耦)
15.1具体的需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
15.2传统的方式的问题分析
1.将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
2.实际上我们的要求是﹔在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的遍历的操作
3)解决方案:==》迭代器模式
15.3迭代器模式基本介绍
1)迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
2)如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
3)迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。
迭代器模式类图:
对原理类图的说明-即(迭代器模式的角色及职责)
-
lterator :迭代器接口,是系统提供,含义hasNext, next, remove
-
Concretelterator :具体的迭代器类,管理迭代
-
Aggregate :一个统一的聚合接口,将客户端和具体聚合解耦
4)ConcreteAggreage:具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合
5)Client :客户端,通过Iterator和Aggregate依赖子类
15.4迭代器模式实现
学院抽象类:
import java.util.Iterator;
public interface College {
public String getName();
//增加系的方法
public void addDepartment(String name, String desc);
//返回一个迭代器,遍历
public Iterator createlterator();
}
ComputerCollege学院类:
import java.util.Iterator;
public class ComputerCollege implements College {
Department[] departments;
int numOfDepartment = 0;//保存当前数组的对象个数
public ComputerCollege() {
departments = new Department[5];
addDepartment("Java专业", "Java专业");
addDepartment("PHP专业", "PHP专业");
addDepartment("大数据专业", "大数据专业");
}
@Override
public String getName() {
return "计算机学院";
}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departments[numOfDepartment] = department;
numOfDepartment += 1;
}
@Override
public Iterator createlterator() {
return new ComputerCollegelterator(departments);
}
}
InfoCollege学院类:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class InfoCollege implements College {
List<Department> departmentList;
public InfoCollege() {
departmentList = new ArrayList<Department>();
addDepartment("信息安全专业", "信息安全专业");
addDepartment("网络安全专业", "网络安全专业");
addDepartment("服务器安全专业", "服务器安全专业 ");
}
@Override
public String getName() {
return "信息工程学院";
}
@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departmentList.add(department);
}
@Override
public Iterator createlterator() {
return new InfoColleagelterator(departmentList);
}
}
学院下的系:
//系
public class Department {
private String name;
private String desc;
public Department(String name, String desc) {
super();
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
ComputerCollegelterator迭代器类:
import java.util.Iterator;
public class ComputerCollegelterator implements Iterator {
//这里我们需要Department是以怎样的方式存放→数组
Department[] departments;
int position = 0;//遍历的位置
public ComputerCollegelterator(Department[] departments) {
this.departments = departments;
}
//判断是否还有下一个元素
@Override
public boolean hasNext() {
if (position >= departments.length || departments[position] == null) {
return false;
}
return true;
}
@Override
public Object next() {
Department department = departments[position];
position += 1;
return department;
}
//删除的方法,默认空实现
public void remove() {
}
}
InfoColleagelterator迭代器类:
import java.util.Iterator;
import java.util.List;
public class InfoColleagelterator implements Iterator {
List<Department> departmentList;
//信息工程学院是以List方式存放系
int index = -1;//索引
public InfoColleagelterator(List<Department> departmentList) {
this.departmentList = departmentList;
}
//判断list中还有没有下一个元素@Override
public boolean hasNext() {
return index < departmentList.size() - 1;
}
@Override
public Object next() {
Department department = departmentList.get(index);
index += 1;
return department;
}
//空实现remove
public void remove() {
}
}
输出实现类:
import java.util.Iterator;
import java.util.List;
public class OutPutImpl {
//学院集合
List<College> collegeList;
public OutPutImpl(List<College> collegeList) {
this.collegeList = collegeList;
}
//遍历所有学院,然后调用printDepartment输出各个学院的系
public void printCollege() {
//从collegeList 取出所有学院, Java中的 List已经实现Iterator
Iterator<College> iterator = collegeList.iterator();
while (iterator.hasNext()) {
//取出一个学院
College college = iterator.next();
System.out.println("=== " + college.getName() + "--=--");
printDepartment(college.createlterator());//得到对应迭代器
}
}
public void printDepartment(Iterator iterator) {
while (iterator.hasNext()) {
Department d = (Department) iterator.next();
System.out.println(d.getName());
}
}
}
客户端:
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//创建学院
List<College> collegeList = new ArrayList<College>();
ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();
collegeList.add(computerCollege);
collegeList.add(infoCollege);
OutPutImpl outPutImpl = new OutPutImpl(collegeList);
outPutImpl.printCollege();
}
}
15.5迭代器模式在JDK-ArrayList集合应用的源码分析
- JDK的ArrayList集合中就使用了迭代器模式
2)类图
3)对类图的角色分析和说明
内部类ltr充当具体实现迭代器Iterator的类,作为ArrayList内部类
List就是充当了聚合接口,含有一个iterator()方法,返回一个迭代器对象
ArrayList是实现聚合接口List 的子类,实现了iterator()
Iterator接口系统提供
迭代器模式解决了不同集合(ArrayList ,LinkedList)统一遍历问题
15.6迭代器模式的注意事项和细节
优点
1)提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
2)隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
3)提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
4)当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式
缺点:
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类
16、观察者模式(通过一个类管理观察者)
16.1天气预报项目需求,具体要求如下:
1I)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。
2)需要设计开放型API,便于其他第三方也能接入气象站获取数据。
3)提供温度、气压和湿度的接口
4)测量数据更新时,要能实时的通知给第三方
16.2天气预报设计方案1-普通方案
WeatherData类:
传统的设计方案
通过对气象站项目的分析,我们可以初步设计出一个WeatherData类
订阅气象信息的网站:
/*显示当前天气情况(可以理解成是气象站自己的网站)
*/
public class CurrentConditions {
//温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
//更新天气情况,是由 WeatherData来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
//显示
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("**Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
气象信息类:
/**
* 类是核心
* 1.包含最新的天气情况信息*2.含有CurrentConditions对象
* 3.当数据有更新时,就主动的调用CurrentConditions 对象update方法(含display),这样他们(接入方)就看到最新的信息
*/
public class WeatherData {
private float temperatrue;
private float pressure;
private float humidity;
private CurrentConditions currentConditions;//加入新的第三方
public WeatherData(CurrentConditions currentConditions) {
this.currentConditions = currentConditions;
}
public float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
//调用接入方的update
currentConditions.update(getTemperature(), getPressure(), getHumidity());
}
//当数据有更新时,就调用setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用dataChange,将最新的信息推送给接入方currentConditions
dataChange();
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建接入方 currentConditions
CurrentConditions currentConditions = new CurrentConditions();
//创建WeatherData 并将接入方currentConditions 传递到WeatherData 中
WeatherData weatherData = new WeatherData(currentConditions);
//更新天气情况
weatherData.setData(30, 150, 40);
//天气情况变化
System.out.println(" +++++++++++++++++++++++++++++++++++");
weatherData.setData(40, 160, 20);
}
}
问题分析
1)其他第三方接入气象站获取数据的问题
2)无法在运行时动态的添加第三方(新浪网站)
3)违反ocp原则→观察者模式
在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange,不利于维护,也不是动态加入
public void dataChange() {
currentConditions.update(getTemperature(), getPressure(), getHumidity());
16.3观察者模式原理
1)观察者模式类似订牛奶业务
2)奶站/气象局:Subject
3)用户/第三方网站: Observer
Subject:登记注册、移除和通知
1) registerObserver注册
2) removeObserver移除
3) notifyObservers())通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定
Observer:接收输入
观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject
通知Observer变化,比如这里的奶站是Subject,是1的一方。用户时Observer,是多的一方。
16.4观察者模式解决气象站问题
观察者接口类:
//观察者接口,有观察者来实现
public interface Observer {
public void update(float temperature, float pressure, float humidity);
}
观察者1:
/*显示当前天气情况(可以理解成是气象站自己的网站)
*/
public class CurrentConditions implements Observer {
//温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
//更新天气情况,是由 WeatherData来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
//显示
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("**Today mPressure: " + pressure + "***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}
观察者2:
/*显示当前天气情况(可以理解成是气象站自己的网站)
*/
public class BaiDuSite implements Observer{
//温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;
//更新天气情况,是由 WeatherData来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}
//显示
public void display() {
System.out.println("***baiduToday mTemperature: " + temperature + "***");
System.out.println("**baiduToday mPressure: " + pressure + "***");
System.out.println("***baiduToday mHumidity: " + humidity + "***");
}
}
气象站:
//接口,让 WeatherData来实现
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
具体类:观察者注册
import java.util.ArrayList;
/**
* 类是核心
* 类是核心
* 1.包含最新的天气情况信息
* 2.含有观察者集合,使用ArrayList 管理
* 3.当数据有更新时,就主动的调用ArrayList,通知所有的(接入方)就看到最新的信息
*/
public class WeatherData implements Subject {
private float temperatrue;
private float pressure;
private float humidity;
//观察者集合
private ArrayList<Observer> observers;
public WeatherData() {
observers = new ArrayList<Observer>();
}
public float getTemperature() {
return temperatrue;
}
public float getPressure() {
return pressure;
}
public float getHumidity() {
return humidity;
}
public void dataChange() {
//调用接入方的update
notifyObservers();
}
//当数据有更新时,就调用setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
//调用dataChange,将最新的信息推送给接入方currentConditions
dataChange();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
if (observers.contains(o)){
observers.remove(o);
}
}
//遍历所由观察者 通知
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this.temperatrue, this.pressure, this.humidity);
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建一个 WeatherData
WeatherData weatherData = new WeatherData();
//创建观察者
Observer currentConditions = new CurrentConditions();
Observer baiduSite = new BaiDuSite();
//注册到weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);
//测试
System.out.println("通知各个注册的观察者,看看信息");
weatherData.setData(10f, 100f, 30.3f);
weatherData.removeObserver(currentConditions);
//测试
System.out.println();
System.out.println("通知各个注册的观察者,看看信息");
weatherData.setData(10f, 100f, 30.3f);
}
}
观察者模式的好处:
1)观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则。
16.5观察者模式在Jdk应用的源码分析
- Jdk的Observable类就使用了观察者模式
2)代码分析+模式角色分析
3)模式角色分析
Observable的作用和地位等价于我们前面讲过Subject
Observable是类,不是接口,类中已经实现了核心的方法 ,即管理Observer 的方法、 add… delete … notif. …Observer的作用和地位等价于我们前面讲过的Observer,有 update
Observable 和 Observer 的使用方法和前面讲过的一样,只是Observable是类,通过继承来实现观察者模式
17、中介者模式(调用麻烦问题,封装中介者对象解决对象之间相互调用问题)
将所有同时对象注册到中介者对象,同事对象向中介者发出消息,由中介者对象负责所有同事对象之间的协调工作
17.1智能家庭项目:
1)智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等
2)主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
传统方案解决智能家庭管理问题
传统的方式的问题分析:
1)当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
2)各个电器对象彼此联系,你中有我,我中有你,不利于松耦合.
3)各个电器对象之间所传递的消息(参数),容易混乱
4)当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想――考虑中介者模式
17.2中介者模式基本介绍
基本介绍
1)中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
2)中介者模式属于行为型模式,使代码易于维护
3) 比如MVC模式,C(Controller控制器)是M(Model 模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用
中介者模式类图:
对原理类图的说明-即(中介者模式的角色及职责)
-
Mediator就是抽象中介者,定义了同事对象到中介者对象的接口
-
Colleague是抽象同事类
-
ConcreteMediator具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理HashMap,并接受某个同事对象消息,完成相应的任务
4)ConcreteColleague具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事类的行为(方法),但是他们都依赖中介者对象
17.3中介者模式应用实例-智能家庭管理
1)应用实例要求
完成前面的智能家庭的项目,使用中介者模式
中介者抽象类:
public abstract class Mediator {
//将给中介者对象,加入到集合中
public abstract void register(String colleagueName, Colleague colleague);
//接收消息,具体的同事对象发出
public abstract void getMessage(int stateChange, String colleagueName);
public abstract void sendMessage();
}
中介者具体类:
import java.util.HashMap;
//具体的中介者类
public class ConcreteMediator extends Mediator {
//集合,放入所有的同事对象
private HashMap<String, Colleague> colleagueMap;
private HashMap<String, String> interMap;
public ConcreteMediator() {
colleagueMap = new HashMap<String, Colleague>();
interMap = new HashMap<String, String>();
}
@Override
public void register(String colleagueName, Colleague colleague) {
colleagueMap.put(colleagueName, colleague);
if (colleague instanceof Alarm) {
interMap.put("Alarm", colleagueName);
} else if (colleague instanceof CoffeeMachine) {
interMap.put("CoffeeMachine", colleagueName);
} else if (colleague instanceof TV) {
interMap.put("TV", colleagueName);
} else if (colleague instanceof Curtains) {
interMap.put("Curtains", colleagueName);
}
}
//1.根据得到消息,完成对应任务
//2.中介者在这个方法,协调各个具体的同事对象,完成任务
@Override
public void getMessage(int stateChange, String colleagueName) {
//处理闹钟发出的消息
if (colleagueMap.get(colleagueName) instanceof Alarm) {
if (stateChange == 0) {
((CoffeeMachine) (colleagueMap.get(interMap
.get("CoffeeMachine")))).startCoffee();
((TV) (colleagueMap.get(interMap.get("TV")))).startTv();
} else if (stateChange == 1) {
((TV) (colleagueMap.get(interMap.get("TV")))).stopTv();
}
} else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
((Curtains) (colleagueMap.get(interMap.get("Curtains"))))
.UpCurtains();
} else if (colleagueMap.get(colleagueName) instanceof TV) {
//如果TV发现消息
} else if (colleagueMap.get(colleagueName) instanceof Curtains) {
//如果是以窗帘发出的消息,这里处理...
}
}
@Override
public void sendMessage() {
}
}
同事抽象类:
//同事抽象类
public abstract class Colleague {
private Mediator mediator;
public String name;
public Colleague(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public Mediator getMediator() {
return this.mediator;
}
public abstract void sendMessage(int stateChange);
}
同事具体类:
//具体的同事类
public class Alarm extends Colleague {
//构造器
public Alarm(Mediator mediator, String name) {
super(mediator, name);
//在创建Alarm同事对象时,将自己放入到ConcreteMediator 对象中[集合]
mediator.register(name, this);
}
public void sendAlarm(int stateChange) {
sendMessage(stateChange);
}
@Override
public void sendMessage(int stateChange) {
//调用的中介者对象的getMessage
this.getMediator().getMessage(stateChange, this.name);
}
}
同事具体类:
public class CoffeeMachine extends Colleague {
public CoffeeMachine(Mediator mediator, String name) {
super(mediator, name);
mediator.register(name, this);
}
@Override
public void sendMessage(int stateChange) {
this.getMediator().getMessage(stateChange, this.name);
}
public void startCoffee() {
System.out.println(" It's time to startcoffee!");
}
public void finishCoffee() {
System.out.println(" After 5 minutes!");
System.out.println("Coffee is ok!");
sendMessage(0);
}
}
同事具体类:
public class Curtains extends Colleague {
public Curtains(Mediator mediator, String name) {
super(mediator, name);
mediator.register(name, this);
}
@Override
public void sendMessage(int stateChange) {
this.getMediator().getMessage(stateChange, this.name);
}
public void UpCurtains() {
System.out.println("I am holding Up Curtains!");
}
}
同事具体类:
public class TV extends Colleague {
public TV(Mediator mediator, String name) {
super(mediator, name);
mediator.register(name, this);
}
@Override
public void sendMessage(int stateChange) {
this.getMediator().getMessage(stateChange, this.name);
}
public void startTv() {
System.out.println("It's time to StartTv!");
}
public void stopTv() {
System.out.println("StopTv!");
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建一个中介者对象
Mediator mediator = new ConcreteMediator();
//创建Alarm并且加入到ConcreteMediator对象的HashMap
Alarm alarm = new Alarm(mediator, "alarm");
//创建了CoffeeMachine对象,并惇且加入到ConcreteMediator对象的HashMap
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine");
//创建Curtains ,并―且加入到 ConcreteMediator 对象的HashMap
Curtains curtains = new Curtains(mediator, "curtains");
TV tV = new TV(mediator, "TV");
//让闹钟发出消息
alarm.sendAlarm(0);
coffeeMachine.finishCoffee();
alarm.sendAlarm(1);
}
}
17.4中介者模式的注意事项和细节
1)多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
2)减少类间依赖,降低了耦合,符合迪米特原则
3)中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
4)如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
18、备忘录模式(用于恢复角色特定时间的状态)
18.1游戏角色状态恢复问题
游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
传统方案解决游戏角色恢复:
传统的方式的问题分析:
1)一个对象,就对应一个保存对象状态的对象,这样当我们游戏的对象很多时,不利于管理,开销也很大.
2)传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
3)解决方案:→备忘录模式
18.2备忘录模式基本介绍
基本介绍
1)备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态2
2)可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
3)备忘录模式属于行为型模式
对原理类图的说明-即(备忘录模式的角色及职责)
- originator:对象(需要保存状态的对象)
2)Memento :备忘录对象,负责保存好记录,即 Originator内部状态 - Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率
4)说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要要HashMap <String,集合>
状态类:
public class Memento {
private String state;
//构造器
public Memento(String state) {
super();
this.state = state;
}
public String getState() {
return state;
}
}
角色类:
public class Originator {
private String state;//状态信息
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//编写一个方法,可以保存一个状态对象Memento
// 因此编写一个方法,返回Memento
public Memento saveStateMemento() {
return new Memento(state);
}
//通过备忘录对象,恢复状态
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
保存某个角色的多个状态类:
import java.util.ArrayList;
import java.util.List;
public class Caretaker {
//在 List 集合中会有很多的备忘录对象
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento memento) {
mementoList.add(memento);
}
//获取到第index 个 Originator的备忘录对象(即保存状态)
public Memento get(int index) {
return mementoList.get(index);
}
}
客户端:
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("状态#1攻击力100 ");
//保存了当前的状态
caretaker.add(originator.saveStateMemento());
originator.setState("状态#2攻击力80 ");
caretaker.add(originator.saveStateMemento());
originator.setState("状态03攻击力50 ");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是=" + originator.getState());
//希望得到状态1,将originator恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1,当前的状态是");
System.out.println("当前的状态是=" + originator.getState());
}
}
18.3游戏角色恢复状态实例
1)应用实例要求
游戏角色有攻击力和防御力,在大战 Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态
2)思路分析和图解(类图)
状态类:
public class Memento {
//攻击力
private int vit;
//防御力
private int def;
public Memento(int vit, int def) {
super();
this.vit = vit;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
游戏角色:
public class GameRole {
private int vit;
private int def;
//创建Memento ,即根据当前的状态得到Memento
public Memento createMemento() {
return new Memento(vit, def);
}
//从备忘录对象,恢复GameRole 的状态
public void recoverGameRoleFromMemento(Memento memento) {
this.vit = memento.getVit();
this.def = memento.getDef();
}
//显示当前游戏角色的状态
public void display() {
System.out.println("游戏角色当前的攻击力:" + this.vit + "防御力:" + this.def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
守护者类:
import java.util.ArrayList;
import java.util.HashMap;
//守护者对象,保存游戏角色的状态
public class Caretaker {
//如果只保存一次状态
private Memento memento;//对GameRole 保存多次状态
private ArrayList<Memento> mementos;//对多个游戏角色保存多个状态
private HashMap<String, ArrayList<Memento>> rolesMementos;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
客户端调用:
public class Client {
public static void main(String[] args) {
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.setVit(100);
gameRole.setDef(100);
System.out.println("和 boss大战前的状态");
gameRole.display();
//把当前状态保存caretaker
Caretaker caretaker = new Caretaker();
caretaker.setMemento(gameRole.createMemento());
System.out.println("和 boss大战~~~");
gameRole.setDef(30);
gameRole.setVit(30);
gameRole.display();
System.out.println("大战后,使用备忘录对象恢复到站前");
gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
System.out.println("恢复后的状态");
gameRole.display();
}
}
18.4注意细节
1)给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
2)实现了信息的封装,使得用户不需要关心状态的保存细节
3)如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意
4)适用的应用场景:1、后悔药。2、打游戏时的存档。3、Windows里的 ctri+z。4、IE中的后退。5、数据库的事务管理
5)为了节约内存,备忘录模式可以和原型模式配合使用
19、解释器模式(对表达式进行解释)
19.1四则运算问题
通过解释器模式来实现四则运算,如计算a+b-c的值,具体要求
1)先输入表达式的形式,比如 a+b+c-d+e,要求表达式的字母不能重复
2)在分别输入a ,b, c, d,e 的值
3)最后求出结果:如图
19.2 传统方案解决四则运算问题分析
- 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
2)问题分析:如果加入新的运算符,比如*/(等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,不够清晰.
3)解决方案:可以考虑使用解释器模式,即:表达式>解释器(可以有多种)→结果
19.3 解释器模式基本介绍
基本介绍
1)在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
2)解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
3)应用场景
应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树,一些重复出现的问题可以用一种简单的语言来表达
类图:
对原理类图的说明-即(解释器模式的角色及职责)
-
Context:是环境角色,含有解释器之外的全局信息.
-
AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
-
TerminalExpression:为终结符表达式,实现与文法中的终结符相关的解释操作
4)NonTermialExpression:为非终结符表达式,为文法中的非终结符实现解释操作.
5)说明:输入Context he TerminalExpression信息通过Client输入即可
19.4解释器模式来实现四则
1)应用实例要求
通过解释器模式来实现四则运算,如计算a+b-c的值
2)类图分析
代码:
抽象类表达式:+ - 1 2等
/**
* 抽象类表达式,通过HashMap键值对,可以获取到变量的值
*/
public abstract class Expression {
// a+b- c
//解释公式和数值, key 就是公式(表达式)参数[a,b,c], value就是就是具体值llHashMap {a=10, b=20}
public abstract int interpreter(HashMap<String, Integer> var);
}
抽象运算符号类:+ -
/**
* 抽象运算符号解析器这里,每个运算符号,都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类*
*/
public class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
//因为SymbolExpression是让其子类来实现,因此interpreter是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}
减号表达式类:
import java.util.HashMap;
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
//求出left和 right表达式相减后的结果
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
加号表达式类:
/**
* 加法解释器
*/
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//处理相加
//var仍然是{a=10,b=20}
public int interpreter(HashMap<String, Integer> var) {
//super.left.interpreter(var) ;返回left表达式对应的值 a= 10
//super.right.interpreter(var);返回right 表达式对应值 b=20
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
变量表达式:
/**
* 变量的解释器
*/
public class VarExpression extends Expression {
private String key; // key=a,key=b,key=c
public VarExpression(String key) {
this.key = key;
}
// var 就是{a=10, b=20}
// interpreter根据变量名称,返回对应值
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
计算类:
import java.util.HashMap;
import java.util.Stack;
public class Calculator {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr) { // expStr = a+b
//安排运算先后顺序
Stack<Expression> stack = new Stack<>();//表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a,+, b]
Expression left = null;
Expression right = null;
//遍历我们的字符数组,即遍历[a,+, b]ll针对不同的情况,做处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+'://
left = stack.pop();// 从stack 取出left =>"a"
right = new VarExpression(String.valueOf(charArray[++i]));//取出右表达式"b"
stack.push(new AddExpression(left, right));//然后根据得到left和 right 构建AddExpresson加入 stack
break;
case '-'://
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
//如果是一个Var 就创建要给 VarExpression对象,并push到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
//当遍历完整个charArray数组后,stack 就得到最后Expression
this.expression = stack.pop();
}
public int run(HashMap<String, Integer> var) {
//最后将表达式a + b和 var = {a = 10, b = 20}
//然后传递给expression 的interpreter进行解释执行
return this.expression.interpreter(var);
}
}
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
public class ClientTest {
public static void main(String[] args) throws IOException {
String expStr = getExpStr(); // a+b
HashMap<String, Integer> var = getValue(expStr);// var{a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果: " + expStr + "=" + calculator.run(var));
}
//获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式: ");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
19.5解释器模式在Spring框架应用的源码剖析
- Spring框架中 SpelExpressionParser就使用到解释器模式2)代码分析+Debug源码
-
6解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
2)应用场景:编译器、运算表达式计算、正则表达式、机器人等
3)使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
20、状态模式(描述不停变化的状态)
20.1 APP抽奖活动问题
请编写程序完成APP抽奖活动具体要求如下:
l)假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
2)奖品数量固定,抽完就不能抽奖
3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
4)活动的四个状态转换关系图(右图)
20.2状态模式基本介绍
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
原理类图:
对原理类图的说明-即(状态模式的角色及职责)
1)Context类为环境角色,用于维护State实例,这个实例定义当前状态
2) State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为3) ConcreteState具体的状态角色,每个子类实现一个与Context 的一个状态相关行为
20.3状态模式解决APP抽奖问
1)应用实例要求
完成APP抽奖活动项目,使用状态模式.
2)思路分析和图解(类图)
-定义出一个接口叫状态接口,每个状态都实现它。-接口有扣除积分方法、抽奖方法、发放奖品方法
代码:
状态抽象类:
/**
*状态抽象类
*
*/
public abstract class State {
//扣除积分–50
public abstract void deductMoney();
//是否抽中奖品
public abstract boolean raffle();
//发放奖品
public abstract void dispensePrize();
}
可以抽奖的状态类:
import java.util.Random;
/**
* 可以抽奖的状态
*/
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
//已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}
@Override
//可以抽奖,抽完奖后,根据实际情况,改成新的状态@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等! ");
Random r = new Random();
int num = r.nextInt(10);//10%中奖机会
if (num == 0) {
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
} else {
System.out.println("很遗憾没有抽中奖品! ");//改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
//不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
奖品发放完的状态类:
/**
* 奖品发放完毕状态
* 说明,当我们 activity改变成 DispenseOutState,抽奖活动结束* @author Administrator
*/
public class DispenseOutState extends State {
//初始化时传入活动引用
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}
奖品发放状态类:
/**
* 发放奖品的状态* @author Administrator
*/
public class DispenseState extends State {
//初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
if (activity.getCount() > 0) {
System.out.println("恭喜中奖了");
//改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
} else {
System.out.println("很遗憾,奖品发送完了");
//改变状态为奖品发送完毕,后面我们就不可以抽奖
activity.setState(activity.getDispensOutState());
//System.out.println("抽奖活动结束");
//System.exit(O);
}
}
}
不能抽象状态类:
/**
* 不能抽奖状态
*/
public class NoRaffleState extends State {
//初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
//当前状态可以扣积分﹐扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
//当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔! ");
return false;
}
//当前状态不能发奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
抽奖类:保存四种状态
/**
* 抽奖活动l/
*/
public class RaffleActivity {
// state表示活动当前的状态,是变化
State state = null;
//奖品数量
int count = 0;
//四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1.初始化当前的状态为noRafflleState(即不能抽奖的状态)//2.初始化奖品的数量
public RaffleActivity(int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分,调用当前状态的deductMoney
public void debuctMoney() {
state.deductMoney();
}
//抽奖
public void raffle() {
//如果当前的状态是抽奖成功
if (state.raffle()) {//领取奖品
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//这里请大家注意,每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}
public State getNoRafflleState() {
return noRafflleState;
}
public void setNoRafflleState(State noRafflleState) {
this.noRafflleState = noRafflleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
public State getDispensOutState() {
return dispensOutState;
}
public void setDispensOutState(State dispensOutState) {
this.dispensOutState = dispensOutState;
}
}
客户端:
/**
* 状态模式测试类*@author Administrator
*/
public class ClientTest {
public static void main(String[] args) {
//创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
//我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("-------第" + (i + 1) + "次抽奖----------");//参加抽奖,第一步点击扣除积分
activity.debuctMoney();
//第二步抽奖
activity.raffle();
}
}
}
20.4状态模式的注意事项和细节
1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
2)方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
3)符合“开闭原则”。容易增删状态
4)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
5)应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
20.5思考
对应业务:一个任务对象包含多种状态,创建几个具体的状态类进行状态描述。根据业务流程更新任务对象的状态进行数据库存储以及check当前状态。并且在不同的状态内部,可实现该状态对应的行为:如上报错误,状态入库,回调平台等
21、策略模式(算法的使用者和算法分离,使用组合聚合的方式,策略子类:一个动作不同的策略实现)
21.1编写鸭子项目,具体要求如下:
1)有各种鸭子(比如野鸭、北京鸭、水鸭等,鸭子有各种行为,比如叫、飞行等) 2)显示鸭子的信息
传统方案解决鸭子问题的分析和代码实现l)传统的设计方案(类图)
写一个鸭子抽象类,让所有鸭子子类去继承
传统的方式实现的问题分析和解决方案:
1)其它鸭子,都继承了Duck类,所以fly 让所有子类都会飞了,这是不正确的
2)上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
3)为了改进1问题,我们可以通过覆盖fly方法来解决=→>覆盖解决
4) 问题又来了,如果我们有一个玩具鸭子ToyDuck,这样就需要ToyDuck 去覆盖Duck的所有实现的方法=>解决思路-》策略模式(strategy pattern)
21.2策略模式基本介绍
1)策略模式(Strategy Pattem)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
2)这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口)﹔第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
策略模式的原理类图:
说明:从上图可以看到,客户context有成员变量strategy或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定
策略模式解决鸭子问题
1)应用实例要求
编写程序完成前面的鸭子项目,要求使用策略模式
2)思路分析(类图)
策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者
代码:
策略接口:
public interface FlyBehavior {
void fly(); //子类具体实现
}
策略接口的具体实现1:
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术高超~~~");
}
}
策略接口的具体实现2:
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞翔");
}
}
策略接口的具体实现3:
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术一般");
}
}
鸭子类:策略接口通过子类构造初始化
public abstract class Duck {
//属性,策略接口
FlyBehavior flyBehavior;
//其它属性<->策略接口
//QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();//显示鸭子信息
public void fly() {
//改进
if (flyBehavior != null) {
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
// public void setQuack Behavior(QuackBehavior quackBehavior){
// this.quackBehavior = quackBehavior;
}
具体鸭子类:
public class PekingDuck extends Duck {
//假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDuck() {
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("~~北京鸭~~~");
}
}
具体鸭子类:
public class ToyDuck extends Duck {
public ToyDuck() {
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
System.out.println("玩具鸭");
}
}
具体鸭子类:
public class WildDuck extends Duck {
//构造器,传入FlyBehavor的对象
public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println("这是野鸭");
}
}
客户端:
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();
ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();
//动态改变某个对象的行为,北京鸭不能飞
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭的实际飞翔能力");
pekingDuck.fly();
}
}
21.3策略模式在JDK-Arrays应用的源码分析
1)JDK的 Arrays 的Comparator就使用了策略模式
2)代码分析+Debug源码+模式角色分析
import java.util.Arrays;
import java.util.Comparator;
public class JDKCode {
public static void main(String[] args) {
//数组
Integer[] data = {9, 1, 2, 8, 4, 3};
//实现降序排序,返回-1放左边,1放右边,О保持不变//说明
//1. 实现了Comparator接口(策略接口),匿名类对象
//new Comparator<Integer > () {..}
// 2. 对象new Comparator<Integer > ()...} 就是实现了策略接口的对象
//public int compare(Integer ol, Integer o2)}指定具体的处理方式
Comparator<Integer> integerComparable = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
};
Arrays.sort(data, integerComparable);
//方式2-同时lambda表达式实现策略模式
Integer[] data2 = {19, 11, 12, 18, 14, 13};
Arrays.sort(data2, (var1, var2) -> {
if (var1.compareTo(var2) > 0) {
return -1;
} else {
return 1;
}
});
System.out.println("data2=" + Arrays.toString(data2));
}
/* public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);//使用了策略对象c
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);//使用了策略对象c
}
}*/
private void test(){
Comparator<Integer> compare = this::compare;
}
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return -1;
} else {
return 1;
}
}
}
21.4策略模式的注意事项和细节
1)策略模式的关键是:分析项目中变化部分与不变部分
2)策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
3) 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
4)提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context 改变它,使它易于切换、易于理解、易于扩展
5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
22、职责链模式(流程形成环状结构)
22.1学校OA系统的采购审批项目
采购员采购教学器材
1)如果金额小于等于5000,由教学主任审批(O<=x<=5000 )
2)如果金额小于等于10000,由院长审批(5000<x<=10000)
3)如果金额小于等于30000,由副校长审批(10000<x<=30000)
4)如果金额超过30000 以上,有校长审批( 30000<x)
请设计程序完成采购审批项目
22.2传统方案解决OA系统审批,传统的设计方案(类图)
传统方案解决OA系统审批问题分析:
1)传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver(审批人)完成审批。
2)传统方式的问题分析︰客户端这里会使用到分支判断(比如 switch)来对不同的采购请求处理,这样就存在如下问题
(1)如果各个级别的人员审批金额发生变化,在客户端的也需要变化
(2)客户端必须明确的知道有多少个审批级别和访问
3)这样对一个采购请求进行处理和Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护
4)解决方案=》职责链模式
22.3职责链模式基本介绍基本介绍
1)职责链模式(Chain of Responsibility Pattem),又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
2)职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
3)这种类型的设计模式属于行为型模式
职责链模式的原理类图:
对原理类图的说明-即(职责链模式的角色及职责)
- Handler:抽象的处理者,定义了一个处理请求的接口,同时含义另外Handler
- ConcreteHandlerA ,B是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链
- Request ,含义很多属性,表示一个请求
22.4职责链模式解决OA系统采购审批
思路分析和图解(类图)
代码:
请求类:
//请求类
public class PurchaseRequest {
private int type = 0;//请求类型
private float price = 0f;
//请求金额
private int id = 0;
//构造器
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}
}
处理人抽象类:
public abstract class Approver {
Approver approver;//下一个处理者
String name;//名字
public Approver(String name) {
this.name = name;
}
//下一个处理者
public void setApprover(Approver approver) {
this.approver = approver;
}
//处理审批请求的方法,得到一个请求,处理是子类完成,因此该方法做成抽象
public abstract void processRequest(PurchaseRequest purchaseRequest);
}
具体的处理人:
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() <= 5000) {
System.out.println("请求编号 id=" + purchaseRequest.getId() + "被" + this.name + "处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
具体的处理人:
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println("请求编号 id=" + purchaseRequest.getId() + "被" + this.name + "处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
具体的处理人:
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println("请求编号 id=" + purchaseRequest.getId() + "被" + this.name + "处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
具体的处理人:
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 30000) {
System.out.println("请求编号 id=" + purchaseRequest.getId() + "被" + this.name + "处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好(处理人构成环形:)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
22.5职责链模式在SpringMVC框架应用的源码分析
- SpringMVC-HandlerExecutionChain类就使用到职责链模式
- SpringMVC请求流程简图
3)代码分析+Debug源码+说明
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);//获取到HandlerExecutionChain对象
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//在 mappedHandler.applyPreHandle 内部得到啦HandlerInterceptor interceptor,调用了拦截器的interceptor.preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//这里调用方法:
/**
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
//说明:在mappedHandler.applyPreHandle 内部中,还调用了triggerAfterCompletion方法,该方法中调用了
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
*/
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//说明: mappedHandler.applyPostHandle 方法内部获取到拦截器,并调用拦截器的interceptor.postHandle(request, response, this.handler, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
//方法调用
/*
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
*/
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
5)对源码总结
springmvc请求的流程图中,执行了拦截器相关方法 interceptor.preHandler 等等>在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式
HandlerExccutionChain主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
HandlerExecutionChain维护了HandlerInterceptor的集合,可以向其中注册相应的拦截器.
22.6职责链模式的注意事项和细节
1)将请求和处理分开,实现解耦,提高系统的灵活性
2)简化了对象,使对象不需要知道链的结构
3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler 中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
s.name + “处理”);
} else {
approver.processRequest(purchaseRequest);
}
}
}
具体的处理人:
```java
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 30000) {
System.out.println("请求编号 id=" + purchaseRequest.getId() + "被" + this.name + "处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好(处理人构成环形:)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
22.5职责链模式在SpringMVC框架应用的源码分析
- SpringMVC-HandlerExecutionChain类就使用到职责链模式
- SpringMVC请求流程简图
3)代码分析+Debug源码+说明
[外链图片转存中…(img-idEwxC0U-1630137889573)]
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);//获取到HandlerExecutionChain对象
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//在 mappedHandler.applyPreHandle 内部得到啦HandlerInterceptor interceptor,调用了拦截器的interceptor.preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//这里调用方法:
/**
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
//说明:在mappedHandler.applyPreHandle 内部中,还调用了triggerAfterCompletion方法,该方法中调用了
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
*/
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//说明: mappedHandler.applyPostHandle 方法内部获取到拦截器,并调用拦截器的interceptor.postHandle(request, response, this.handler, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
//方法调用
/*
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
*/
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
5)对源码总结
springmvc请求的流程图中,执行了拦截器相关方法 interceptor.preHandler 等等>在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式
HandlerExccutionChain主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
HandlerExecutionChain维护了HandlerInterceptor的集合,可以向其中注册相应的拦截器.
22.6职责链模式的注意事项和细节
1)将请求和处理分开,实现解耦,提高系统的灵活性
2)简化了对象,使对象不需要知道链的结构
3)性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler 中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
4)调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
5)最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中Tomcat:对Encoding的处理、拦截器
最后
以上就是负责戒指为你收集整理的23种设计模式学习的全部内容,希望文章能够帮你解决23种设计模式学习所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复