配合 尚硅谷Java设计模式 学习效果更佳!视频连接
设计模式介绍
-
设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,
模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)
代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时
间的试验和错误总结出来的。 -
设计模式的本质提高 软件的扩展性,维护性,通用性和灵活性,并降低软件的复杂
度。 -
<<设计模式>> 是经典的书,作者是 Erich Gamma、Richard Helm、Ralph
Johnson 和 John Vlissides Design(俗称 “四人组 GOF”) -
设计模式并不局限于某种语言,java,php,c++ 都有设计模式
设计模式的重要性
-
软件工程中,设计模式 (design pattern) 是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学
-
大厦 VS 简易房
一栋大厦的建造需要经过各种可行性研究,结构规划,以及许多设计人员参与设计与调研才能在正确的实施。
而简易房无需那么多的步骤也可以建造成功,但是相应的在高度上,稳定性上会远远落后于大厦。 -
拿实际工作经历来说, 当一个项目开发完后,如果客户提出增新功能,怎么办?
-
如果项目开发完后,原来程序员离职,你接手维护该项目怎么办? (维护性[可读性、规范性])
-
目前程序员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么设计模式,怎样使用的,解决了什么问题。
-
设计模式在软件中哪里?面向对象(oo)=>功能模块[设计模式+算法(数据结构)] =>框架[使用到多种设计模式] => 架构 [服务器集群]
-
如果想成为合格软件工程师,那就花时间来研究下设计模式是非常必要的.
设计模式的目的
编写软件过程中,程序员面临着来自耦合性,内聚性 以及可维护性,可扩展性,重用性,灵活性 等多方面的挑战,设计模式是为了让程序(软件),具有更好的
- 代码重用性 (即:相同功能的代码,不用多次编写)
- 可读性 (即:编程规范性, 便于其他程序员的阅读和理解)
- 可扩展性 (即:当需要增加新的功能时,非常的方便,称为可维护)
- 可靠性 (即:当我们增加新的功能后,对原来的功能没有影响)
- 使程序呈现 高内聚,低耦合 的特性,
分享金句: 设计模式包含了面向对象的精髓,“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要”
Scott Mayers 在其巨著《Effective C++》就曾经说过:C++老手和 C++新手的区别就是前者手背上有很多伤疤。
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
饿汉式(静态常量)
单例模式之饿汉式(静态常量)
私有化构造器,防止外部new
类的内部创建对象
向外暴露一个公共方法获取该实例
public class Singleton01 {
private Singleton01(){}
private final static Singleton01 INSTANCE = new Singleton01();
public static Singleton01 getInstance(){
return INSTANCE;
}
}
饿汉式(静态代码块)
单例模式之饿汉式(静态代码块)
私有化构造器,防止外部new
类的内部创建对象
向外暴露一个公共方法获取该实例
public class Singleton02 {
private Singleton02(){}
private final static Singleton02 INSTANCE;
static{
INSTANCE = new Singleton02();
}
public static Singleton02 getInstance(){
return INSTANCE;
}
}
优缺点:
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始
- 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getlnstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
- 结论: 这种单例模式可用,可能造成内存浪费
懒汉式(非线程安全)
单例模式之懒汉式(非线程安全)
提供公有方法,当调用getInstance方法时,才会创建实例
public class Singleton03 {
private Singleton03(){}
private static Singleton03 instance;
public static Singleton03 getInstance(){
if(instance == null){
instance = new Singleton03();
}
return instance;
}
}
优缺点:
- 起到了Lazy Loading的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
- 结论: 在实际开发中,不要使用这种方式.
懒汉式(线程安全)
单例模式之懒汉式(线程安全)
提供公有方法,当调用getInstance方法时,才会创建实例
public class Singleton04 {
private Singleton04(){}
private static Singleton04 instance;
// 使用同步代码块,解决线程安全问题
public static synchronized Singleton04 getInstance(){
if(instance == null){
instance = new Singleton04();
}
return instance;
}
}
优缺点:
- 解决了线程不安全问题
- 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
- 结论: 在实际开发中,不推荐使用这种方式.
推荐使用的单例模式
双重检查(推荐使用)
单例模式之双重检查
提供公有方法,当调用getInstance方法时,才会创建实例
public class Singleton05 {
private Singleton05(){}
// volatile 关键字表示:
// 对变量的操作会立即更新到主存中,即该变量的改变是其它线程立即可见的
private static volatile Singleton05 instance;
// 使用双重检查机制,解决线程安全问题,同时解决懒加载懒加载问题
public static Singleton05 getInstance(){
if(instance == null){
synchronized(Singleton05.class){
if(instance == null){
instance = new Singleton05();
}
}
}
return instance;
}
}
优缺点:
- Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
- 这样,实例化代码只用执行一次,后面再次访问时,判断 if(singleton == null),直接return实例化对象,也避免的反复进行方法同步.
- 线程安全; 延迟加载; 效率较高
- 结论: 在实际开发中,推荐使用这种单例设计模式
静态内部类(推荐使用)
单例模式之静态内部类
*/
public class Singleton06 {
private Singleton06(){}
/*
静态内部类
在外部类加载的时候静态内部类不会跟随一起加载
只有在用到的时候才会加载
并且在类加载的是线程安全的
*/
private static class SingletonInstance{
private final static Singleton06 INSTANCE = new Singleton06();
}
// 使用这种方式实现单例模式既解决了线程安全问题,同时解决了懒加载问题
public Singleton06 getInstance(){
return SingletonInstance.INSTANCE;
}
}
优缺点:
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,此时装载SingletonInstance类,从而完成Singleton的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
- 结论: 在实际开发中,推荐使用这种单例设计模式
枚举(推荐使用)
public class Test{
public static void main(String[] args){
Singleton07 instance = Singleton07.INSTANCE;
instance.sayHello();
}
}
使用枚举的方式实现单例模式
enum Singleton07 {
INSTANCE;
public void sayHello(){
System.out.println("hello");
}
}
优缺点:
- 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
- 这种方式是Effective Java作者Josh Bloch提倡的方式
- 结论: 推荐使用
注意事项
工厂模式
简单工厂模式
基本介绍:
- 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
- 简单工厂模式: 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
- 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
/*
简单工厂
封装实例化对象的代码,专门用来实例化对象
如果生成对象的逻辑有变化,则直接修改工厂类即可
*/
public class SimpleFactory {
// 外部通过SimpleFactory.creatPizza()获取pizza实例
public static Pizza creatPizza(String name){
Pizza pizza = new Pizza(name);
return pizza;
}
}
mian(){
Pizza pizza = SimpleFactory.creatPizza();
}
工厂方法模式
基本介绍:
- 工厂方法模式: 定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
工厂方法模式
将具体地创建逻辑下沉到子类,由子类来决定对象应该如何创建
public abstract class OrderPizza {
// 抽象方法,由子类来决定具体地实现逻辑
abstract Pizza creatPizza();
}
public class OrderHamPizza extends OrderPizza{
@Override
Pizza creatPizza() {
return new Pizza("火腿");
}
}
public class OrderCheesePizza extends OrderPizza{
@Override
Pizza creatPizza() {
return new Pizza("芝士");
}
}
main(){
// 创建火腿pizza
OrderPizza op = new OrderHamPizza();
Pizza hamPizza = op.creatPizza();
// 创建芝士pizza
op = new OrderCheesePizza();
Pizza cheesePizza = op.creatPizza();
}
抽象工厂模式
基本介绍:
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的工厂类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
- 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。
抽象工厂模式
定义创建对象的接口,由子类工厂提供创建对象的具体逻辑
需要注意的是,当需使用工厂类时,使用者只需要聚合这个接口即可
public interface AbsFactory {
Pizza creatPizza();
}
火腿pizza工厂类
public class HamPizzaFactory implements AbsFactory{
@Override
public Pizza creatPizza() {
return new Pizza("火腿");
}
}
芝士pizza工厂类
public class CheesePizzaFactory implements AbsFactory{
@Override
public Pizza creatPizza() {
return new Pizza("芝士");
}
}
只需要聚合AbsFactory,即可使用所有的工厂子类
即把单个简单工厂变成了工厂簇
更利于代码的维护和扩展
public class OrderPizza {
这个工厂可能是火腿工厂,也可能是芝士工厂
这就将单个简单工厂变成了很多个可选的工厂子类
如果需要有新的工厂类,只需要实现AbsFactory即可使用
AbsFactory factory;
void setFactory(AbsFactory factory){
this.factory = factory;
}
Pizza order(){
return factory.creatPizza();
}
public static void main(String[] args) {
OrderPizza op = new OrderPizza();
op.setFactory(new HamPizzaFactory());
Pizza hamPizza = op.order();
op.setFactory(new CheesePizzaFactory());
Pizza cheesePizza = op.order();
}
}
总结
- 如果项目中的某个产品的种类很多,需要为每个产品对象定制不同的实现方式,那么抽象工厂是很好的选择,扩展性和灵活性都会很棒。
- 如果项目中的产品种类很少,并且将来也没有扩展的机会,那么可以选择简单工厂模式。
工厂模式的意义
- 创建对象实例时,不要直接new,而是把这个new的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
- 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
- 不要让类继承具体类,而是继承抽象类或者是实现接口(依赖倒转原则)
- 不要覆盖基类中已经实现的方法(里氏替换原则)。
原型模式
基本介绍:
- 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是: 实现Cloneable接口后利用原型对象调用clone()方法,即可拷贝出新的对象
- 注意: clone()方法使用的是浅拷贝,对于对象中的基本数据类型的变量,是进行值传递,对于引用数据类型的变量(类,数组),也是值传递(引用传递),也就是说如果对某个对象中的引用数据类型的变量进行修改,则修改会在所有对象中生效。
原型模式
即想要克隆的对象类型需要实现 Cloneable 这个接口
实现这个接口代表这个类能够克隆并且拥有克隆的能力
当需要克隆的时候直接调用clone这个方法即可
并且当对象的结构发生变化的时候,clone() 依旧是有效的
@Data
@ToString
public class Sheep implements Cloneable{
private String name;
private Integer age;
@Override
public Sheep clone() {
try {
Sheep clone = (Sheep) super.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
main(){
Sheep sheep = new Sheep();
sheep.setAge(10);
sheep.setName("小羊");
Sheep[] sheeps = new Sheep[5];
// 克隆五只一样的羊
for(int i = 0; i < 5; ++i){
sheeps[i] = sheep.clone();
}
for(Sheep s : sheeps) System.out.println(s);
}
原型模式之深拷贝
基本介绍:
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并完全复制该变量,直到所有的变量全部完成克隆。
- 深拷贝实现方式1: 重写clone方法来实现深拷贝
- 深拷贝实现方式2: 通过对象序列化实现深拷贝 (推荐),原型对象必须实现Serializable接口
深拷贝之重写clone方法
@Data
@ToString
public class Sheep implements Cloneable, Serializable{
private String name;
private Integer age;
private CloneTest test;
@Override
public Sheep clone() {
try {
Sheep sheep = (Sheep) super.clone();
// 单独对引用数据类型进行拷贝
sheep.test = (CloneTest) test.clone();
return sheep;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
class CloneTest implements Cloneable, Serializable{
String text;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深拷贝之反序列化
@Override
public Sheep clone() {
ByteArrayInputStream bis = null;
ByteArrayOutputStream bos = null;
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Sheep) ois.readObject();
} catch (Exception e) {
throw new AssertionError();
}finally {
try {
bis.close();
bos.close();
oos.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结
- 当需要创建新的对象且过程比较繁琐时,可以使用原型模式简化对象的创建方式,同时也可提高效率。
- 当使用原型模式创建对象时,无论对象结构如何变化,创建的对象总是符合当前结构。
- 当需要使用深拷贝时,可能会比较繁琐。
建造者模式
基本介绍:
- 建造者模式(BuilderPattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
建造者模式的四个角色:
- Product (产品角色):一个具体的产品对象。
- Builder(抽象建造者):创建一个Product对象的各个部件指定的接口。
- ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
- Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了对象与对象的生产过程,二是:负责控制产品对象的生产过程。
类图
适配器模式
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类: 类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式: 将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图
类适配器
目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V
输出220v的类
public class Output220V {
public int output220V(){
return 220;
}
}
输出5V的接口
public interface Output5V {
int output5V();
}
构建Adapter类让他继承Output220V 实现Output5V
在其中完成适配,即让电压降至5V
public class MyAdapter extends Output220V implements Output5V{
@Override
public int output5V() {
return output220V() / 44;
}
}
main(){
Output5V op = new MyAdapter();
op.output5V(); // 输出 5v
}
注意事项
- 由于类适配器必须继承被适配者且实现目标接口,所以使用起来有一定的局限性。
对象适配器 (常用)
基本介绍
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承被适配类,而是持有被适配类的实例,以解决兼容性的问题。即:聚合被适配类,实现目标接口,完成适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
目标:假设插孔电压220V, 手机充电需要5V, 利用类适配模式器将220V电压降至5V
其它的相较于类适配器来说都不用变,对于适配类来说用聚合关系替换继承关系即可
构建Adapter类让他实现Output5V 并聚合一个Output220V
在其中完成适配,即让电压降至5V
public class MyAdapter implements Output5V{
private Output220V op220;
public MyAdapter(Output220V op220){
this.op220 = op220;
}
@Override
public int output5V() {
return op220.output220V() / 44;
}
}
main(){
Output5V op = new MyAdapter(new Output220V());
op.output5V(); // 输出 5v
}
注意事项
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承被适配者的局限性问题。
- 使用成本更低,更灵活。
接口适配器
注意事项
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个类不想实现一个接口所有方法的情况
- 简单来说,即利用抽象类来降低类和接口间的实现难度。一个类不用去实现一个接口所有的方法,因为在抽象类这一层,已经被实现了,子类只需要重写它需要的方法即可。
享元模式
基本介绍
- 享元模式(Flyweight Pattern)也叫 蝇量模式:运用共享技术有效地支持大量细粒度的对象
- 享元模式可以这样理解,享就是共享,元就是对象,即共享对象。
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题 ,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 用唯一标识码标识对象存放在池中(一种典型的存储方式就是HashMap)。当需要使用时,如果存在于池中,则直接使用池中的对象;如果不存在,则创建后放入池中再返回该对象;享元模式可以大大减少对象的创建,降低内存的占用,提高效率。
- 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
装饰者模式
基本介绍
- 装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
- 简单来说,装饰者(Decorator)与被装饰者(Decorator / Coffee)同时继承一个抽象类(Drink),装饰者中聚合一个该抽象类(聚合一个被装饰者)。相当于装饰者套被装饰者,被装饰者继续套被装饰者。
策略模式
基本介绍
- 策略模式( Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
- 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口)﹔第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
- 当聚合了策略接口时,外部传入什么策略实例,聚合者就使用什么策略。
策略接口
public interface Speakable {
void speak();
}
策略接口
public interface FlyAble {
void fly();
}
本来可以实现speak和fly接口,然后让子类自己定义这两个行为
但是使用策略模式会更加灵活,子类也无需实现具体的方法
当子类需要更换fly逻辑时,不需要修改源码,只需要传入一个fly接口的实例,即可重写定义fly方法的逻辑
即外面传什么进来,鸭子就怎么飞
对于具体的操作,得看聚合进来的策略实例
多使用关联代替继承、实现 —— 合成复用原则
public abstract class Duck {
FlyAble flyAble; // 飞翔策略接口
Speakable sa; // 说话策略接口
void fly(){
flyAble.fly(); // 调用策略实例的fly方法,即是否能飞翔完全取决于外部传入的策略
}
void speak(){
sa.speak(); // 调用策略实例的speak方法,即是否能说话完全取决于外部传入的策略
}
public void setFlyAble(FlyAble flyAble){
this.flyAble = flyAble;
}
public void setSa(Speakable sa) {
this.sa = sa;
}
}
观察者模式
基本介绍
- 观察者模式简单来说就是 注册—通知,给定Subject和Observer,将Observer注册到对应的Subject中,然后通过Subject给所有注册的Observer发送消息。
- 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
- 这样,我们增加观察者(Observer),只需要注册进Subject即可,无需修改源码,遵守了ocp原则。
责任链模式
基本介绍
- 职责链模式( chain of Responsibility Pattern) ,又叫责任链模式,为请求创建了一个接收者对象的链。这种模式对请求的发送者和接收者进行解耦。
- 职责链模式,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.
- 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 这种类型的设计模式属于行为型模式
用责任链模式来完成器材订购案例,金额<=5000由主任批准,金额<=20000由校长批准,其它金额由董事批准
处理请求的抽象接收者,类图中的Handler
public abstract class Approve {
Approve approve; // 后继接收者
public void setApprove(Approve approve) { // 设值后继接收者
this.approve = approve;
}
public abstract void process(Request request); // 处理请求的方法
}
具体的请求
public class Request {
private int id; // 请求id
private float price; // 价格
public Request(int id, float price) {
this.id = id;
this.price = price;
}
public int getId() {return id;}
public float getPrice() {return price;}
}
具体接收者之主任,后继为校长
public class SeniorApprove extends Approve{
@Override
public void process(Request request) {
if(request.getPrice() <= 5000) {
System.out.println("订单 " + request.getId() + " 金额<=5000,由主任批准");
}else {
approve.process(request); // 主任无法处理,则传给后继接收者处理
}
}
}
具体接收者之校长,后继为董事
public class HeadmasterApprove extends Approve{
@Override
public void process(Request request) {
if(request.getPrice() <= 20000){
System.out.println("订单 "+request.getId()+" 金额<=20000,由校长批准");
}else {
approve.process(request); // 校长无法处理,传给后继接收者
}
}
}
具体接收者之董事,无后继,即所有价格在这里都可以被处理
public class DirectorApprove extends Approve{
@Override
public void process(Request request) {
System.out.println("订单 "+request.getId()+" 金额过大,由董事批准");
}
}
main(){
Approve senior = new SeniorApprove(); // 接收者之主任
Approve head = new HeadmasterApprove(); // 接收者之校长
Approve director = new DirectorApprove(); // 接收者之董事
// 构建责任链 主任的后继是校长,校长的后继是董事
senior.setApprove(head);
head.setApprove(director);
// 三种请求调用
Request request1 = new Request(1, 4000);
Request request2 = new Request(2,15000);
Request request3 = new Request(3,50000);
senior.process(request3); // 订单 3 金额过大,由董事批准
senior.process(request2); // 订单 2 金额<=20000,由校长批准
senior.process(request1); // 订单 1 金额<=5000,由主任批准
}
注意事项
模板方法模式
基本介绍
- 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern), 在一个抽象类中公开定义方法的执行流程。它的子类可以按需要重写方法实现,但调用顺序将以抽象类中定义的模板方法进行。
- 简单说,模板方法模式就是在模板方法中定义调用其它方法的执行顺序,将所需要执行的方法推迟到子类中实现,使得子类可以不改变调用方法的顺序和结构,就可以重定义该算法的某些特定步骤
- 这种类型的设计模式属于行为型模式。
类图
模板方法模式完成豆浆制作,除了选择的材料不同外,其它的步骤完全一致
public abstract class SoyMilk {
// 模板方法,即定义了算法的执行流程
void make(){
select();
soak();
add();
stir();
}
// 选择原材,制作什么类型的豆浆,就选择什么材料,由子类实现
abstract void select();
// 添加到豆浆机中
void add(){ System.out.println("添加到豆浆机中"); };
// 侵泡
void soak(){ System.out.println("侵泡原材料"); }
// 搅拌
void stir(){ System.out.println("搅拌"); }
}
黄豆制作豆浆
public class Soy extends SoyMilk{
@Override
void select() {
System.out.println("选择原材料 黄豆");
}
}
红豆制作豆浆
public class Red extends SoyMilk{
@Override
void select() {
System.out.println("选择原材料 红豆");
}
}
main(){
SoyMilk soyMilk = new Red();
soyMilk.make(); // 模板方法 制作红豆豆浆
soyMilk = new Soy();
soyMilk.make(); // 模板方法 制作黄豆豆浆
}
模板方法模式在Spring IOC容器初始化的时候会用到,refresh()方法就是模板方法,内部定义了一套容器初始化的执行流程。
代理模式
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
静态代理
静态代理在使用时,需要定义接口或者父类。被代理对象(即目标对象)与代理对象一起实现相同接口或者是继承相同父类
类图
代理对象与被代理对象一起实现的接口
public interface ITeacherDao {
void teach();
}
代理类,聚合一个被代理对象
public class TeacherDaoProxy implements ITeacherDao{
ITeacherDao dao; // 被代理对象
public TeacherDaoProxy(ITeacherDao dao){
this.dao = dao;
}
@Override
public void teach() {
System.out.println("进入代理方法");
dao.teach(); // 调用代理对象的方法, 可以再调用之前或者之后进行功能扩展
System.out.println("退出代理方法");
}
}
被代理类
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("调用了 teach 方法,开始教书");
}
}
通过调用代理对象的teach,完成对目标对象的调用
public static void main(String[] args) {
TeacherDao dao = new TeacherDao();
ITeacherDao proxy = new TeacherDaoProxy(dao); // 代理teacherDao
// 通过代理对象,调用目标方法
proxy.teach();
/* 控制台输出
进入代理方法
调用了 teach 方法,开始教书
退出代理方法
*/
}
动态代理
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
类图
获取动态代理对象的工厂类
public class ProxyFactory {
ITeacherDao teacherDao;
public ProxyFactory(ITeacherDao teacherDao){
this.teacherDao = teacherDao;
}
利用java的反射机制,动态的在内存中构建代理对象
public Object getProxyInstance(){
/*
Proxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
方法的三个参数:
loader 目标对象的ClassLoader, 利用固定的方法获取
interfaces 目标对象实现的接口, 利用固定的方法获取
InvocationHandler 事件处理 执行目标对象的方法时,会触发事件处理器,把目标对象的方法作为一个参数传入
*/
return Proxy.newProxyInstance(teacherDao.getClass().getClassLoader(),
teacherDao.getClass().getInterfaces(),
new InvocationHandler() {
@Override // method 目标方法 args 调用目标方法时,传入的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入动态代理方法");
Object returnVal = method.invoke(teacherDao ,args); // 执行目标方法
System.out.println("动态代理方法结束");
return returnVal;
}
});
}
}
main(){
TeacherDao dao = new TeacherDao();
ProxyFactory pf = new ProxyFactory(dao);
ITeacherDao proxy = (ITeacherDao) pf.getProxyInstance();
proxy.teach();
/* 控制台输出
进入动态代理方法
调用了 teach 方法,开始教书
动态代理方法结束
*/
}
Cglib代理
通过Cglib完成代理,需要引入四个jar
通过Cglib获取代理对象
public class ProxyFactory implements MethodInterceptor{
ITeacherDao teacherDao; // 被代理对象
public ProxyFactory(ITeacherDao teacherDao){
this.teacherDao = teacherDao;
}
public Object getProxyInstance(){
// 1. 创建一个工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperClass(teacherDao.getClass());
// 3.设置回调函数
enhancer.setCallback(this);
// 4.返回代理对象
return enhancer.create();
}
}
// 重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object arg0, Method method , Object[] args, MethodProxy arg3) throws Throwable{
System.out.println("Cglib代理方法开始执行");
Object returnVal = method(teacherDao, args);
System.out.println("Cglib代理方法结束");
return returnVal;
}
岁月悠悠,衰微只及肌肤;热忱抛却,颓废必致灵魂