设计模式----行为型设计模式
行为性设计模式关注各个类之间的相互作用,将职责划分清除,使得代码更加清晰。
行为性模式分为类行为模式和对象行为模式,类行为模式采用继承机制来在类间分派任务,对象行为模式采用组合或者聚合在对象间分配任务,对象行为模式比类行为模式具有更大的灵活性
行为性设计模式是最大的一类模式,包含了11种模式
1、模版模式(Template Method):定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中。
2、策略模式(Strategy):定义一系列的算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法客户。
3、命令模式(Command):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
4、责任链模式(Chain Of Responsibilty):将请求从链中的一个对象传到下一个对象,直到请求被响应为止,通过这种模式去除对象间的耦合。
5、状态模式(State):允许一个对象在其内部状态发生改变时改变其行为能力。
6、观察者模式(Observer):多个对象存在一对多的关系,当一个对象发生改变时,把这种改变通知该其他的对象,从而影响其他对象的行为。
7、中介者模式(Mediator):定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,是原有的对象之间不必相互了解。
8、迭代器模式(Iterator):提供一种方法来顺序访问聚合对象中一系列数据,而不用暴露聚合对象的内部表示。
9、访问者模式(Visitor):在不改变聚合元素的前提下,为集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
10:备忘录模式(Memento):在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
11:解析器模式(Interpreter):提供如何定义语言的方法,以及对语言句子的解释方法,即解释器。
观察者模式
观察者模式介绍
定义:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并自动更新,
又称之为发布-订阅模式。
观察者模式类图UML
观察者模式定义个四个角色:抽象主题、具体主题、抽象观察者、具体观察者。
抽象主题(Subject):是一个抽象类或者是接口,定义了增加、删除、通知观察者对象的方法。
具体主题(ConcreteSubject):该角色继承或者实现了抽象主题,定义了一个集合存储注册过的具体观察者对象,在具体主题的内部状态发生改变时,会给所有的注册过的观察者发送通知。
抽象观察者(Observer):该角色是具体观察者的抽象类,定义了一个更新方法。
具体观察者(ConcreteObserver):是具体的观察者对象,在得到具体主题更改通知时更新自身的状态。
代码实现
/**
* 抽象观察者
*/
public interface Observer {
public void update(String msg);
}
/**
* 具体观察者
*/
public class Person implements Observer {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void update(String msg) {
System.out.println(name+"接收到通知:"+msg);
}
}
/**
* 抽象主题
*/
public interface Subject {
/**
* 增加观察者
*/
public void register(Observer observer);
/**
* 删除观察者
*/
public void delete(Observer observer);
/**
* 通知观察者
*/
public void notify(String msg);
}
/**
* 具体主题
*/
public class APPSubject implements Subject {
//用于保存所有订阅者
private List <Observer> observers = new ArrayList <>();
private String version;
@Override
public void register(Observer observer) {
observers.add(observer);
}
@Override
public void delete(Observer observer) {
observers.remove(observer);
}
@Override
public void notify(String msg) {
for (Observer o:observers) {
o.update(msg);
}
}
public void setVersion(String msg) {
this.version = msg;
//一旦具体主题发送改变,需要通知观察者
notify(msg);
}
}
测试代码:
public class ObserveTest {
public static void main(String[] args) {
AppSubject appSubject=new AppSubject();
Person p1=new Person("p1");
Person p2=new Person("p2");
Person p3=new Person("p3");
appSubject.addPerson(p1);
appSubject.addPerson(p2);
appSubject.addPerson(p3);
appSubject.setVersion("hello");
}
}
观察者模式总结
优点:
1、观察者和被观察者之间是解耦合的
2、建立一套触发机制
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者,将所有的观察者都通知会花费很长的时间(解决方案:采用异步通知)
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发他们之间进行循环依赖,可能会导致系统奔溃。
适用场景:
跨系统的消息交换场景,比如消息队列,事件总线的消息机制。
当对一个对象的更改会需要更改其他的对象,而其他的关联对象很多的时候。
观察者模式在Java中的应用
spring中的观察者模式
spring中观察者包含三部分:Event事件(相当于消息)、Listener监听者(相当于观察者)、Publisher发送者(相当于被观察者/主题)
在Spring中,实现观察者主要包含三部分
定义一个继承自ApplicationEvent事件
定义一个实现了ApplicationListener监听器
定义一个发布者,发送者调用ApplicationCentext来发送事件消息
// Event事件
public class DemoEvent extends ApplicationEvent {
private String message;
public DemoEvent(Object source, String message) {
super(source);
}
public String getMessage() {
return this.message;
}
}
// Listener监听者
@Component
public class DemoListener implements ApplicationListener {
@Override
public void onApplicationEvent(DemoEvent demoEvent) {
String message = demoEvent.getMessage();
System.out.println(message);
}
}
// Publisher发送者
@Component
public class DemoPublisher {
@Autowired
private ApplicationContext applicationContext;
public void publishEvent(DemoEvent demoEvent) {
this.applicationContext.publishEvent(demoEvent);
}
}
在Spring中,把观察者注册到了ApplicationContext对象中
责任链模式
责任链模式介绍
责任链定义:
创建多个对象,使对象之间形成一个链,并沿着这条链传递请求,直到链上的某一个对象决定处理次请求。
特点:
接收请求的对象连接成一条链,对象之间存在层级关系。
这些对象可以处理请求,也可以传递请求,直到有对象处理该请求。
责任链类图UML:
责任链中涉及角色:
抽象处理者角色:定义了处理请求的的接口或者抽象类,提供了处理请求的方法和设置下一个处理者的方法。
具体处理者角色:实现或者继承了抽象处理者角色,具体处理者接收到请求之后,可以选择将请求处理掉,或者将请求传递给下一个对象,由于具体处理者是持有下一个处理者的引用。
责任链模式代码
问题描述:判断输入的月份是第几季度
/**
* 抽象处理者角色
*/
public abstract class Handler {
/**
* 持有后续的责任对象
*/
protected Handler handler;
public void setHandler(Handler handler){
this.handler=handler;
}
public Handler getHandler() {
return handler;
}
/**
* 处理请求方法
*/
public abstract void HandlerRequest(int month);
}
/**
* 具体处理者角色
*/
public class ConCreteHandler1 extends Handler{
@Override
public void HandlerRequest(int month) {
if(month>=1&&month<=3){
System.out.println("该月份是第一季度");
}else{
/**
* 判断有没有后续责任对象,有就交给后续责任对象处理,没有就输出:无法处理此月份,给定参数不在范围内
*/
if(getHandler()!=null){
getHandler().HandlerRequest(month);
}else{
System.out.println("不能处理此月份,给定参数不在范围内");
}
}
}
}
//省略 ConCreteHandler2、ConCreteHandler3、ConCreteHandler4
测试代码:
public class ChainOfResponsibilityTest {
public static void main(String[] args) {
ConCreteHandler1 conCreteHandler1=new ConCreteHandler1();
ConCreteHandler2 conCreteHandler2=new ConCreteHandler2();
ConCreteHandler3 conCreteHandler3=new ConCreteHandler3();
ConCreteHandler4 conCreteHandler4=new ConCreteHandler4();
//组装责任链
conCreteHandler1.setHandler(conCreteHandler2);
conCreteHandler2.setHandler(conCreteHandler3);
conCreteHandler3.setHandler(conCreteHandler4);
conCreteHandler1.HandlerRequest(8);
}
}
责任链模式总结
优点:
1、降低耦合性:客户端不需要知道请求由哪个处理者处理,而处理者也不需要知道处理者之间的传递关系,由系统灵活的组织和分配。
2、良好的扩展性:增加处理者的实现比较简单,只需要重写处理请求业务逻辑方法。
缺点:
1、请求会从链头发出,直到有处理者响应,在责任链比较长的时候会影响系统性能。
2、请求处理在调试排错比较麻烦。
适用场景:
多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定
需要动态处理一组对象处理请求
比如:会员等级系统 会员等级之间构成一条链,用户发起请求,系统只需要将请求分发到责任链模式入口,直到传递到与用户等级匹配的等级,这样使各个会员的业务逻辑会变得更加清晰。
责任链模式在Java中的应用
1、netty:网络通信框架
netty基于NIO实现高并发的网络通信框架
netty下存在重要的组件:
ChannelHandler和ChannelPipeline组成责任链,使得一组ChannelHandler像一条链一样执行下去。
Channel:表示通道连接,和流直接相连,可以理解为一个请求就是一个channel。
ChannelHandler:处理单元,核心处理业务逻辑在这里进行处理,用于处理业务请求。
ChannelPipeline:用于保存处理过程中需要用到的ChannelHandler和ChannelHandlerContext。
ChannelHandlerContext:用户传输数据。
ChannelHandler分为inbound和outBount,分别对应IO中read和write的执行链。
ChannelHandler是用ChannelHandlerContext包裹着,有prev和next节点,可以获取前后的ChannelHandler,read是从ChannelPipeline的head到tail,同理,write是从tail执行到head。
2、SpringMVC中使用责任链
Spring Interceptor是SpringMVC的一部分,是对请求和响应的处理的,
用户请求的URL通过DispatchServlet转发给处理器映射器,处理器返回处理器执行链HandlerExecutionChain,可能会存在多个HandlerInterceptor,会对请求的方法进行拦截,主要通过遍历interceptors中的每个拦截器的preHandler方法来对请求分别进行拦截处理,
整个处理过程具体流程如下:
HandlerExecutionChain:处理器链控类,负责串联整条链上处理器并将请求分发给对应的处理器作出响应的处理
HandlerInterceptor:处理器类,真正对请求和响应进行拦截处理的类
也就是这个步骤:
模板模式
模板模式的介绍
定义:定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以在不改变一个算法结构即可重定义该算法的某些特定步骤。
模板模式的类图UML:
模板模式的角色说明:
抽象类/抽象模板(Abstract Class):负责给出一个算法的轮廓和骨架。他由一个模板方法和若干个基本方法构成,这些方法定义:
模板方法:定义算法的骨架,按照某种顺序调用其包含的基本方法
基本方法:是整个算法中的一个步骤,包含几种类型:
抽象方法:在抽象类中声明,在具体子类实现
具体方法:在抽象类已经实现,在具体的子类可以继承或者重写他
钩子方法:在抽象类已经实现,包括用于判断逻辑方法和需要子类重写的空方法两种
具体子类(ConcreteClass):具体实现类,实现抽象类中所定义的抽象方法和钩子方法,他是一个顶级逻辑的一个组成步骤。
代码实现
/**
* 抽象类
*/
public abstract class AbstractClass {
//模板方法
public void TemplateMethod(){
//定义普通方法的顺序
SpecialMethod();
AbstractMethod2();
AbstractMethod1();
}
//具体方法
public void SpecialMethod(){
System.out.println("抽象类中的具体方法被调用...");
}
//抽象方法
public abstract void AbstractMethod1();
public abstract void AbstractMethod2();
}
public class ConcreteClass extends AbstractClass {
@Override
public void AbstractMethod1() {
System.out.println("抽象方法1的实现被调用...");
}
@Override
public void AbstractMethod2() {
System.out.println("抽象方法3的实现被调用...");
}
}
public static void main(String[] args) {
ConcreteClass concreteClass = new ConcreteClass();
concreteClass.TemplateMethod();
}
一个抽象类:定义骨架流程(抽象方法放在一起)
确定的共同的方法步骤,放在抽象类
不确定的步骤,给子类来实现差异化
模板模式总结
模板模式解决的问题:
1、提高代码的复用性:将相同部分的代码放在抽象的父类中,从而将不同的代码放在不同子类中。
2、实现类反向控制:通过一个父类调用其子类的操作,通过对子类的具体事项扩展不同的行为,实现了反向控制。
优点:
1、封装了不变部分,扩展了可变部分,把认为不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类扩展实现。
2、在父类中提取了公共部分代码,便于代码复用。
3、部分方法由子类实现,因此子类可以通过扩展方法实现响应的功能,符合开闭原则。
缺点:
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大增加了系统实现的复杂度。
由于继承关系自身的缺点,如果父类定义了新的抽象方法,则所有子类都需要改一遍。
适用场景:
1、算法步骤整体固定,部分易变。
2、子类存在多个公共行为,将公共行为抽象为模板方法。
模板模式在Java中的应用
1、spring Bean的创建过程
在Spring中Bean的创建包含的主要步骤这其中就涉及模板模式,体现Spring扩展性,利用模板模式,Spring能够让用于定义Bean的创建过程。
Spring Bean的整个生命周期:
观察者模式和模板模式,这两种模式能够帮助我们创建扩展点,让框架的使用者在不修改源码的情况下,基于扩展点定制化扩展功能。
迭代器模式
迭代器模式介绍
定义:
迭代器模式(Iterator):提供一种方法来顺序访问一个聚合对象中的各个元素,而不暴露该对象的内部表示。
迭代器模式类图UML
迭代器模式主要包含的角色:
1、抽象聚合角色(Aggregate):定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
2、具体聚合角色(ConcreteAggregate):实现了抽象聚合类,返回一个具体的迭代器实例。
3、抽象迭代器角色(Iterator):定义访问和遍历聚合元素的接口,通常包含hasNext\first\next等方法。
4、具体迭代器角色(ConcreteIterator):实现抽象迭代器接口中定义的所有方法,完成对聚合对象的遍历,记录遍历的当前位置。
迭代器的四种角色和Java中集合类比:
抽象聚合角色和集合中的顶层接口是等同的,比如Collection接口、List、Map等接口,在这些接口中提供了集合操作的方法,也提供了获取迭代器实例的方法。(Iterable接口提供了iterator方法)
具体聚合角色和集合中具体实现类等同,比如ArrayList实现类,是实现自顶层接口List。
抽象迭代器角色和集合中接口Iterator等同的,Iterator接口声明hasNext、next、remove等方法来访问集合。
具体迭代器角色和ArrayList中的Itr类等同,该类Itr实现了Iterator接口,该方法针对ArrayList集合进行遍历实现。
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next();
void remove()
}
迭代器模式总结
优点
1、访问一个聚合对象的内容无需暴露它的内部细节。
2、遍历任务交给迭代器完成,简化聚合类。
3、封装性很好,为遍历不同的聚合结构提供了统一的接口。
缺点
增加了类的个数,增加了系统的复杂度。
应用场景
主要是对聚合对象提供统一的遍历方式。
对于聚合对象的遍历不对外暴露内部细节的场景。
迭代器模式在Java中的应用
对于Java集合中的使用。
策略模式
策略模式的介绍
策略模式定义了算法族,分别封装起来,让他们之间相互替换,让算法的变化独立于使用算法的客户端。
策略模式的类图UML:
策略模式由以下3中角色组成:
抽象策略模式(Strategy):策略类,通常是由一个接口或者是抽象类实现。
具体策略模式(ConcreteStrategy):实现自抽象策略模式,包含了相关算法和行为。
环境角色(Context):持有一个策略类的引用,最终给客户端使用。
模拟实现计算器功能:具有加减乘除特点
/**
* 模拟计算器功能
*/
public class Test1 {
public static void main(String[] args) {
//使用if else实现计算器
String value = test1("3", "4", "multiply");
System.out.println(value);
}
//计算机方法
/**
* if -else 方式实现计算机功能
*/
public static String test1(String a,String b,String operator){
String result = "不合法的操作符";
if ("add".equals(operator))result = a +"+"+b;
else if ("subtract".equals(operator)) result = a+"-"+b;
else if ("multiply".equals(operator)) result = a+"*"+b;
else if ("divide".equals(operator)) result = a+"/"+b;
return result;
}
/**
* switch 语句实现计算器功能
*/
public static String test2(String a,String b,String operator){
String result ;
switch (operator){
case "add":
result = a +"+"+b;
break;
case "subtract":
result = a +"-"+b;
break;
case "multiply":
result = a +"*"+b;
break;
case "divide":
result = a +"/"+b;
break;
default:
result = "不合法的操作";
break;
}
return result;
}
}
代码存在问题:
1、如果分支比较多,使得代码会变得臃肿,难以维护,可读性比较差。
2、如果新增加分支,则只能在源代码上做修改,违背了面向对象的开闭原则。
策略模式代码
/**
* 抽象策略角色
*/
public interface Strategy {
String operateStr(String a,String b);
}
/**
* 具体策略角色
*/
public class AddStrategy implements Strategy {
@Override
public String operateStr(String a, String b) {
return a+"+"+b;
}
}
/**
* 具体策略角色
*/
public class DivideStrategy implements Strategy {
@Override
public String operateStr(String a, String b) {
return a+"/"+b;
}
}
/**
* 环境类
*/
public class Context {
//持有策略类对象
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public String operateStr(String a,String b){
return strategy.operateStr(a,b);
}
}
策略模式的调用
public static void main(String[] args) {
Context context = new Context();
//给定具体的策略
context.setStrategy(new DivideStrategy());
String s = context.operateStr("5","6");
System.out.println(s);
}
策略模式总结
优点
1、避免了繁琐的if,swich判断逻辑。
2、符合开闭原则,扩展性好,便于维护。
缺点
1、策略如果很多的话,会造成策略类膨胀。
2、使用者必须清楚知道所有的策略类何其用途。
适用场景
针对同一类型的问题有很多的处理方案,仅仅是具体的处理方案有差别时。
策略模式一般在大量出现if/else场景下使用。
策略模式在Java中的应用
在Spring中通过容器获取对象
在Spring中容器的实现是ApplicationContext接口
ApplicationContext也提供了一个额外的功能,比提供支持国际化、事件通知等,通常总是使用ApplicationContext,很少使用BeanFactory
ApplicationContext接口常见实现类:
● ClassPathXmlApplicationContext
读取的xml配置文件放置在类路径下,优先考虑使用ClassPathXmlApplicationContext。
//获取IOC容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(“springcontext1.xml”);
● FileSystemXmlApplicationContext
读取的配置文件放置在文件系统路径下,则优先考虑使用FileSystemXmlApplicationContext。
FileSystemXmlApplicationContext context1 = new FileSystemXmlApplicationContext(“c://applacation.xml”);
● XMLWebApplicationContext
需要在web环境下读取配置信息XMLWebApplicationContext
该容器实现是具有策略模式特征
ApplicationContext是抽象策略类
FileSystemXmlApplicationContext等是具体的策略类