设计模式重要性体现
软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的问题,所提出的解决方案。
- 拿实际工作经历来说, 当一个项目开发完后,如果客户提出增新功能,怎么办?。
- 如果项目开发完后,原来程序员离职,你接手维护该项目怎么办? (维护性[可读性、规范性])
- 目前程序员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么
设计模式,怎样使用的,解决了什么问题。 - 如果想成为合格软件工程师,那就花时间来研究下设计模式是非常必要的
设计模式目的
编写软件过程中,程序员面临着来自 耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序,具有更好
设计模式的7大原则
- 单一职责原则
- 接口隔离原则
- 依赖倒转(倒置)原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
单一职责原则
一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2
接口隔离原则
客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖(使用)应该建立在最小的接口上。
案例分析
类A通过接口依赖B,但只会使用1,2,3方法;类C通过接口依赖D,但只会使用1,4,5方法;接口对于A,C来说都不是最小接口,所以类B,D应该去实现A,C需要的方法,不需要的方法不用实现
依赖倒转原则
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转原则的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的
多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象
指的是接口或抽象类,细节就是具体的实现类
案例分析
依赖关系传递的3种方式
- 接口传递
- 构造方法传递
- 通过setter方法传递
里氏替换原则
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可
以通过聚合,组合,依赖 来解决问题。.
解决方法
在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.
开闭原则
一个软件实体,对扩展开放,对修改关闭
案例分析:
迪米特法则
迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public 方法,不对外泄露任何信息
合成复用原则
尽量使用合成、聚合的方式,而不是使用继承
UML(unified modeling language)统一建模语言
定义:用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系
类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合
举例:
继承
带空心箭头的实线线表示,箭头指向父类
实现
用空心箭头的虚线表示,箭头指向父类
依赖关系
定义:只要是在类中使用到了对方,那么他们之间就存在依赖关系,用带虚线的三角箭头表示,箭头指向被使用者。
案例:
关联关系
定义:关联关系实际上就是类与类之间的联系,用带三角箭头的实线表示
案例:
聚合关系
定义:表示的是整体和部分的关系,整体与部分可以分开。如:一台电脑由键盘(keyboard)、显示器(monitor),鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来的,使用带空心菱形的实线表示。
案例:
组合关系
定义:表示的是整体和部分的关系,整体与部分不可以分开。如:在程序中我们定义实体,Person与IDCard、Head, 那么 Head 和Person 就是 组合,IDCard 和 Person 就是聚合。使用带实心菱形的实现表示。
案例:
单例模式–singleton
定义:保证在内存中只有一个实例
饿汉式
类加载到内存中,就实例化一个单例,JVM保证线程安全。
简单实用,推荐使用
唯一缺点:不管用到与否,类加载时就完成实例化
Mgr01.java:
package com.leetecode.designpattern.hungry;
//饿汉式
public class Hungry {
//一开始就初始化对象
private static final Hungry h=new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return h;
}
public static void main(String[] args) {
Hungry instance1 = Hungry.getInstance();
Hungry instance2 = Hungry.getInstance();
System.out.println(instance1==instance2);
}
}
运行结果:
懒汉式
虽然达到按需初始化的目的,但是却带来线程不安全的问题
方式一 :线程不安全
package com.leetecode.designpattern.hungry;
//非线程安全
public class Lazzy {
//一开始不创建对象,需要的时候再创建对象
private static Lazzy l=null;
private Lazzy(){}
public static Lazzy getInstance(){
if(l==null) {
l = new Lazzy();
}
return l;
}
public static void main(String[] args) {
}
}
运行结果:
方式二:双重检查,不影响效率
package com.leetecode.designpattern.hungry;
//线程安全,懒汉式
public class Lazzy2 {
private static Lazzy2 l=null;
private Lazzy2(){}
public static Lazzy2 getInstance(){
if(l==null){
synchronized (Lazzy2.class){
if(l==null){
l=new Lazzy2();
}
}
}
return l;
}
}
方式三:完整的多线程环境下的单例模式的实现代码如下(面试必会)
package com.leetecode.designpattern.hungry;
public class Lazzy3 {
private static volatile Lazzy3 l=null;
private Lazzy3(){}
public static Lazzy3 getInstance(){
if(l==null){
synchronized (Lazzy3.class){
if(l==null){
l=new Lazzy3();
}
}
}
return l;
}
}
静态内部类式
外部类加载的时候,不会加载内部类,因此不会一开始就创建单一实例
public class Mgr05 {
private Mgr05(){}
//静态内部类
private static class mgr05Holder{
private static final Mgr05 INSTACE=new Mgr05();
}
public static Mgr05 getInstance(){
return mgr05Holder.INSTACE;
}
public static void main(String[] args) {
for(int i=0;i<100;++i){
new Thread(()->{ //java8行特性--lamda表达式
System.out.println(Mgr05.getInstance().hashCode());
}
).start();
}
}
运行结果:
枚举式
既可以解决线程同步,还可以防止反序列化
public enum Mgr06 {
INSTANCE;
public static void main(String[] args) {
for(int i=0;i<100;++i){
new Thread(()->{ //java8行特性--lamda表达式
System.out.println(Mgr05.getInstance().hashCode());
}
).start();
}
}
}
运行结果:
策略模式-strategy
定义:定义算法族,分别封装起来,让他们之间可以相互替换
Dog对象:
public class Dog {
int food;
public Dog(int food) {
this.food = food;
}
@Override
public String toString() {
return "Dog{" +
"food=" + food +
'}';
}
}
Cat对象:
public class Cat {
int weight, height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
排序:
public class Sorter<T> {
public void sort(T[] arr, Comparator<T> comparator) {
for(int i=0; i<arr.length - 1; i++) {
int minPos = i;
for(int j=i+1; j<arr.length; j++) {
minPos = comparator.compare(arr[j],arr[minPos])==-1 ? j : minPos;
}
swap(arr, i, minPos);
}
}
void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
接口:
public interface Comparator<T> {
int compare(T o1, T o2);
default void m() {
System.out.println("m");
}
}
Dog-food比较策略:
public class DogComparator implements Comparator<Dog> {
@Override
public int compare(Dog o1, Dog o2) {
if(o1.food < o2.food) return -1;
else if (o1.food > o2.food) return 1;
else return 0;
}
}
Cat-Height比较策略:
public class CatHeightComparator implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
if(o1.height > o2.height) return -1;
else if (o1.height < o2.height) return 1;
else return 0;
}
}
Cat-Weight比较策略:
public class CatWeightComparator implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
if(o1.weight < o2.weight) return -1;
else if (o1.weight > o2.weight) return 1;
else return 0;
}
}
Dog测试类:
public class Main {
public static void main(String[] args) {
Dog[] a = {new Dog(3), new Dog(5), new Dog(1)};
Sorter<Dog> sorter = new Sorter();
sorter.sort(a, new DogComparator());
System.out.println(Arrays.toString(a));
}
}
Cat测试类:
public class Main {
public static void main(String[] args) {
Cat[] a = {new Cat(3, 3), new Cat(5, 5), new Cat(1, 1)};
Sorter<Cat> sorter = new Sorter();
sorter.sort(a, new CatWeightComparator());
System.out.println(Arrays.toString(a));
}
}
运行结果:
工厂模式-factory
核心本质:实例化对象不使用new,用工厂方法代替
简单工厂模式
用来生产同一等级中的任意产品(对于新增加其他产品,需要修改已有代码)
工厂方法模式
用来生产同一等级中的任意产品(对于新增加其他产品,需要扩展代码)
抽象工厂模式
抽象工厂模式和工厂方法模式的区别:
工厂方法 | 抽象工厂 |
---|---|
针对的是同一等级产品结构,如:特斯拉、五菱汽车、宝马、奔驰等 | 针对的是多个等级产品结构,如:汽车、手机、口罩、吹风机、衣服等 |
原型模式—prototype
浅克隆
浅克隆是使用默认的clone()方法实现。
原理图:
案例:
深克隆
深克隆进行重写clone()方法
原理:
案例:
细节:spring中bean的创建就使用到原型模式。
适配器模式
定义:将某个类的接口转换成客户端期望的接口表示,主要目的兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作,其别名叫做Wrapper
分为3类:类适配器模式、对象适配器模式(常用)、接口适配器模式
对象适配器优点:
可以把多个适配者适配到一个目标中
案例:
桥接模式
定义:它是将抽象部分与实现部分分离,使它们都可以独立的变化,又称为接口模式。
应用场景有:虚拟机实现平台跨平台、JDBC驱动程序
原理图:
案例:
使用桥接模式后,在品牌和类型之间搭建桥梁,创建品牌或者类型互不影响
静态代理模式
案例:
动态代理模式
动态代理分为两大类:
- 基于接口的动态代理—JDK动态代理
- 基于类的动态代理—cglib
动态代理优点:
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
- 一个动态代理类代理的是一个接口,一般是对应的一类业务
- 公共业务发生扩展的时候,方便集中管理
案例:
万能模板:
建造者模式
定义:用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态的创建具有复合属性的对象。
原理图:
案例:
组合模式
定义:又叫整体与部分模式,它创建对象组的树形结构,将对象组合成树形结构以表示“整体-部分”的层次关系。
使用场景:hashmap中
适用场景:需要遍历的组织机构或者处理的对象具有树形结构时,非常适合使用组合模式
原理图:
案例:
外观模式
定义:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,用以屏蔽子系统内部的细节,使得调用端只需跟这个接口发生调用而无需关心子系统内部细节,这个接口使得子系统更加容易使用
使用场景:mybatis中
案例:
享元模式
定义:运用共享技术有效地支持大量细粒度对象,享元模式能够解决重复对象的内存浪费问题,当系统中有大量相似对象,需要缓冲池时,不用总是创建对象,可以从缓冲池里面拿,这样可以降低系统内存,提高效率。
比如围棋、五子棋、跳棋,他们有大量的棋子对象,围棋和五子棋只有黑白两种颜色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态,可以共享;而各个棋子之间的差别就是位置不同,当我们落子后,棋子颜色是固定的,但位置是变化的,所以棋子坐标就是外部状态状态,不可共享的。
内部状态:对象共享出来的信息,存储在享元对象内部,且不会随着环境的改变而改变
外部状态:对象得以依赖的的一个标记,是随着环境改变而改变,不可共享的状态
应用场景:string常量池、数据库连接池、缓冲池技术、Integer的valueOf()方法。
案例:
Integer中享元模式分析:
如果 Integer.valueOf(x) x 在 -128 — 127 直接,就是使用享元模式返回,如果不在则仍然 new
模板模式
定义:在一个抽象类公开定义了执行它的方法模板后,它的子类可以按需要重写的方法实现,但调用将以抽象类中定义的方式进行。
应用场景:IOC中
原理图:
案例分析:
模板模式之钩子方法
在模板方法模式父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为钩子
案例分析:
命令模式
定义:在软件设计中我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者,可以使用命令模式,在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求,同时命令模式也支持可撤销的操作。
如:将军发送命令,士兵去执行,其中有几个角色,将军、士兵、命令
原理图:
案例分析:
访问者模式
定义:封装一些作用于某种数据结构各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。主要将数据结构和数据操作分离,解决数据结构和数据操作的耦合性。
基本原理:在被访问的类里面加一个对外提供接待访问者的接口
优点:
- 访问者模式符合单一职责原则,让程序具有优秀的扩展性、灵活性非常高
- 访问者模式可以对功能进行统一,可以做报表、UI 拦截器与过滤器,适用于数据结构相对稳定的系统
缺点:
- 访问者关注其他类的内部细节,这是迪米特法则不建议的,造成了具体元素变更比较困难
- 违背了依赖倒转原则
- 如果一个系统有比较稳定的数据结构,又有经常变化的功能,那么访问者模式比较合适。
案例分析:
public abstract class Action {
//得到男性 的测评
public abstract void getManResult(Man man);
//得到女的 测评
public abstract void getWomanResult(Woman woman);
}
public class Success extends Action {
@Override
public void getManResult(Man man) {
System.out.println(" 男人给的评价该歌手很成功 !");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println(" 女人给的评价该歌手很成功 !");
}
}
public class Fail 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);
}
}
//数据结构,管理很多人(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 static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//成功
Success success = new Success();
objectStructure.display(success);
Fail fail = new Fail();
objectStructure.display(fail);
System.out.println("=======给的是待定的测评========");
Wait wait = new Wait();
objectStructure.display(wait);
}
运行结果:
中介模式
概述:一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那同事类之间的关系将变为星型结构,从下图中可以看到,任何一个类的变动,只会影响的类本身,以及中介
者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属
于自己的行为。
定义:定义一个中介角色来封装一系列对象之间的交互,使原有的对象之间耦合松散,且可以独立改变它们之间的交互。
案例分析:
租房者通过中介租房子,房主通过中介出租房子
运行结果:
优点
- 松散耦合
通过把多个对象之间的交互封装到中介者对象里面,从而使同事对象之间松散耦合,基本上可以做到互补依赖,这样一来,同事对象之间独立的变化和复用,就不再牵一处动全身了。 - 集中控制交互
多个同事对象的交互,被封装在中介者对象里集中管理,使得这些交互行为发生变化,只需要修改中介对象就可以,如果是已经做好了的系统,那么就扩展中介对象 - 一对多关联变成一对一关联
没有使用中介模式,同事对象之间是一对多的,引入中介模式后,中介对象和同事对象关系变成一对一。
缺点
当同事类太多时,中介职责很大,它会变得复杂而庞大,以至于系统难以维护
使用场景
- 系统对象之间存在复杂的引用关系,系统结构混乱且难以理解
- 当相创建一个运行于多个类之间的对象,又不想生成新的子类时
迭代器模式
定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示
案例:
定义一个可以存储学生对象的容器对象,将遍历该容器的功能交给迭代器实现
运行结果:
优点
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中是需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式
- 迭代器简化了聚合类
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无需修改原有代码,满足开闭原则
缺点
增加了类的个数,这在一定程度上增加了系统的复杂度
使用场景
- 当需要为聚合对象提供多种遍历方式时
- 当需要为遍历不同的聚合结构提供一个统一接口时
- 当访问一个聚合对象的内容而无需暴露内部细节时
迭代器模式在JDK中的使用
注意:当我们在使用java开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现Iterable并实现其中的iterator()方法并返回一个Iterator实现类就可以
备忘录模式
概述:备忘录模式提供了一种状态恢复实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销操作,如:记事本、word、photoshop、idea;还有在浏览器中的后退键、数据库事务中管理中的回滚操作、玩游戏时的存档功能,数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后需要时能将该对象恢复到原先保存的状态。
白箱备忘录模式
案例分析:
游戏中某个场景,游戏角色有生命力、攻击力、防御力等数据,在打boss前后一定会不一样,我们允许玩家如果感觉与boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
运行结果:
黑箱备忘率模式
运行结果:
优点
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便的将数据恢复到某个历史的状态
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
缺点
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源
使用场景
- 需要保存与恢复数据的场景,如:玩游戏的中间结果存档功能
- 需要提供一个可回滚操作的场景,如:word、记事本、photoshop等
解释器模式
概述:设计一个软件进行加减运算,我们第一想法就是使用工具类,提供对应的加减法工具方法
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式,比如:1+2+3、1+2、1+2+3+5等。显示,需要一种翻译识别机器,能够解析由数字以及±符号构成的合法运算序列。如果把运算符和数字都看做节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的文字。
在解释器模式中,我们需要将待解决问题,提取出规则,抽象为一种语言。比如:加减法运算,规则为,由数值和±符号组成的合法序列,1+3+2就是这种语言的句子。
案例分析
设计实现加减法的软件
运行结果:
优点
- 易于改变和扩展文法
- 实现文法较为容易
- 增加新的解释表达式较为方便
缺点
- 对于复杂文法难以维护
- 执行效率较低
使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时
- 当问题重复出现,且可以用一种简单的语言来表达时
- 当一个语言需要解释执行,且语言中的句子可以表示为一个抽象语法树时
状态模式
概述
通过按钮来控制一个电梯的状态,一个电梯有开门状态、关门状态、停止状态、运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。
问题分析
- 使用了大量的switch…case(if…else)这样的判断,使程序的可阅读性大大降低
- 扩展性差,如果新增断电、维修状态,我们需要修改下面判断逻辑
定义
对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
案例分析
优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便的增加新的状态,只需要改变对象状态即可改变对象行为
- 允许状态转换逻辑与状态对象合成一体
缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序和代码混乱
- 状态模式对“开闭原则”的支持不太友好
使用场景
- 当一个对象的行为取决于他的状态,并且他必须在运行时根据状态改变它的行为,就可以考虑使用状态模式
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象状态时
责任链模式
概述
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。
定义
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递下去,知道有对象处理它为止。
案例分析
现需要开发一个请假流程控制系统。请假一天以下的假只需要组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。
运行结果:
优点
- 降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。 - 增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。 - 增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。 - 责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的if 或者if.·*olse 语句。 - 责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。
由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的未端都得不到处理。 - 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责链的错误设置而导致系统出错,如可能会造成循环调用。
装饰者模式
概述
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。
定义
再不改变现有结构的情况下,动态的给该对象增加一些职责(即既增加一些额外功能)
案例
使用装饰者模式对快餐店进行改进
优点
- 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合同的装饰者对象来获取具有不同行为状态的多样化结果
- 装饰者类和被装饰者类可以独立发展,不会相互耦合,装饰者模式是继承的一个替代模式,装饰者模式可以动态扩展一个实现类的功能。
使用场景
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时
不能采用继承情况有2类:
第一类,系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
第二类, 类被final修饰。
- 当对象的功能要求可以动态的添加,也可以再动态的撤销时
装饰者模式在JDK中的应用
IO流中的包装类使用到了装饰者模式,BufferedInputStream,BufferedOutput
Stream,BufferedReader,BufferedWriter