设计模式八股
设计模式七大原则
- 单一职责原则:一个类只负责一个功能,降低耦合性,方便迭代维护。
- 开放封闭原则:类、方法可以进行功能扩展,但不能修改。对扩展开放,对修改封闭。
- 依赖倒置原则:高级模块不能依赖低级模块,都应该依赖于接口。先将类进行抽象,先设计功能接口,再对接口进行功能细节的实现。
- 接口隔离原则:不同接口定义不同的功能。
- 里氏代换原则:子类可替换其父类。
- 迪米特原则:每个模块间要尽可能少的相互调用,减少依赖,降低耦合性。
设计模式讲解链接:
单例模式实现
懒汉式(线程不安全)
1 | public class Lazy { |
懒汉式是指先不创建实例,当首次调用后再创建。
私有化构造方法,不能使用 new 进行实例化,使用方法判断当前实例是否已存在,不存在则创建,保证实例的单一性。
线程不安全,在多线程情况下,可能首次会有多个线程一起调用 getLazyInstance方法 ,那么此时实例为空,会同时创建多个实例。
饿汉式(线程安全)
1 | public class Hungry { |
饿汉式指无论是否要使用实例,先进行一次实例化,随后需要实例时直接调用方法返回即可;
线程安全,因为初始化类时已经进行了实例化。但若长时间不使用该实例会造成资源浪费。
懒汉式(线程安全)
1 | public class LazyConcurrent { |
在实例化执行方法上加锁保证线程安全。
线程安全,但效率低,多线程争夺锁会造成线程阻塞。
双重检查锁实现(线程安全)
1 | public class DoubleCheck { |
双重检查锁相当于是线程安全懒汉式的优化版。
首先我们不是在执行方法上加锁,而是在方法内部加锁。先进行一次实例判断,如果为空则获得锁然后再进行一次判断,因为第一次判断可能有多个线程同时通过,而获取锁后需要第二次判断再实例对象,以防同时通过第一次判断线程进行多次实例化。
实例变量使用volatile修饰。我们正常代码实例化执行步骤如下:
1 | doubleCheckInstance = new DoubleCheck(); |
由于JVM的指令重排机制,指令执行顺序可能会由原来的123变为132。而在多线程的影响下,会导致线程得到一个尚未初始化的实例。为了解决该问题可以在声明实例变量时加上 volatile,禁止JVM进行指令重排,保证多线程的安全。
静态内部类实现(线程安全)
1 | public class Single { |
当外部类Single加载时,其静态内部类SingleInner并未加载,当调用实例获取方法走到返回值时,才会去加载静态内部类,并进行实例化。
使用静态内部类可以延迟实例化,节省资源,并保证线程安全。
枚举实现(线程安全)
1 | public enum Unique { |
枚举默认即线程安全 + 单例,还可防范一系列反射破解单例的操作。
工厂模式(简单工厂、工厂、抽象工厂)
简单工厂
一个产品接口,有多种具体实现,我们创建一个工厂类,声明一个实例获取的方法,根据参数和if - else判断生成哪一个具体的产品实例。然后可以使用该实例。
工厂类使用if - else判断,扩展性差。
工厂模式
将工厂抽象化,通过工厂的具体子类创建具体产品实例,工厂抽象类中将实例产品的方法抽象化,交由具体子类实现,而其他的功能方法则可以直接写。
这样我们有新产品加入时,只需要创建对应的子工厂继承抽象工厂,然后使用子工厂进行操作即可,不用修改其他类。
但如果我们产品种类很多时,不可能为每一个产品都创建一个子工厂,这会大大增加代码的复杂度。
抽象工厂
为了缩减工厂子类的数量,我们不必为每一个产品分配一个工厂类,可将产品分组,比如有产品键盘和鼠标,然后我们将工厂分类,工厂接口声明可创建很多产品(键盘、鼠标等),然后创建具体品牌工厂实现接口,声明各品牌的键盘鼠标。比如戴尔工厂有戴尔的键盘和鼠标,联想工厂有联想的键盘和鼠标。此时需要哪个品牌的哪种产品直接去对应品牌工厂获取即可。
但是抽象工厂每次新增一个产品需要进行很多扩展,对产品和工厂都要新增。
总结
- 简单工厂:单一工厂类,单一产品接口,创建工厂根据方法的入参判断需要创建的具体产品实例。
- 工厂:多个工厂类,单一产品接口,通过子工厂实现多态,完成不同产品实例的创建,避免入参判断。
- 抽象工厂:多个工厂类,多个产品抽象类,产品会根据子类分组,同一个产品工厂实现同种组件,减少了工厂子类的数量。