1. 六大设计原则是什么?【SOLID】
2. 提高代码质量的方法论包含:
3. 大部分设计模式要解决的都是代码的可重用性、可扩展性问题。
一、创建型模式(5种)
1.1 单例模式
(1)饿汉式
实例在类加载时实例化,有JVM保证线程安全,不支持延迟加载。
(2)懒汉式
支持延迟加载,存在线程安全问题,用synchronized
锁住又可能会出现并发度低。
(3)双重检测
既支持延迟加载、又支持高并发的单例实现方式。
//单例模式-双重校验
public class Singleton_04 {
//使用 volatile保证变量的可见性
private volatile static Singleton_04 instance = null;
private Singleton_04(){}
//对外提供静态方法获取对象
public static Singleton_04 getInstance(){
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null){
synchronized (Singleton_04.class){
//抢到锁之后再次进行判断是否为null
if(instance == null){
instance = new Singleton_04();
}
}
}
return instance;
}
}
(4)静态内部类
原理:根据静态内部类的特性(外部类的加载不影响内部类),同时解决了按需加载、线程安全的问题,同时实现简洁。
public class Singleton_05 {
private static class SingletonHandler{
private static Singleton_05 instance = new Singleton_05();
}
private Singleton_05(){}
public static Singleton_05 getInstance(){
return SingletonHandler.instance;
}
}
扩展:反射&序列化对单例的破坏
解决方法一: 在单例类的构造方法中 添加判断 instance != null
时,直接抛出异常,不够优雅。
解决方法二: Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
//只要在Singleton类中定义readResolve就可以解决该问题
private Object readResolve() {
return singleton;
}
(5)枚举(推荐方式)
特点: 满足单例模式所需的 创建单例、线程安全、实现简洁的需求。
public enum Singleton_06{
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Singleton_06 getInstance(){
return INSTANCE;
}
}
单例模式总结
1.2 工厂方法模式★★★
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
工厂方法模式的目的很简单,就是封装对象创建的过程,提升创建对象方法的可复用性。
工厂方法模式的主要角色:
-
抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
-
具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
-
抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
-
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
我们直接来看看工厂方法模式的 UML 图:
优点:
-
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
-
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:
-
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
什么时候使用工厂方法模式
-
需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等。
-
创建对象要访问外部信息或资源时,比如,读取数据库字段,获取访问授权 token 信息,配置文件等。
-
创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
-
创建池化对象时,比如,连接池对象、线程池对象、日志对象等。这些对象的特性是:有限、可重用,使用工厂方法模式可以有效节约资源。
-
希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。
1.3 抽象工厂模式
略。
1.4 建造者模式
建造者模式 (builder pattern), 也被称为生成器模式。
定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式与工厂模式区别&建造者模式的优缺点 (见讲义)
1.5 原型模式(用的少)
定义: 原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
其实现在不推荐大家用Cloneable接口,实现比较麻烦,现在借助Apache Commons或者springframework可以直接实现:
-
浅克隆:
BeanUtils.cloneBean(Object obj);BeanUtils.copyProperties(S,T);
-
深克隆:
SerializationUtils.clone(T object);
BeanUtils是利用反射原理获得所有类可见的属性和方法,然后复制到target类。 SerializationUtils.clone()就是使用我们的前面讲的序列化实现深克隆,当然你要把要克隆的类实现Serialization接口。
使用场景
原型模式常见的使用场景有以下六种。
-
资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。
-
复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。
-
性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。
-
同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。
-
需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。
二、结构型模式(7种)
结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。
常用的有:代理模式、桥接模式、装饰者模式、适配器模式。
不常用的有:门面模式、组合模式、享元模式。
2.1 代理模式
2.1.1 JDK动态代理
(1)JDK动态代理实现
(2)类是如何动态生成的★★★
(3)代理类的调用过程★★★
2.1.2 cglib动态代理
(1)cglib动态代理实现
cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target。具体代码请看讲义,简洁明晰。
2.1.3 总结
1. jdk代理和CGLIB代理:
2. 代理模式优缺点
3. 代理模式使用场景
2.2 桥接模式
桥接模式(bridge pattern) 的定义是:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式原理的核心是: 首先有要识别出一个类所具有的的两个独立变化维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合.
仔细看讲义,体悟其精髓。通过例子掌握4种角色。
桥接模式使用场景:
2.3 装饰器模式
装饰模式(decorator pattern) 的原始定义是:动态的给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式提供了一种比使用子类更加灵活的替代方案。
在软件设计中,装饰器模式是一种用于替代继承的技术,它通过一种无须定义子类的方式给对象动态的增加职责,使用对象之间的关联关系取代类之间的继承关系。
装饰器模式的适用场景:
2.4 适配器模式
代理、桥接、装饰器、适配器 4 种设计模式的区别
代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似,但其各自的用意却不同,简单说一下它们之间的关系:
-
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
-
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
-
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
-
适配器模式:将一个类的接口转换为客户希望的另一个接口,适配器模式让那些不兼容的类可以一起工作。
2.5 其他模式
外观模式( Facade Pattern),也叫门面模式, 外观模式的原始定义是:为子系统中的一组接口提供统一的接口。它定义了一个更高级别的接口,使子系统更易于使用。
组合模式:略。
享元模式:本质上就是找到对象的不可变特征,并缓存起来,当类似对象使用时从缓存中读取,以达到节省内存空间的目的。
三、行为型模式(11种)
一句话概括行为型模式:负责对象间的高效沟通和职责传递委派。
- 常用的有:观察者模式、模板模式、策略模式、责任链模式、迭代器模式、状态模式。
- 不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
11 种行为型模式中,模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
3.1 观察者模式
应用很多,如MQ等。
JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.
用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式。
3.2 模板方法模式
模板方法模式是一种基于继承的代码复用技术,它是一种类行为模式. 模板方法模式其结构中只存在父类与子类之间的继承关系。模板方法的作用主要是提高程序的复用性和扩展性。
3.3 策略模式
面试问题: 如何用设计模式消除代码中的if-else
策略模式优点:
策略模式缺点: