![](https://img.haomeiwen.com/i9007799/8449f39cb03135b5.png)
![](https://img.haomeiwen.com/i9007799/ba56c3a3d9789c2c.png)
1 设计模式的设计原则(6大原则)
- 单一职责原则
一个方法、一个类、一个微服务子系统都只有一个职责 - 高内聚低耦合
单个类或子系统内聚程度要高、专注解决一个问题,彼此之间的耦合性要低,便于微服务拆分、扩展、重构等 - 开闭原则
一个类应该对扩展开放,对修改关闭 - 依赖倒置原则
要依赖抽象,不要依赖具体类,面向接口编程而不是面向实现编程 - 里氏替换原则
子类型能够正确替换父类型,对程序功能没影响,反之不行,继承和多态的体现。 - 迪米特原则
最小知识原则,一个类的调用者或依赖者只需要知道它所需要的方法即可,其他的知道的越少越好,类与类之间耦合性越小越好。
2 设计模式分类
设计模式起源于建筑行业,1995年四人帮GoF提出了23种设计模式。大致可以分为三类:
- 创建型设计模式(5个):描述对象实例化过程
- 抽象工厂模式(Abstract Factory)
- 工厂方法模式(Factory Method)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 单例模式(Singleton)
- 结构型设计模式(7个):描述如何组装类和对象以获得更大的结构
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
- 行为型设计模式(11个):描述算法和对象间职责的分配
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
3 工厂模式
3.1 简单工厂/静态工厂
创建型模式,其实不算做23种GoF中的设计模式。根据不同的参数返回不同类的实例。一般被创建的实例都有共同的父类。
3.2 工厂方法模式
- 定义接口,父类延迟到子类实现,子类选择实例化哪个类
- 工厂方法模式是针对单一产品,抽象工厂模式是针对多个产品
3.3 抽象工厂模式
- 定义接口,父类延迟到子类实现,子类选择实例化哪个类
- 工厂方法模式是针对单一产品,抽象工厂模式是针对多个产品
4 单例模式
4.1 两种方式
懒汉式:空间换时间,延迟加载,线程非安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
恶汉式:时间换空间,线程安全,利用了static的特性,只会在JVM加载类的时候初始化一次,不会出现并发
public class Singleton {
// 4.定义一个静态变量存储创建的实例,直接new实例,主动利用static特性(static变量在JVM加载类的时候初始化一次,不会出现并发)
private static Singleton instance = new Singleton();
// 1.私有化构造方法
private Singleton() {
}
// 2.提供一个方法返回实例
// 3.这个方法要定义成类方法,要加上static
public static Singleton getInstance() {
// 5.直接返回这个实例
return instance;
}
}
4.2 懒汉式线程安全问题
- 恶汉式是线程安全的,由JVM保证的,JVM加载类的时候不会出现并发
- 懒汉式是线程非安全的,可以进行如下优化:
synchronized同步方法
public class Singleton {
// 4.定义一个变量存储创建的实例
// 5.因为要在静态方法中使用,所以被迫加上static,并非主动使用static的特性
private static Singleton instance = null;
// 1.私有化构造方法
private Singleton() {
}
// 2.提供一个方法返回实例
// 3.这个方法要定义成类方法,要加上static
public static synchronized Singleton getInstance() {
// 6.判断是否有实例
if (null == instance) {
// 6.1 没有就创建一个实例
instance = new Singleton();
}
// 6.2 如果有直接使用
return instance;
}
}
synchronized同步代码块,双重检查加锁
public class Singleton {
// 4.定义一个变量存储创建的实例
// 5.因为要在静态方法中使用,所以被迫加上static,并非主动使用static的特性
private volatile static Singleton instance = null;
// 1.私有化构造方法
private Singleton() {
}
// 2.提供一个方法返回实例
// 3.这个方法要定义成类方法,要加上static
public static Singleton getInstance() {
// 6.判断是否有实例
if (null == instance) {
// 6.1 没有就创建一个实例
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
// 6.2 如果有直接使用
return instance;
}
}
静态内部类+多线程缺省同步锁
- 没有用synchronized,效率高,JVM保证线程安全
public class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
- 存在反射攻击
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newSingleton = constructor.newInstance();
System.out.println(singleton == newSingleton);
}
- 存在反序列化攻击
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
public class Singleton implements Serializable {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
byte[] serialize = SerializationUtils.serialize(instance);
Singleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(instance == newInstance);
}
}
枚举
- JVM保证线程安全
- 自动支持序列化机制,防止多次实例化
- 防止反射机制破坏单例
- 防止反序列化时破坏单例
public enum Singleton {
// 定义一个枚举元素,代表Singleton的唯一实例
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
public class User {
//私有化构造函数
private User(){ }
//定义一个静态枚举类
static enum SingletonEnum{
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private User user;
//私有化枚举的构造函数
private SingletonEnum(){
user=new User();
}
public User getInstnce(){
return user;
}
}
//对外暴露一个获取User对象的静态方法
public static User getInstance(){
return SingletonEnum.INSTANCE.getInstnce();
}
}
public class Test {
public static void main(String [] args){
System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());
}
}
结果为true
- 调用枚举单例模式
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
5 枚举为什么能实现单例模式?
- 枚举是一组有限常量集,java枚举本质上是int值,构造器私有,只能自己实例化,所有的实例就是枚举定义的几个,其他地方不允许创建实例。
6 枚举为什么不能继承?
- 可以实现接口,但不能继承类,因为所有枚举类都继承自java.lang.Enum(由编译器添加),同时java不支持多继承。
7 枚举为什么是线程安全的?
- 枚举类在第一次被使用时会被JVM加载并初始化,由JVM层面保证线程安全。
8 为什么反序列化枚举类型也不会创建新的实例?
- 普通类的反序列化是通过反射实现的,枚举类的反序列化不是通过反射实现的。源码基于jdk1.8
- 枚举类型在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
9 原型模式
- 原型模式是创建重复的对象,对对象的属性的完全复制,包括基本类型属性和引用类型的对象的属性的安全复制。当直接创建对象代价比较大时,可以采用这种模式。
- 浅拷贝
一般通过clone实现,引用类型的属性复制的是引用,而不是新的对象。原型对象需要实现java.lang.Cloneable。 - 深拷贝
- 可以通过手动进行set赋值,引用对象类型的属性,先创建该对象再赋值。
- 通过序列化的方式进行赋值。对象需要实现java.io.Serializable。
- 浅拷贝
10 建造者模式 / 生成器模式
-
建造者模式将一个复杂对象的构建和表示分离,分离整体构建算法Director和部件构造Builder,也叫创建者模式或生成器模式。
-
Client---------》Director--------》Builder
- Director负责整体构建算法,相对不变的部分,变化的部分要分离出去;
- 生成器Builder负责变化的部分,负责构建的具体实现
-
优点:
- 使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 具体的建造者类之间是相互独立的,这有利于系统的扩展。
- 具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。
-
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
11 代理模式(Proxy)
- Client----------代理对象Proxy-----------目标对象Target,为其他对象提供一种代理以控制对这个对象的访问。开闭原则,中介隔离。
静态代理:编译时就创建代理对象
- 优点
- 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
- 缺点:
- 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。
- 一旦接口增加方法,目标对象与代理对象都要维护。
动态代理 / JDK动态代理
- 动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。
- 常说的动态代理也叫做JDK代理也是一种接口代理,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect,JDK动态代理本质是基于java的反射机制,适用于有接口的类
- 实现JDK动态代理要implements InnovationHandler并重写invoke方法
- 代理对象不需要实现接口,但是被代理的目标对象一定要实现接口,否则不能使用动态代理,这是JDK动态代理的缺陷。
CGlib动态代理
- CGlib动态代理不需要被代理的目标对象必须实现接口,CGlib动态代理本质是基于ASM字节码操纵技术,适用于没有接口的类。
- CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
- CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
12 适配器模式(Adapter)
![](https://img.haomeiwen.com/i9007799/c39e838b0d9c0781.png)
- 转换适配,复用功能。尽量重构,少用适配。
- 新老接口兼容问题,中国和美国的插座转换问题都可以用适配器模式。
13 外观模式 / 门面模式(Facade)
![](https://img.haomeiwen.com/i9007799/52470429fc08b199.png)
14 装饰模式(Decorator)
- 动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
15 桥接模式(Bridge)
-
将抽象部分和实现部分分离,使他们都能独立的变化。桥接模式是为了解决继承的缺陷而提出的设计模式。
-
Java中的实现:JDBC,各种驱动
-
优点
- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
-
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
16 组合模式(Composite)
![](https://img.haomeiwen.com/i9007799/7fde34fcae126f49.png)
- 不用区分组合对象和叶子对象,设计一个抽象类,来统一这两个对象
- 表示对象的部分-整体层次结构用组合模式
17 享元模式(Flyweight)
![](https://img.haomeiwen.com/i9007799/6d48665f3f86b148.png)
- 池化技术是享元模式的典型应用,运用共享技术有效地支持大量细粒度的对象,共享、池化(String常量池、数据库连接池、缓冲池)、缓存
- 享元模式的本质是分离与共享,重点在于分离变与不变。把一个对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是可变,通过共享不变的部分,达到减少对象数量并节约内存的目的。
18 观察者模式(Observer)
- 观察者模式又叫做发布/订阅模式Publish/Subscribe
- 被观察者(notify)---------》观察者1/2/.../N(update)
- 推模式和拉模式
- java中实现的观察者java.util.Observable和Observer
- MQ、Redis、ZooKeeper的watch机制都用到了观察者模式
19 职责链模式 / 责任链模式(Chain of Responsibility)
![](https://img.haomeiwen.com/i9007799/b83a0ee74c91afa8.png)
- 一个请求多个对象都有机会处理,将这些对象连成一条链,并沿着这条链路传递该请求,直到有一个对象处理请求。
- 常用责任链模式的场景:
- 工作流、审批流
- springmvc的HandlerExecutionChain
20 策略模式(Strategy)
![](https://img.haomeiwen.com/i9007799/1bcc1608a6c09517.png)
解决什么问题?
- 商场针对不同的客户有不同的折扣,有不同的算法,传统的方式需要写很多的if判断,这样不利于程序扩展和维护,可以引入策略模式Strategy
- 程序中要写很多if-else的时候,可以考虑使用策略模式
实现逻辑
-
将不同的折扣计算定义为不同的算法,他们实现同一个接口Strategy,不同的算法实现自这个Strategy接口,引入一个上下文对象Context,负责持有算法,但是具体的选择哪个实现算法由Client决定
-
策略算法是相同行为的不同实现
-
可以在Client端和Context选择具体的策略算法
扩展性
新增加一种策略的两种实现方式:
-
新增一个Context继承之前的Context
- 风格统一,所有的策略需要的数据都来自于Context,扩展的策略也可以作为公共功能给其他人使用
-
新增一个策略算法implements原来的策略接口
- 实现简单,面向接口编程,但是风格不统一,别的策略数据都来自Context,扩展的策略一部分来自Context、一部分来自自己。
策略模式的本质
分离算法,选择实现
21 模板方法模式(Template Method)
- 模板方法模式是定义一个算法的骨架,将其中一些具体步骤的实现延迟到子类,使得子类不改变算法的骨架就可以重新定义该算法的具体的步骤。
- 类似重构,将类似的方法抽出一个公共的方法来,相当于算法的骨架
- AbstractClass算法骨架(抽象类,不变的部分,移到父类)------------ConcreteClass具体的实现子类(变的部分,放到子类实现)
- 模板方法定义为抽象类,一般既要约束子类的行为,又要为子类提供公共功能的时候就用抽象类。
- java使用回调方式实现模板方法,比继承耦合程度更低,基于接口。
22 状态模式(State)
![](https://img.haomeiwen.com/i9007799/c93fa1d7b8a21a4d.png)
- 状态模式的本质是根据状态来分离和选择行为
解决什么问题?
- if-else问题
- 工作流问题
23 备忘录模式(Memento)
![](https://img.haomeiwen.com/i9007799/3d2dc12c5271234e.png)
- 在不破坏对象封装性的前提下,保存和恢复对象的状态
- 一个备忘录是一个对象,它存储另一个对象某一个瞬间的内部状态,后者被称为备忘录的原发器。
- 备忘录模式的本质是保存和恢复对象状态,保存是手段,恢复才是目的
24 中介者模式(Mediator)
![](https://img.haomeiwen.com/i9007799/9df2d6e979c8484b.png)
- 优点
- 高内聚,低耦合,使用中介者明显降低了对象之间的耦合
- 通过让对象彼此解耦,增加对象的复用性
- 通过将控制逻辑集中,可以简化系统维护
- 通过中介者使一对所变成了一堆一,便于理解
- 缺点
- 如果涉及不好,引入中介者会使程序变的复杂
- 中介者承担过多责任,维护不好会出大事
25 命令模式(Command)
![](https://img.haomeiwen.com/i9007799/395377909f6b346b.png)
- Client------------》接收者对象----------》命令对象----------》Invoker
- 命令队列,可以将命令记入日志,允许接受请求的一方否决请求,容易实现请求的撤销和重做,新增加的命令也很容易扩展
26 解释器模式(Interpreter)
![](https://img.haomeiwen.com/i9007799/d0f84fc323942aef.png)
- 定义一种语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子
27 迭代器模式(Iterator)
![](https://img.haomeiwen.com/i9007799/81299733b1815c20.png)
- 聚合对象Aggregate------迭代器Iterator
- 把聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中
28 访问者模式(Visitor)
![](https://img.haomeiwen.com/i9007799/1c88de118a928c64.png)
-
访问者模式是一种将数据操作和数据结构分离的设计模式。最复杂的设计模式,使用率不高。
-
访问者模式的优点:
- 各角色职责分离,符合单一职责原则
- 具有优秀的扩展性
- 如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
- 灵活性
-
访问者模式的缺点:
- 具体元素对访问者公布细节,违反了迪米特原则
- 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象
29 spring用到了哪些设计模式?用在什么场景?
- 工厂模式,IoC,BeanFactory,ApplicationContext
- 单例模式,spring的bean的作用域默认是singleton,还有prototype等
- 代理模式,AOP,JDK动态代理,cglib动态代理
- 模板方法模式,jdbcTemplate,hibernateTemplate
- 观察者模式,spring事件驱动模型,事件角色ApplicationEvent、事件监听ApplicationListener、事件发布者ApplicationEventPublisher
- 适配器模式
- AOP的增强或通知advice
- springmvc的controller
- 装饰模式
- spring配置DataSource动态切换数据源,装饰模式都在类名上加 Wrapper或者 Decorator
- 策略模式
- 加载资源文件Resource,ClassPathResource、FileSystemResource
30 Tomcat用到了哪些设计模式?
- 门面模式
- ApplicationContext、ServletContext
- 观察者模式
- 控制组件生命周期的Lifecycle
- servlet实例的创建
- Session的管理
- 责任链模式
- Container
- 命令模式
- connector通过命令模式调用container
- 装饰器模式
- javax.servlet.http.HttpServletRequestWrapper
网友评论