文章目录
设计原则
开闭原则
**开闭原则定义**: 一个软件实体 如类、模块和函数应该对扩展开放,修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。强调的是用抽象构建框架,用实现扩展细节。可以提高软件的可复用性及可维护性,开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统,例如: 我们版本更新,我尽可能不修改源代码,但是可以增加新功能。
在现实生活中对开闭原则也有体现。比如,很多互联网公司都实行弹性制作息时间,规定每天工作8小时,意思就是说,对于每天工作8小时哥规定是关闭的,但是你什么时候来,什么时候走是开放的,早来早走,晚来晚走。
优点:提高软件系统的可复用性及可维护性
案例:
ICourse接口
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}
JavaCourse类
public class JavaCourse implements ICourse{
private Integer id;
private String name;
private Double price;
public JavaCourse(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
public Integer getId() {
return this.id;
}
public String getName() {
return this.name;
}
public Double getPrice() {
return this.price;
}
}
JavaDiscountCourse类
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getDiscountPrice(){
return super.getPrice() * 0.6;
}
}
测试类
public class OpenCloseTest {
public static void main(String[] args) {
ICourse iCourse = new JavaDiscountCourse(1,"Java架构",11800D);
JavaDiscountCourse discountCourse = (JavaDiscountCourse)iCourse;
System.out.println("课程ID:" + discountCourse.getId() +
"\n课程标题:《" + discountCourse.getName() + "》" +
"\n原价:" + discountCourse.getPrice() +
"\n售价:" + discountCourse.getDiscountPrice());
}
}
类图
依赖倒置原则
依赖倒置原则是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象;细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险。
优点: 可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。
ICourse 接口:
public interface ICourse {
void study();
}
JavaCourse 类:
public class JavaCourse implements ICourse{
public void study() {
System.out.println("baby 在学习 Java 课程");
}
}
PythonCourse 类:
public class PythonCourse implements ICourse{
public void study() {
System.out.println("baby 在学习 Python 课程");
}
}
Baby类:
public class Baby {
public void study(ICourse course){
course.study();
}
}
调用实现
public class DipTest {
public static void main(String[] args) {
Baby baby = new Baby();
baby.study(new JavaCourse());
baby.study(new PythonCourse());
}
}
单一职责原则
单一职责是指不要存在多于一个导致类变更的原因。假设我们有一个Class负责两个职责,一旦发生需求变更,修改其中的一个职责的逻辑代码,有可能会导致另一个职责的功能代码发生故障。这样一来,这个Class存在两个导致类变更的原因。如何解决这个问题呢?我们就要给两个职责分别用两个Class来实现,进行解耦。后期需求变更维护互不影响,这样的设计,可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险。总体来说就是一个Class/Interface/Method只负责一项职责。
一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因、是实现高内聚、低耦合的指导方针
解释:
高内聚
尽可能类的每个成员方法只完成一件事(最大限度的聚合)
模块内部的代码,相互之间的联系越强,内聚就越高,模块的独立性就越好
低耦合
减少类内部,一个成员方法调用另一个成员方法,不要有牵一发动全身
优点: 降低类的复杂度、提高类的可读性、提高系统的可维护性、降低变更引起的风险
接口隔离原则
接口隔离原则是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖他不需要的接口。这原则指导我们在设计接口时应当注意以下几点:
一个类对一类的依赖应该建在最小的接口之上。
使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度)
接口隔离原则符合我们常说的高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。
迪米特法则
最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
类之间的耦合度越低,就越有利于复用,一个处在松耦中的类一旦被修改,不会对关联的类造成太大波及
通过引入一个合理的第三者来降低现有对象之间的耦合度
里氏替换原则
任何基类可以出现的地方,子类一定可以出现
在程序中尽量使用基类类型来对对象进行定义,而在运行时在确定其子类型,用子类型对象来替换父类对象
引申含义:
子类可以实现父类的抽象方法,但不能改变父类原有的功能。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件要比父类方法的输入参数更宽松。
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出、返回值)要比父类更严格或相等。
三大设计模式
创建型模式
工厂模式
-
工厂模式介绍:
它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
-
工厂模式的实现方式
-
简单工厂模式
通过传入相关的类型来返回相应的类,这种方式比较单一,可扩展性相对较差。
- 简单工厂模式又称静态工厂方法,可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类、由于工厂方法时静态方法,可通过类名直接调用,而且只需要传入简单的参数即可。
- 核心组成
- Factory: 工厂类,简单工厂模式的核心,它负责实现创建所有实例的内部逻辑
- IProduct:抽象产品类,简单工厂模式所创建的所有对象的父类,描述所有实例所共有的公共接口
- Product:具体产品类,是简单工厂模式的创建目标
- 实现步骤
- 创建抽象产品类,里面有产品的抽象方法,由具体的产品类去实现
- 创建具体产品类,继承了他们的父类,并实现具体方法
- 创建工厂类,提供了一个静态方法createXXX用来生产产品,只需要传入你想要的产品名称
- 优点
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
- 缺点
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则相违背
- 即开闭原则、对扩展开放,对修改关闭,程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果
- 将会在增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不用模式
-
工厂方法模式
通过实现类实现相应的方法来决定相应的返回结果,这种方式的扩展性比较强
-
工厂方法模式简介
- 工厂方法模式又称工厂模式,是对简单工厂模式进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则
- 通过工厂父类定义负责创建产品的公共接口,通过子类来确定所需要创建的类型
- 相比简单工厂而言,此种方法具有更多的可扩展性和复用性,同时也增强了代码的可读性
- 将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类。
-
核心组成
-
IProduct:抽象产品类,描述所有实例所共有的公共接口
-
Product: 具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个
-
IFactory:抽象工厂类,描述具体工厂的公共接口
-
Factory:具体工厂类,实现创建产品类对象,实现抽象工厂类的接口,如果有多个需要定义多个
-
-
编码实践
/** * 抽象工厂方法 */ public interface PayFactory { Pay getPay(); }
/** * 具体产品工厂 */ public class AliPayFactory implements PayFactory { @Override public Pay getPay() { return new AliPay(); } }
/** * 抽象产品 */ public interface Pay { /** * 统一下单 */ void unifiedorder(); }
/** * 具体产品 */ public class AliPay implements Pay { @Override public void unifiedorder() { System.out.println("支付宝支付 统一下单接口"); } }
-
优点
- 符合开闭原则、增加一个产品类,只需要实现其他具体的产品类和具体的工厂类
- 符合单一职责原则,每个工厂只负责生产对应的产品
- 使用者只需要产品的抽象类,无须关系其他实现类,满足迪米特法则,依赖倒置原则和里氏替换原则
- 迪米特法则:最少知道原则,实体应当尽量少的与其他实体之间发生相互作用
- 依赖倒置原则:针对接口编程,依赖于抽象而不依赖于具体
- 里氏替换原则:俗称LSP,任何基类可以出现的地方,子类一定可以出现,对实现抽象化的具体步骤的规范
-
缺点
- 增加一个产品,需要实现对应的具体工厂类和具体产品类
- 每个产品需要有对象的具体工厂和具体产品类
- 类的个数容易过多,增加复杂度。增加了系统的抽象性和理解难度
-
-
抽象工厂模式
基于上述两种模式的扩展,且支持细化产品
-
-
应用场景
- 解耦:分离职责,把复杂对象的创建和使用的过程分开
- 复用代码降低维护成本:
- 如果对象创建复杂且多处需用到,如果没处都进行编写,则很多重复代码,如果业务逻辑发生了改变,需要四处修改;
- 使用工厂模式统一创建,则只要修改工厂类即可,降低成本
-
抽象工厂模式
单例模式
-
单例设计模式:
这个是最简单的设计模式。单例意思只包含一个对象被称为单例的特殊类
通过单例模式可以保证系统中,应用该模式的类只有一个对象实例
-
使用场景
业务系统全局只需要一个对象实例,比如发号器、redis连接对象等
Spring IOC容器中的bean默认就是单例
Spring boot中Controller、Service、Dao层中通过@Autowired的依赖注入对象默认都是单例的
-
分类
懒汉: 就是所谓的懒加载,延迟创建对象
饿汉:与懒汉相反,提前创建对象
-
实现步骤
私有化构造函数
提供获取单例的方法
建造者模式
-
建造者模式
- 四个角色
- 产品(Product): 要创建的产品类对象
- 建造者抽象(Builder): 建造者的抽象类,规范产品对象的各个组成部分的建造,一般由子类实现具体的建造过程。
- 建造者(ConcreteBuilder): 具体的Builder类,根据不同的业务逻辑,具体化对象的各个组成部分的创建。
- 调用者(Director):调用具体的建造者,来创建对象的各个部分,在指导中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
- 使用多个简单的对象一步一步构建成一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- 允许用户只通过指定复杂对象的类型和内容就可以构建它们,不需要知道内部的具体构建细节
- 四个角色
-
应用场景
- 相同的方法,不同的执行顺序,产生不同的结果时
- 多个部位或零件,都可以装配到一个对象中,但是产生的结果又不相同。
- 产品类非常复杂,或者产品类中的调用顺序不同产生不同的作用。
- 当初始化一个对象特别复杂,参数多,二期很多参数都具有默认值时。
-
场景举例
- 电脑有低配、高配,组织需要CPU、内存电源。硬盘、主板等
-
编码实战
/** * 声明了建造者的公共方法 */ public interface Builder { /** *细节方法 */ void buildCpu(); void buildMainboard(); void buildDisk(); void buildPower(); void buildMemory(); Computer createComputer(); } public class Director { public Computer craete(Builder builder){ builder.buildMemory(); builder.buildCpu(); builder.buildMainboard(); builder.buildDisk(); builder.buildPower(); return builder.createComputer(); } } public class HighComputerBuilder implements Builder{ private Computer computer = new Computer(); @Override public void buildCpu() { computer.setCpu("高配 CPU"); } @Override public void buildMainboard() { computer.setMainboard("高配 主板"); } @Override public void buildDisk() { computer.setDisk("高配 磁盘"); } @Override public void buildPower() { computer.setPower("高配 电源"); } @Override public void buildMemory() { computer.setMemory("高配 内存"); } @Override public Computer createComputer() { return computer; } }
-
优点
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解构
- 每一个集体建造者都相对独立,而与其他的具体建造者无关,更加精细地控制产品的创建过程
- 增加新的具体建造者无须修改原有类库的代码,符合开闭原则
- 建造者模式结合链式编程来使用,代码上更加美观
- 封装性好,创建和使用分离,扩展性好,建造类之间独立,一定程度上解耦
-
缺点
- 建造者模式所创建的产品一般具有较多的共同点,如果产品差异大则不建议使用
- 产生多余的Builder对象,产品部变化,建造者都要修改,成本较大
-
JDK源码的应用
- TCP传输协议protobuf生产的api、Java中的StringBuilder(不完全一样,但是思想一样)
结构型模式
适配器模式
-
适配器模式
- 适配器模式又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作属于结构型设计模式。
-
常见的几类适配器
-
类的适配器模式
-
想将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可
-
-
对象的适配器模式
-
想将一个对象转换成满足另一个接口的对象时,可以创建一个适配器类,持有原类的一个实例,在适配器类的方法中,调用实例的方法就行
-
-
接口的适配器模式
- 不想实现一个接口中所有的方法时,可以创建一个Adapter,实现所有方法,在写别的类的时候,继承Adapter类即可
-
-
应用场景
- 电脑需要读取内存卡的数据,读卡器就是适配器
- 日常使用的转接头
- JDBC就是我们用的最多的适配器模式
-
编码实践
-
类适配器模式
//目标接口 interface Target { public void request(); } //适配者接口 class Adaptee { public void specificRequest() { System.out.println("适配者中的业务代码被调用!"); } } //类适配器类 class ClassAdapter extends Adaptee implements Target { public void request() { specificRequest(); } } //客户端代码 public class ClassAdapterTest { public static void main(String[] args) { System.out.println("类适配器模式测试:"); Target target = new ClassAdapter(); target.request(); } }
-
对象适配器
//对象适配器类 class ObjectAdapter implements Target { private Adaptee adaptee; public ObjectAdapter(Adaptee adaptee) { this.adaptee=adaptee; } public void request() { adaptee.specificRequest(); } } //客户端代码 public class ObjectAdapterTest { public static void main(String[] args) { System.out.println("对象适配器模式测试:"); Adaptee adaptee = new Adaptee(); Target target = new ObjectAdapter(adaptee); target.request(); } }
-
桥接模式
-
桥接模式
- 将抽象与实现分离,使他们可以独立变化,它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度
- 通俗来说,是通过组合来桥接其它的行为/维度
-
桥接模式主要角色
-
抽象化(Abstraction)角色: 定义抽象类,并包含一个对实现化对象的引用
-
扩展抽象(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
-
实现化(Implementor)角色: 定义实现化角色的接口,供扩展抽象化角色调用
-
具体实现化(Concrete Implementor)角色: 给出实现化角色接口的具体实现
-
-
应用场景
-
当一个类存在两个独立变化的的维度,且这两个维度都需要进行扩展时
-
系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性
-
不想使用继承倒置系统类的个数急剧增加系统
-
有时候一个类,可能会拥有多个变化维度,比如啤酒,有不同的容量和品牌,可以使用继承组合的方式进行开发,假如维度很多,就容易出现类的膨胀,使用桥接模式就可以解决这个问题,且解耦
-
-
编码实践
public class BridgeTest { public static void main(String[] args) { Implementor imple = new ConcreteImplementorA(); Abstraction abs = new RefinedAbstraction(imple); abs.Operation(); } } //实现化角色 interface Implementor { public void OperationImpl(); } //具体实现化角色 class ConcreteImplementorA implements Implementor { public void OperationImpl() { System.out.println("具体实现化(Concrete Implementor)角色被访问"); } } //抽象化角色 abstract class Abstraction { protected Implementor imple; protected Abstraction(Implementor imple) { this.imple = imple; } public abstract void Operation(); } //扩展抽象化角色 class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor imple) { super(imple); } public void Operation() { System.out.println("扩展抽象化(Refined Abstraction)角色被访问"); imple.OperationImpl(); } }
装饰器模式
-
装饰器模式
-
装饰器模式的定义
- 指在不改变有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于结构型模式
-
模式的结构
- 抽象构建(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构建的实例,可以通过一些子类扩展具体构件的功能
- 具体装饰(ConcreteDecorator)角色: 实现抽象装饰的相关方法,并给具体构建对象添加附加的责任
-
模式的优点
- 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态地给一个对象扩展功能,即插即用
- 通过使用不同装饰类及这些装饰类的排列组合,可以实现不同的效果
- 装饰器模式完全遵守开闭原则
-
主要缺点
- 装饰器模式会增加许多子类,过度使用会增加程序的复杂性
-
应用场景
- 以动态、透明的方式给单个对象添加职责,但又不能改变其结构
- JDK源码里面应用最多的就是IO流,大量使用装饰器设计模式
- 远程代理:这种通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问
- 虚拟代理: 通常用于要创建的目标对象开销很大时
- 安全代理:用于控制不同种类客户对真实对象的访问权限
-
编码实践
public class DecoratorPattern { public static void main(String[] args) { Component p = new ConcreteComponent(); p.operation(); System.out.println("---------------------------------"); Component d = new ConcreteDecorator(p); d.operation(); } } //抽象构件角色 interface Component { public void operation(); } //具体构件角色 class ConcreteComponent implements Component { public ConcreteComponent() { System.out.println("创建具体构件角色"); } public void operation() { System.out.println("调用具体构件角色的方法operation()"); } } //抽象装饰角色 class Decorator implements Component { private Component component; public Decorator(Component component) { this.component = component; } public void operation() { component.operation(); } } //具体装饰角色 class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void operation() { super.operation(); addedFunction(); } public void addedFunction() { System.out.println("为具体构件角色增加额外的功能addedFunction()"); } }
-
代理模式
-
代理模式
-
代理模式的定义
- 由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- 客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象
- 为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式
-
模式的结构
-
抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法
-
真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
-
代理(Proxy)类: 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
-
-
动态代理
- 静态代理: 由代码创建代理类或特定工具自动生成源代码在对其编译,在程序运行前代理类的.class文件就已经存在了
- 动态代理:在程序运行时,运用放射机制动态创建而成
-
应用场景
- 客户端不想直接访问实际的对象,或者访问实际的对象存在困难,通过一个代理对象来完成间接的访问
- 想在访问一个类时做一些控制,或者增强功能
-
编码实践
public class ProxyTest { public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.Request(); } } //抽象主题 interface Subject { void Request(); } //真实主题 class RealSubject implements Subject { public void Request() { System.out.println("访问真实主题方法..."); } } //代理 class Proxy implements Subject { private RealSubject realSubject; public void Request() { if (realSubject == null) { realSubject = new RealSubject(); } preRequest(); realSubject.Request(); postRequest(); } public void preRequest() { System.out.println("访问真实主题之前的预处理。"); } public void postRequest() { System.out.println("访问真实主题之后的后续处理。"); } }
-
行为型模式
责任链模式
-
责任链模式
-
责任链模式定义
- 为了避免请求发送者与多个请求处理者耦合在一起,于是将所有的请求通过处理者通过前一对象记住其下一个对象引起而连成一条链,当有请求发生时,可以将请求沿着这条链路传递,直到有对象处理它为止
- 有两个核心行为:一是处理请求,二是将请求传递到下一节点
-
模式的结构角色
-
抽象处理者(handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后续连接
-
具体处理者(Concrete Handler)角色: 实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
-
客户类(Client)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
-
-
应用场景
-
Tomcat对Encoding编码处理的处理,SpringBoot里面的拦截器、过滤器链
-
Netty里面的Handler处理器就是就是使用责任链模式
-
在请求处理者不明确的情况下向多个对象中的一个提交请求
-
如果有多个对象可以处理同一个请求,但是具体由哪个对象处理是由运行时刻动态决定的,这种对象就可以使用职责链模式
-
-
模式优点
- 降低了对象之间的耦合度。改模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥对方的明确信息
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可以动态地新增或者删除责任
- 责任链简化了对象之间的连接,每个对象只需要保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的if或者if–else语句
- 责任分担,每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则
-
模式缺点
-
不能保证每个请求一定被处理,由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
-
对于较长的职责链,请求的处理可能涉及多个处理对象,系统性将受到一定影响
-
责任链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责任链的错误设置而导致系统出错,如可能会造成循环调用
-
-
编码实践
public class ChainOfResponsibilityPattern { public static void main(String[] args) { //组装责任链 Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); handler1.setNext(handler2); //提交请求 handler1.handleRequest("two"); } } //抽象处理者角色 abstract class Handler { private Handler next; public void setNext(Handler next) { this.next = next; } public Handler getNext() { return next; } //处理请求的方法 public abstract void handleRequest(String request); } //具体处理者角色1 class ConcreteHandler1 extends Handler { public void handleRequest(String request) { if (request.equals("one")) { System.out.println("具体处理者1负责处理该请求!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("没有人处理该请求!"); } } } } //具体处理者角色2 class ConcreteHandler2 extends Handler { public void handleRequest(String request) { if (request.equals("two")) { System.out.println("具体处理者2负责处理该请求!"); } else { if (getNext() != null) { getNext().handleRequest(request); } else { System.out.println("没有人处理该请求!"); } } } }
-
迭代器模式
-
迭代器模式
-
迭代器模式的定义
-
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示,迭代器模式是一种对象行为模式
-
应该是Java中应用最多的设计模式
提到迭代器,想到它是与集合相关的,集合也叫容器,可以将集合看成是一个可以包容对象的容器,例如List,Set,Map,甚至数组都可以叫做集合,迭代器的作用就是把容器中的对象一个一个地遍历出来
-
-
模式的结构角色
-
抽象聚合(Aggregate)角色: 定义存储、添加、删除聚合对象以及创建迭代器对象的接口
-
具体聚合(ComreteAggregate)角色: 实现抽象聚合类,返回一个具体迭代器的实例
-
抽象迭代器(lterator)角色: 定义访问喝遍历聚合元素的接口,通常包含hashNext()、first()、next()等方法
-
具体迭代器(Concretelterator)角色: 实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
-
-
模式优点
- 访问一个聚合对象的内容而无须暴露它的内部表示
- 遍历任务交由迭代器完成,这简化了聚合类
- 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历
- 增加新的聚合类和迭代器类都很方便,无须修改原有代码
- 封装性良好,为遍历不同的聚合结构提供一个统一的接口
-
模式缺点
-
增加了类的个数,在一定程度上增加了系统的复杂性
-
迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常
-
-
编码实践
public class IteratorPattern { public static void main(String[] args) { Aggregate ag = new ConcreteAggregate(); ag.add("中山大学"); ag.add("华南理工"); ag.add("韶关学院"); System.out.print("聚合的内容有:"); Iterator it = ag.getIterator(); while (it.hasNext()) { Object ob = it.next(); System.out.print(ob.toString() + "\t"); } Object ob = it.first(); System.out.println("\nFirst:" + ob.toString()); } } //抽象聚合 interface Aggregate { public void add(Object obj); public void remove(Object obj); public Iterator getIterator(); } //具体聚合 class ConcreteAggregate implements Aggregate { private List<Object> list = new ArrayList<Object>(); public void add(Object obj) { list.add(obj); } public void remove(Object obj) { list.remove(obj); } public Iterator getIterator() { return (new ConcreteIterator(list)); } } //抽象迭代器 interface Iterator { Object first(); Object next(); boolean hasNext(); } //具体迭代器 class ConcreteIterator implements Iterator { private List<Object> list = null; private int index = -1; public ConcreteIterator(List<Object> list) { this.list = list; } public boolean hasNext() { if (index < list.size() - 1) { return true; } else { return false; } } public Object first() { index = 0; Object obj = list.get(index); ; return obj; } public Object next() { Object obj = null; if (this.hasNext()) { obj = list.get(++index); } return obj; } }
-
观察者模式
-
观察者模式
-
观察者模式定义
- 指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,这种模式有时又称作发布-订阅模式、模型-试图模式,它是对象行为模式
-
模式角色
-
抽象主题(Subject)角色: 也叫抽象目标类,他提供了一个用于保存观察者对象的聚集类和增加,删除观察者对象的方法,以及通知所有观察者的抽象方法
-
具体主题(Concrete Subject)角色: 也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
-
抽象观察者(Observer)角色: 它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
-
具体观察者(Conerete Observer)角色: 实现抽象观察者中定义的抽象方法,以便在得到目标得更改通知时更新自身得状态
-
-
模式的优点
-
降低了目标与观察者之间的耦合关系,两者之间时抽象耦合关系,符合依赖倒置原则
-
目标与观察者之间建立了一套触发机制
-
-
模式的缺点
-
观察者和观察目标之间有循环依赖的话,会触发它们之间进行循环调用,可能导致系统奔溃
-
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
-
-
应用场景
- 消息通知里面: 邮件通知、广播通知、微信朋友圈等,就是监听观察事件
- 当一个对象的改变需要同时改变其它对象,且它不知道具体有多少对象有待改变的时候,考虑使用观察者模式
-
编码实践
public class ObserverPattern { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer obs1 = new ConcreteObserver1(); Observer obs2 = new ConcreteObserver2(); subject.add(obs1); subject.add(obs2); subject.notifyObserver(); } } //抽象目标 abstract class Subject { protected List<Observer> observers = new ArrayList<Observer>(); //增加观察者方法 public void add(Observer observer) { observers.add(observer); } //删除观察者方法 public void remove(Observer observer) { observers.remove(observer); } public abstract void notifyObserver(); //通知观察者方法 } //具体目标 class ConcreteSubject extends Subject { public void notifyObserver() { System.out.println("具体目标发生改变..."); System.out.println("--------------"); for (Object obs : observers) { ((Observer) obs).response(); } } } //抽象观察者 interface Observer { void response(); //反应 } //具体观察者1 class ConcreteObserver1 implements Observer { public void response() { System.out.println("具体观察者1作出反应!"); } } //具体观察者1 class ConcreteObserver2 implements Observer { public void response() { System.out.println("具体观察者2作出反应!"); } }
-
状态模式
-
状态模式
-
状态模式的定义
- 对有状态的对象,把复杂的”判断逻辑“提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为
-
模式的结构角色
-
环境类(Context)角色: 也称上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换
-
抽象状态(State)角色: 定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为
-
具体状态(Conerete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换
-
-
模式的优点
- 结构 清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足”单一职责原则"
- 将状态转换显示化,减少对象间的相互依赖,将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖
- 状态类职责明确。有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换
-
模式的缺点
-
状态模式的使用必然会增加系统的类与对象的个数
-
状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码混乱
-
状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改哪些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对象类的源码。
-
-
应用场景
-
一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为
-
代码中包含大量与对象状态有关的条件语句,比如一个操作中含有庞大的多分支的条件if else语句,且这些分支依赖于该对象的状态
-
电商订单状态: 未支付、已支付、派送中、收货完成等状态,各个状态下处理不同的事情
-
-
编码实践
public class StatePatternClient { public static void main(String[] args) { Context context = new Context(); //创建环境 context.Handle(); //处理请求 context.Handle(); context.Handle(); context.Handle(); } } //环境类 class Context { private State state; //定义环境类的初始状态 public Context() { this.state = new ConcreteStateA(); } //设置新状态 public void setState(State state) { this.state = state; } //读取状态 public State getState() { return (state); } //对请求做处理 public void Handle() { state.Handle(this); } } //抽象状态类 abstract class State { public abstract void Handle(Context context); } //具体状态A类 class ConcreteStateA extends State { public void Handle(Context context) { System.out.println("当前状态是 A."); context.setState(new ConcreteStateB()); } } //具体状态B类 class ConcreteStateB extends State { public void Handle(Context context) { System.out.println("当前状态是 B."); context.setState(new ConcreteStateA()); } }
-
策略模式
-
策略模式
-
策略模式定义
- 通过定义一系列算法,并将每个算法封装 起来,使他们可以相互替换,且算法的变化不会影响使用算法的客户,策略模式属于对象欣慰模式,它通过对算法进行封装,吧使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理
-
模式的结构角色
-
抽象策略(Strategy)类: 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
-
具体策略(Concrete Strategy)类: 实现了抽象策略定义的接口,提供具体的算法实现
-
环境(Context)类: 持有一个策略类的引用,最终给客户端调用
-
-
模式的优点
- 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如if…else语句、switch…case语句
- 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码
- 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的
- 策略模式提供了开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法
- 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略中,实现了二者的分离
-
模式的缺点
-
客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类
-
策略模式可以造成很多的策略类,增加维护难度
-
-
应用场景
-
如果在一个系统里面有许多类,它们之间的区别仅在于行为,那么可以使用策略模式(例如支付方式)
-
不希望暴露复杂的,与算法有关的数据结构,那么可以使用策略模式来封装算法
-
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
-
-
编码实践
public class StrategyPattern { public static void main(String[] args) { Context c = new Context(); Strategy s = new ConcreteStrategyA(); c.setStrategy(s); c.strategyMethod(); System.out.println("-----------------"); s = new ConcreteStrategyB(); c.setStrategy(s); c.strategyMethod(); } } //抽象策略类 interface Strategy { public void strategyMethod(); //策略方法 } //具体策略类A class ConcreteStrategyA implements Strategy { public void strategyMethod() { System.out.println("具体策略A的策略方法被访问!"); } } //具体策略类B class ConcreteStrategyB implements Strategy { public void strategyMethod() { System.out.println("具体策略B的策略方法被访问!"); } } //环境类 class Context { private Strategy strategy; public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void strategyMethod() { strategy.strategyMethod(); } }
-
模板模式
-
模板模式
-
模板模式定义
- 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得之类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,它是一种行为型模式
-
模式的结构
-
抽象类/抽象模板(Abstract Class): 抽象模板类,负责给出一个算法的轮廓和骨架
- 模板方法
- 定义了算法的骨架,按某种顺序调用其包含的基本方法
- 基本方法: 是整个算法中的一个步骤,包含以下几种类型
- 抽象方法: 在抽象类中声明,由具体子类实现
- 具体方法: 在抽象中已经实现,在具体子类中可以继承或重写它
- 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种
- 模板方法
-
具体子类/具体实现(Conerete Class)
- 具体实现类
- 实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤
- 具体实现类
-
-
模板模式的优点
- 它封装了不变部分,扩展可变部分,它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展
- 它在父类中提取了公共的部分代码,便于代码复用
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则
-
模板模式的缺点
-
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计由更加抽象,间接地增加了系统实现的复杂度
-
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度
-
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有的子类都要改一遍
-
-
应用场景
-
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现
-
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码重的不同之处,并且将不同之处分离为新的操作,最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
-
当需要控制子类的扩展使,模板方法只在特定点调用钩子 操作,这样就只允许在这些点进扩展
-
-
代码实践
public class HookTemplateMethod { public static void main(String[] args) { HookAbstractClass tm = new HookConcreteClass(); tm.TemplateMethod(); } } //含钩子方法的抽象类 abstract class HookAbstractClass { //模板方法 public void TemplateMethod() { abstractMethod1(); HookMethod1(); if (HookMethod2()) { SpecificMethod(); } abstractMethod2(); } //具体方法 public void SpecificMethod() { System.out.println("抽象类中的具体方法被调用..."); } //钩子方法1 public void HookMethod1() { } //钩子方法2 public boolean HookMethod2() { return true; } //抽象方法1 public abstract void abstractMethod1(); //抽象方法2 public abstract void abstractMethod2(); } //含钩子方法的具体子类 class HookConcreteClass extends HookAbstractClass { public void abstractMethod1() { System.out.println("抽象方法1的实现被调用..."); } public void abstractMethod2() { System.out.println("抽象方法2的实现被调用..."); } public void HookMethod1() { System.out.println("钩子方法1被重写..."); } public boolean HookMethod2() { return false; } }
-
操作,这样就只允许在这些点进扩展
-
代码实践
public class HookTemplateMethod { public static void main(String[] args) { HookAbstractClass tm = new HookConcreteClass(); tm.TemplateMethod(); } } //含钩子方法的抽象类 abstract class HookAbstractClass { //模板方法 public void TemplateMethod() { abstractMethod1(); HookMethod1(); if (HookMethod2()) { SpecificMethod(); } abstractMethod2(); } //具体方法 public void SpecificMethod() { System.out.println("抽象类中的具体方法被调用..."); } //钩子方法1 public void HookMethod1() { } //钩子方法2 public boolean HookMethod2() { return true; } //抽象方法1 public abstract void abstractMethod1(); //抽象方法2 public abstract void abstractMethod2(); } //含钩子方法的具体子类 class HookConcreteClass extends HookAbstractClass { public void abstractMethod1() { System.out.println("抽象方法1的实现被调用..."); } public void abstractMethod2() { System.out.println("抽象方法2的实现被调用..."); } public void HookMethod1() { System.out.println("钩子方法1被重写..."); } public boolean HookMethod2() { return false; } }