分类
创建者模式
结构型模式
行为型模式
2 类图 UML
2.1 类图概述
显示了模型的静态结构
2.2 类图表示法
2.2.1 类的表示方式
Demo |
---|
+ age:int |
+ method(args 1):int |
+:public
-:private
#:protected
属性完整表示: 可见性 名称:类型
方法完整表示: 可见性 名称(参数列表)[:返回类型]
2.2.2 类与类之间关系的表示方式
2.2.2.1 关联关系
1.单向关联
一个顾客有一个地址
2.双向关联
一个顾客可以有多个商品,一件商品也有一个顾客
3.自关联
自己包含自己
2.2.2.2 聚合关系
强关联关系,是整体和部分之间的关系
2.2.2.3 组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在整合关系中,整体对象可以控制部分对象的生命周期,一旦整体不存在,部分也不将存在。
2.2.2.4 依赖关系
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
2.2.2.5 继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般和特殊的关系,是父类与子类之间的关系,是一种继承关系。
2.2.2.6 实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所有声明的抽象方法。
3 软件设计原则
3.1 开闭原则
对扩展开放,对修改关闭。在程序需要进行拓展时,不能修改原有代码。想达到这种效果,需要使用抽象类和接口。
3.2 里氏代换原则
任何基类出现的地方,子类一定可以出现。
子类可以拓展父类的功能,但不能改变父类的功能。
换句话说,子类继承父类时,除了添加新功能外,不要重写父类的方法。
正方形继承长方形,重写了setLength方法,违反里氏代换原则。
3.3 依赖倒转原则
高层模块不应该依赖底层模块,两者应该依赖其抽象;抽象不应该依赖细节,细节应该抽象。简单的说,就是要对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块之间的耦合。
比如组装电脑。只需要,内存,机箱cpu,硬盘,只要这些东西有了,就能运行计算机。但具体的品种可以选择。
3.4 接口隔离原则
客户端不应该被迫依赖于它不适用的方法。一个类对另一个类的依赖应该建立在最小的接口上。
3.5 迪米特法则
又叫最少知识原则。
只和你的直接朋友交谈,不跟“陌生人”说话。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的互相调用,可以通过第三方(包含了这两个软件)转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象,当前对象创建的对象、当前对象的方法参数等。这些对象同当前对象存在关联、聚合或者组合关系,可以直接访问对象的方法。
3.6 合成复用原则
尽量使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
采用组合或者聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能。
4 创建者模式
创建者模式的主要关注点是“怎样创建对象”。它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不用关注对象的创建细节。
创建者模式分为:
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 原型模式
- 建造者模式
4.1 单例设计模式
单例模式是java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了创建对象的最佳方式。
4.1.1 实现
饿汉:类加载就导致该单实例被创建
懒汉:首次需要使用时,才会创建对象
饿汉式-静态方式
public class Singleton{
//1.私有构造方法
private Singleton(){
}
//2.在本类中创建本类对象
private static Singleton instance = new Singleton();
//3.提供一个公共的访问方式,让外界获取对象
public static Singleton getInstance(){
return instance;
}
}
饿汉式-静态代码块
public class Singleton{
//1.私有构造方法
private Singleton(){
}
//2.声明本类对象
private static Singleton instance = null;
//3.在代码块中进行赋值
static{
instance = new Singleton();
}
//4.提供一个公共的访问方式,让外界获取对象
public static Singleton getInstance(){
return instance;
}
}
饿汉式-枚举类
public enum Singleton{
INSTANCE;
}
懒汉式-线程不安全
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.声明本类对象
private static Singleton instance = null;
//3.提供一个公共的访问方式,让外界获取对象
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
懒汉式-线程安全
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.声明本类对象
private static Singleton instance = null;
//3.提供一个公共的访问方式,让外界获取对象
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
懒汉式-双锁检查
因为锁降低了性能
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.声明本类对象
private static volatile Singleton instance = null;
//volatile保证指令可见性,有序性。去掉,在多线程时,可能出现空指针问题
//3.提供一个公共的访问方式,让外界获取对象
public static Singleton getInstance(){
//第一次判断,为空,抢占锁
if(instance == null){
sychronized(Singleton.class){
//第二次判断,为空,创建对象
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
懒汉式-静态内部类 不加锁且安全
JVM在加载外部类时,是不会加载静态内部类的。只有内部类的属性/方法被调用时,才会被加载。
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并名初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
4.1.2 破坏单例模式
序列化和反序列化破坏单例模式
//将对象写入文件,再将对象读出文件
反射破坏单例模式
Class clazz = Singleton.class;
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
Singleton s1 = (Singleton) cons.newInstance();
Singleton s2 = (Singleton) cons.newInstance();
4.1.3 问题解决
4.1.3.1 解决反序列化破坏
//实现Serializable接口,写readResolve()方法
public class Singleton implements Serializable{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并名初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
//当进行反序列化时,会自动调用该方法。
public Object readResolve(){
return SingletonHolder.INSTANCE;
}
}
4.1.3.2 解决反射破坏
public class Singleton{
private static boolean flag = flase;
//1.私有构造方法
private Singleton(){
sychronized(Singleton.class){
//判断是否是第一次访问
if(flag){
throw new RuntimeException("不能创建多个对象")
}
}
}
//2.定义一个静态内部类
private static class SingletonHolder{
//在内部类中声明并名初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
4.2 工厂方法模式
4.2.1 简单工厂模式
简单工厂不是一种设计模式,反而比较像一种编程习惯。
提供方法给用户来获取对象。
优点:
解耦,但增加了产品和工厂的耦合。
缺点
增加新产品时,需要修改工厂类中的代码。违背了开闭原则。
结构:
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现或者继承抽象产品的子类。
- 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品。
4.2.2 工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
结构
- 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法创建产品
- 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品:实现了抽象产品角色锁定义的接口,由具体工厂来创建,它同具体工厂之间一一对应
实现
工厂:
public interface CoffeFactory{
Coffee createCoffee();
}
public class AmericanCoffeeFactory implements CoffeeFactory{
public Coffee createCoffee(){
return new AmericanCoffee();
}
}
商店:
public class CoffeeStore{
private CoffeeFactory factory;
public void setFactory(CoffeeFactory factory){
this.factory = factory;
}
//点咖啡
public Coffee orderCoffee(){
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
测试:
psvm{
//创建咖啡店
CoffeeStore store = new CoffeeStore();
//创建咖啡工厂
CoffeeFactory factory = new AmericanCoffeeFactory();
store.setFactory(factory);
//点咖啡
Coffee coffee = store.orderCoffee();
}
优点
- 用户只需要知道具体工厂的名称就可以获得所要的产品,无需知道产品的具体创建过程
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无需对原工厂进行修改。
缺点
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
4.3 抽象工厂模式
工厂方法模式只考虑生产同级别的产品,但是现实生活中,许多工厂时综合性的工厂,能生产多种产品。
抽象工厂模式,将考虑多等级产品的生产。
同一个产品族可以指的是,同一个厂家生产的。
构成
- 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
实现
优点
当一个产品族中的多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的产品
缺点
当产品族需要增加一个新产品时,所有的工厂类都需要进行修改。
使用场景
5 结构型模式
描述如何将类或者对象按照某种布局组成更大的结构。它分为类结构型模式和对象结构型某事,前者采用继承机制来组织接口和类,后者采用组合或者聚合来组合对象。
5.1 代理模式
由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象的中介。
结构
- 抽象主题类:通过接口或者抽象类说明真实主题和代理对象实现的业务方法。
- 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或拓展真实主题的功能。
5.1.1 静态代理
售票的是抽象主题类,火车站等是真实主题类,代理商是代理类。
public interface SellTickets{
void sell();
}
public TrainStation implements SellTickets{
public void sell(){
sout("火车站卖票");
}
}
//代理类
public class ProxyPoint implements SellTickets{
//声明火车站类对象
private TranStation trainStation = new TranStation();
public void sell(){
sout("代售点收一点代理费");
trainStation.sell();
}
}
5.1.2 JDK动态代理
java中提供一个动态代理类Proxy,提供一个创建代理对象的静态方法。(newProxyInstance方法)。
只对接口进行代理.
public interface SellTickets{
void sell();
}
public TrainStation implements SellTickets{
public void sell(){
sout("火车站卖票");
}
}
//动态代理
public class ProxyFactory{
//声明被代理对象
private TrainStation train = new TrainStation();
public SellTickets getProxyObject(){
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args)throws Exception{
sout("invoke方法被调用了");
sout("收取一定费用---jdk");
Object obj = method.invoke(station,args); //执行被代理对象的方法。
return obj;
}
}
);
return proxyObject;
}
}
psvm{
proxyObject.sell(); //这是增强之后的对象.
}
底层原理
5.1.3 CGLIB动态代理
//需要导入cglib jar包
public interface SellTickets{
void sell();
}
public TrainStation implements SellTickets{
public void sell(){
sout("火车站卖票");
}
}
//cglib动态代理
public class ProxyFactory implements MethodInterceptor{
private TrainStation station = new TrainStation();
public TrainStation getProxyObject(){
//创建Enhancher对象,类似于JDK代理中的Proxy类
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation)enhancer.create(); //返回TrainStation的子类
return proxyObject
}
public Ojbect Entercept(Object o,Method method,Object[]objects,MethodProxy methodProxy) throws Exception{
sout("sell()方法执行了");
sout("增强");
Object obj = method.invoke(station,objects);
return obj;
}
}
psvm{
ProxyFactory factory = new ProxyFactory();
TraninStation t = factory.getProxyObject();
t.sell();
}
5.1.4 三种代理对比
5.1.5 优缺点
优点:
- 代理模式在客户端和目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端和目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度
5.2 适配器模式
定义
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类一起工作.
结构
- 目标接口:当前系统业务所期待的接口,它可以是抽象类或者接口
- 适配者类:它是被访问和适配的现存组件库中的组件接口
- 适配器类:它是一个转换器,通过继承或者引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者.
5.2.1 类适配器模式
实现方式
定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
现有一台电脑只能读取SD卡,而要读取TF卡中内容,这就需要用到适配器。
适配者类:
public interface TFCard{
//往TF卡中读
String readTF();
//往TF卡中写
void writeTF(String msg);
}
public class TFCardImpl implements TFCard{
pubic String readTF(){
String msg = "读取TF卡";
return msg;
}
public void writeTF(String msg){
sout("写TF卡" + msg);
}
}
目标接口类:
public interface SDCard{
//往SD卡中读
String readSD();
//往SD卡中写
void writeSD(String msg);
}
public class SDCardImpl implements SDCard{
pubic String readSD(){
String msg = "读取TF卡";
return msg;
}
public void writeSD(String msg){
sout("写SD卡" + msg);
}
}
使用者:
public class Computer{
//从SD卡中读取数据
public String readSD(SDCard sdCard){
if(sdCard == null){
throw new NullPointerException("sd card is null");
}
return sdCard.readSD();
}
}
psvm{
//直接读SD卡
Computer c = new Computer();
String msg = c.readSD(new SDCardImpl());
//使用适配器读取SD卡
String msg = c.readSD(new SDAdapterTF());
}
适配器类:继承适配者类。实现目标接口
public class SDAdatpterTF extends TFCardImpl implements SDCard{
public String readSD(){
sout("适配器读取tf卡");
return readTF();
}
public void writerSD(String msg){
sout("适配器写tf卡");
writTF(msg);
}
}
缺点:违背了合成复用原则。类适配器是客户类有一个接口规范的情况下使用,反之不可用。(只能继承一个类)
5.2.2 对象适配器模式
实现
对象适配器模式可采用现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口.
适配器类:聚合(拥有)适配者类.
public class SDAdatpterTF implements SDCard{
//声明适配这类
private TFCard tfCard;
public SDAdatpterTF(TFCarc tfCard){
this.tfCard = tfCard;
}
public String readSD(){
sout("适配器读取tf卡");
return tfCard.readTF();
}
public void writerSD(String msg){
sout("适配器写tf卡");
tfCard.writTF(msg);
}
}
使用者类:
public class Computer{
//从SD卡中读取数据
public String readSD(SDCard sdCard){
if(sdCard == null){
throw new NullPointerException("sd card is null");
}
return sdCard.readSD();
}
}
psvm{
//直接读SD卡
Computer c = new Computer();
String msg = c.readSD(new SDCardImpl());
//使用适配器读取SD卡
//创建适配器类
SDAdapter sdAdapterTF = new SDAdapterTF(new TFCardImpl());
String msg = computer.readSD(sdAdapterTF);
}
5.3 装饰者模式
快餐加料.若继承,或产生很多子类.
定义
在不改变现有对象结构的情况下,动态地给对象增加一些职责(即增加额外功能)地模式.
结构
- 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象. (好比快餐)
- 具体构件角色:实现抽象构件,通过装饰角色为其添加一些职责
- 抽象装饰角色:继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能. (好比配料)
- 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任.
抽象构件角色:快餐
public abstract class FastFood{
private float price;//价格
private String desc; //描述
//get and set方法
public FastFood(){
}
public FastFood(Float price, String desc){
this.price = price;
this.desc = desc;
}
public abstract float cost();
}
具体构件角色:
炒饭
public class FriedRice extends FastFood{
public FriedRice(){
super(10,"炒饭");
}
public float cost(){
return getPrice();
}
}
炒面
public class FriedNoodles extends FastFood{
public FriedRice(){
super(12,"炒面");
}
public float cost(){
return getPrice();
}
}
抽象装饰角色:(加了配料的快餐)
public abstract class Garnish extends FastFood{
//快餐
FastFood fastFood;
//get 和 set方法
public Garnish(FastFood fastFood,float price,String desc){
super(price,desc);
this.fastFood = fastFood;
}
}
具体装饰者类:
鸡蛋
public class Egg extends Garnish{
public Egg(FastFood fastFood){
super(fastFood,1,"加鸡蛋");
}
public float cost(){
return getPrice() + getFstFood().cost(); //快餐价格+鸡蛋价格
}
//描述
@Override
public String getDesc(){
return super.getDesc() + getFastFood().getDesc();
}
}
测试类
psvm{
//点一份炒饭
FastFood food = new FriedRice();
sout(food.getDesc() + " " + food.cost());
//加一个鸡蛋
food = new Egg(food);
sout(food.getDesc() + " " + food.cost());
}
好处
- 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
JDK中包装类BufferdInputStream,BufferedOutputStream,BufferedReader使用了装饰者模式.!
5.3.1 代理和装饰者的区别
-
相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法.
-
不同点:
-
目的不同
装饰者是为了增强目标对象.
静态代理是为了保护和隐藏目标对象
-
获取目标对象的构建地方不同
装饰者是由外界传递进来,可以通过构造方法传递
静态代理是在代理类内部创建,以此来隐藏目标对象.
-
6 行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
6.1 模板模式
概述
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
定义:
定义一个操作中的算法骨架,而将算法中的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤.
结构
-
抽象类:负责给出一个算法的轮廓和骨架.它由一个模板方法和若干个基本方法构成
-
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法.
-
基本方法:是实现了算法各个步骤的方法,是模板方法的组成部分.基本方法又分为3中
- 抽象方法:抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method):在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
-
-
具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
实现
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:
(抽象类,定义模板方法和基本方法)
public abstract class AbstractClass{
public final void cookProcess(){
this.pourOil();
this.heatOil();
this.pourVegetable();
this.pourSauce();
this.fry();
}
public void pourOil(){
sout("倒油");
}
public void heatOil(){
sout("热油");
}
//倒蔬菜和调料是不同的,因此用抽象方法
public abstract void pourVegetable(){
}
public abstract void pourSauce(){
}
pulibc void fry(){
sout("炒菜炒");
}
}
具体子类:
public class Caixin extends AbstractClass{
public void pourVegetable(){
sout("炒的是菜心");
}
public void pourSauce(){
sout("下的酱料是蒜蓉");
}
}
优点
- 提高代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中.
- 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合"开闭原则"。
缺点
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
JDK源码中使用了模板方法
InputStream类中使用了模板方法.
6.2 责任链模式
概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的"击鼓传花”游戏等。
定义
又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
结构
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
实现
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
请假条类:
public class LeaveRequest{
private String name;
private int num;
private String content;
public LeaveRequest(String name, int num, String content){
this...;
}
//get 方法
}
抽象处理者类:
public abstract class Handler{
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
//领导处理的请求天数区间
private int numStart;
private int numEnd;
private Handler nextHandler;
public Handler(int numStart){
this...;
}
public Handler(int numStart, int numEnd){
this...;
}
//设置领导
public void SetNextHandler(Handler nextHnadler){
this.nextHandler = nextHandler;
}
//领导处理请假条的方法
protected abstract void handleLeave(LeaveRequest leaveRequest);
//提交上级领导
public final void submit(LeaveRequest leaveRequest){
this.handleLeave(leavRequest);
if(this.nextHandler != null && leaveRequest.getNum() > this.numEnd){
this.nextHandler.submit(leaveRequest);
}else{
sout("流程结束");
}
}
}
具体处理者类:
(小组长)
public class GroupLeader extends Handler{
public GroupLeader(){
super(0,Handler.NUM_ONE);
}
protected void HandleLeave(LeaveRequest leaveRequest){
sout(leaveRequest...);//请假条的信息
sout("小组长审批:同意");
}
}
(经理)
public class Manager extends Handler{
public GroupLeader(){
super(0,Handler.NUM_ONE);
}
protected void HandleLeave(LeaveRequest leaveRequest){
sout(leaveRequest...);//请假条的信息
sout("经理审批:同意");
}
}
客户端:
psvm{
LeaveRequest leave = new LeaveRequest("小明",1,"身体不适");
GroupLeader g = new GroupLeader();
Manager m = new Manager();
//设置处理者链
g.setNextHandler(m);
//小明提交请假条
g.submit(leave);
}
优点
-
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。 -
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。 -
增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。 -
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if或者 if…else语句。 -
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
JAVAWEB
FilterChain是责任链模式的典型应用
6.3 观察者模式
定义
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构
- subject : 抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject : 具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- 0bserver: 抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己.
- Concrere0bserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
实现
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。
类图如下:
抽象主题:
public interface Subject{
//添加订阅者(添加观察者对象)
void attach(Observer observer);
//删除订阅者
voidetach(Observer observer);
//通知订阅者
void notify(String message);
}
具体主题
public class SubscriptionSubject implements Subject{
private List<Observer> weixinUserList = new ArrayList<Observer>();
public void attach(Observer observer){
weixinUserList.add(observer);
}
public void detach(Observer observer){
weixinUserList.remove(observer);
}
public void notify(String message){
//遍历结合
for(Observer o:weixinUserList){
observer.update(message);
}
}
}
抽象观察者类
public interface Observer{
void update(String message);
}
具体观察者类
public class WeiXinUser implements Observer{
private String name;
public WeiXinUser(String name){
this.name = name;
}
public void update(String message){
sout(name + "-" + message);
}
}
测试类
psvm{
//1.创建公众号对象
SubscriptionSubject s = new SubscriptionSubject();
//2.订阅公众号
subject.attach(new WeiXinUser("悟空"));
//3.公众号更新,发出信息给观察者
subject.notify("更新了");
}
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
缺点:
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
JDK中通过java.util.Observable类和java.util.Observer接口定义了观察者模式