设计模式
设计模式七大原则
1.单一职责原则
一个类的职责只有一个,如果一个类要负责多个职责就要把这个类进行分解
2.接口隔离原则
类实现接口时应该只实现使用到的方法而不是所有方法,所以应该分解接口
3.依赖倒转原则
面向接口编程,传递一个类的时候应该传递这个类的接口或者抽象类
4.里氏替换原则
子类尽量不要重写父类的方法
5.开闭原则
最基础最重要的设计原则
ocp原则
对扩展开放,对修改关闭
6.迪米特法则
又叫 最少知道原则
迪米特法则的核心是降低类之间的耦合
陌生的类最好不要以局部变量的形式出现在类的内部。
7.合成复用原则
尽量使用合成/聚合的方式,而不是使用继承
- 类适配器(继承)–>对象适配器(聚合)
UML类图
类之间的关系
- 依赖
- 泛化(继承)
- 实现
- 关联
- 聚合和组合
依赖关系
在类中要到了对方,那么就存在依赖关系
泛化关系(继承)
其实就是继承
实现关系
A类实现了B接口,实现关系是依赖关系的特例
关联关系
聚合关系
电脑没有鼠标和显示器还是电脑.这种可以分开的就是聚合关系
组合关系
如果整体和部分是可以分开的那么就是聚合关系
如果整体和部分是不可以分开的那么就是组合关系
设计模式分类
创建型模式
单例模式
、工厂模式
、抽象工厂模式、原型模式、建造者模式
结构型模式
- 适配器模式、桥接模式、
装饰模式
、组合模式、外观模式、享元模式、代理模式
行为型模式
- 模板方法模式、命令模式、访问者模式、迭代器模式、
观察者模式
、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)
单例模式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查(提高效率)
- 静态内部类
- 枚举
工厂模式
简单工厂模式
如果这时候新加一个Pizza类,需要在多处OrderPizza进行修改,违反了ocp原则
可以创建一个工厂类,来创建Pizza对象,当我们需要创建一个新的Pizza类的时候,就只要对工厂类进行修改就行了.
通常为给工厂类一个String,工厂类返回一个对象给你
简单工厂模式的缺陷:
- 扩展性非常差,新增产品的时候,需要去修改工厂类。
工厂模式
把对象的实例化推迟到子类中
是对简单工厂模式多了一层抽象,将实例化某一类对象具体细分给对应的工厂,而不是在一个工厂里通过依赖参数
工厂方法模式应用案例
披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如 北京的奶酪pizza、北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza
抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
- 类图
小结
- 工厂模式的意义
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。 - 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
- 设计模式的依赖抽象原则
原型模式
使用Object类的clone方法进行拷贝对象
浅拷贝、深拷贝
浅拷贝使用默认的clone方法实现
- 基本数据类型和字符串,浅拷贝会直接复制一份新的,也就是值传递
- 引用数据类型,浅拷贝会进行引用传递,也就是共用同一个实例
深拷贝
-
基本数据类型和字符串,深拷贝会直接复制一份新的,也就是值传递
-
引用数据类型,深拷贝会申请内存空间,新建一份新的
-
实现方法
-
重写clone方法
先用父类Object的clone把基本数据类型和String拷贝了
然后用内部引用数据类型的clone拷贝一份引用数据类型
- 对象序列化(推荐)
先把对象序列化然后反序列化
public Object deepClone() { //创建流对象 ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { //序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //当前这个对象以对象流的方式输出 //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); DeepProtoType copyObj = (DeepProtoType)ois.readObject(); return copyObj; } catch (Exception e) { ... } finally { ... } }
-
建造者模式
传统方法建房 把产品和产品创建的过程封装在一起,耦合性增强了,所以我们加一层抽象
四个角色
- Product(产品): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口/抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一:隔离了客户与对象的生产过程,二:负责控制产品对象的生产过程
抽象工厂模式和建造者模式区别
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采
用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。 - 而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
工厂模式一般都是创建一个产品,注重的是把这个产品创建出来就行,只要创建出来,不关心这个产品的组成部分。从代码上看,工厂模式就是一个方法,用这个方法就能生产出产品。
建造者模式也是创建一个产品,但是不仅要把这个产品创建出来,还要关系这个产品的组成细节,组成过程。从代码上看,建造者模式在建造产品时,这个产品有很多方法,建造者模式会根据这些相同方法但是不同执行顺序建造出不同组成细节的产品。
工厂模式关心整体,建造者模式关心细节。
适配器模式
类适配器模式
Phone中有一个Voltage5V的接口, Client调用Phone的时候直接传入VoltageAdapter适配器就行
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
//获取到 220V 电压
int srcV = output220V();
int dstV = srcV / 44 ; //转成 5v
return dstV;
}
}
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;
- src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
- 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
对象适配器模式
-
基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
-
根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
-
对象适配器模式是适配器模式常用的一种
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口。 - 使用成本更低,更灵活。
接口适配器模式
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供
一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求 - 适用于一个接口不想使用其所有的方法的情况。
简单来说用一个抽象类去实现接口的所有方法,但是实现方法的时候都是空方法,这样再来个Client类用匿名内部类的方式就能只重写我们想重写的方法了
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
//在 AbsAdapter 我们将 Interface4 的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
//默认实现
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
//只需要去覆盖我们 需要使用 接口方法
@Override
public void m1() {
System.out.println("使用了 m1 的方法");
}
};
absAdapter.m1();
}
}
适配器模式在SpringMVC中的应用
- SpringMvc 中的 HandlerAdapter, 就使用了适配器模式
- SpringMVC 处理请求的流程回顾
- 使用 HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方
法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller,
就得修改原来的代码,这样违背了 OCP 原则。 - 代码分析+Debug 源码
总结
三种类型,根据src是以什么样的形式给到Adapter
代理模式
静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
一旦接口增加方法,目标对象与代理对象都要维护
动态代理
JDK 代理、接口代理
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
JDK 中生成代理对象的 API
代理对象工厂
public class ProxyFactory {
//维护一个目标对象,Object
private Object target;
//构造器,对 target 进行初始化
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance() {
/*
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//这里也可以让ProxyFactory实现InvocationHandler接口,然后传入this就行了
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK 代理开始~~");
//反射机制调用目标对象的方法
Object returnVal = method.invoke(target, args);
System.out.println("JDK 代理提交");
return returnVal;
}
});
}
}
调用
public class Client {
public static void main(String[] args) {
//创建目标对象
ITeacherDao target = new TeacherDao();
//给目标对象,创建代理对象, 可以转成 ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0 有$符号说明 内存中动态生成了代理对象
System.out.println("proxyInstance=" + proxyInstance.getClass());
//通过代理对象,调用目标对象的方法
//proxyInstance.teach();
//这里会去调用invoke方法
proxyInstance.sayHello(" tom ");
}
}
Cglib代理
可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴
-
静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,
这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理 -
Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将 Cglib 代理归属到动态代理。因为Cglib代理的方法是继承,所以不能对final修饰的类进行代理。
-
在 AOP 编程中如何选择代理模式:
- 目标对象需要实现接口,用 JDK 代理
- 目标对象不需要实现接口,用 Cglib 代理
-
导入jar包
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
-
在内存中动态构建子类,注意代理的类不能为 final,否则报错
java.lang.IllegalArgumentException:
-
目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
老师实体类
public class TeacherDao {
public void teach(){
System.out.println("老师授课中...");
}
}
代理工厂
package com.qiuyu.dao;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory implements MethodInterceptor {
//目标对象
private Object target;
//传入目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象: 是 target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib 代理模式 ~~ 开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib 代理模式 ~~ 提交");
return returnVal;
}
}
调用
public class Client {
public static void main(String[] args) {
//创建目标对象
TeacherDao target = new TeacherDao();
//获取到代理对象,并且将目标对象传递给代理对象
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法,触发 intecept 方法,从而实现 对目标对象的调用
proxyInstance.teach();
}
}
代理模式变种(了解)
-
防火墙代理
- 内网通过代理穿透防火墙,实现对公网的访问。
-
缓存代理
- 比 如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok, 如果取不到资源,再到公网或者数据库取,然后缓存。
-
远程代理
- 远程对象的本地代表 ,通过它可以把远程对象当本地对象来调用 。远 程代理通过网络和真正的远程对象沟通信息 。
-
同步代理:主要使用在多线程编程中,完成多线程间同步工作