Skip to content

设计模式

设计模式的主题是可维护性和可复用性

  • 可维护性

    理解、改正、适应、扩展
  • 可复用性

    重复使用的难易程度

七大原则

SOLID+C

S_单一职责 Single Responsibility Principle SRP

承担的越多, 复用的越少

解耦职责

一个类只负责一个功能; 一个模块只负责一个功能;一个函数只负责一个功能

  • 主要指的是大部分的具体类s
  • 抽象类和接口需要依照具体情况
不同的职责放到不同的类中
  • 一个对象只包含单一职责

  • 被完整的封装在一个类中


O_ 开闭原则 Open-Closed Principle OCP

为系统提供抽象层, 提高扩展性

要求尽量面向抽象类、接口进行编程

基于实体——软件模块、一个或多个类

  • 对扩展开放
  • 对修改关闭

如果对模块功能的拓展修改了源码,如果不考虑重构的情况,那么这就是违背开闭原则的

可变封装原则 Principle of Encapsulation of Variation EVP

  • 将可变因素,比如变量,可变交互数据独立进行封装

L_ 里氏代换原则 Liskov Substitution Principle LSP

设计上:父类 实现上:子类替代父类——父类与子类的关系

  • 引用基类,必然可以使用子类的对象

父类替换为子类,功能不受影响;

  • 子类的所有方法在父类中声明
  • 父类尽可能设置为抽象类、接口

I _依赖倒转原则 Dependence Inversion Principle DIP

(开闭原则的进阶原则)

抽象层指导编写

  • 高层模块不依赖低层模块

    高层模块依赖于抽象

  • 抽象不依赖于细节

    细节依赖抽象(先有抽象,再有细节)

依赖指的是父类与子类之间的依赖;具体类依赖抽象类,而不能反过来;

  • 具体类只负责实现,而不进行扩充

常见概念

  • 类间耦合

    • 零耦合
    • 具体耦合
    • 抽象耦合
  • 依赖注入

    一个类的对象传入另一个类;注入时尽量传入父类对象;程序运行时通过子类覆盖父类;

    • 构造注入
    • 设值注入
    • 接口注入

I _接口隔离原则 Interface Segregation Principle ISP

将大接口转化为小接口;每个接口承担一个独立的角色,也称为角色隔离原则;一个接口中只包含某一类用户定制的方法;

控制接口的粒度(职责、大小)

  • 客户端不依赖其不需要的接口
私有的方法在类中实现,interface中的接口均为所需接口
如果不遵循,会看到不属于自己的方法。

定制服务:为不同的功能提供具体接口

D _迪米特法则 Law of Demeter LoD

限制实体之间的通信宽度和深度;控制访问权限;信息隐藏;降低类之间的耦合,不过会增加大量小方法;

  • 尽量创建松耦合的类

  • 降低成员变量 && 方法的访问权限

  • 只要是类,尽量设计成不变类

  • 只要可能,一个对象对其他对象的引用程度降到最低

适当中介,减少耦合

  • 每个软件单位对其他的单位只有最少的认知
  • 仅在与本单位相关联的软件单位
中介者模式

C _合成复用原则 Composite Reuse Principle CRP

使用组合/聚合而非继承;将一个类的对象作为另一个类的对象的一部分;

继承的问题

  • 破坏系统封装性——白箱复用
  • 继承的是静态,运行时也不改变——灵活性差
  • 仅能在有限环境中使用——不能声明为final

组合的优势

  • 已有对象纳入新对象——黑箱复用
  • 运行时动态进行

继承是静态的, 组合是动态的

  • 优先组合

  • 而非继承

    结合里氏替换,将extends的方法改为引用的私有属性对象

创造型

工厂方法——CLASS

抽象工厂

生成器 (Builder)

原型

单例

基本概念

​ 单例模式属于设计模式中的创建型模式之一。

特点

​ 使用单例模式的对象只有一个实例。 ​ 具体应用场景,比如QQ、Broswer这种单应用程序和插件、客户端,资源管理器等

实现方法

实现方法目前主流的有:

  1. 饿汉式单例
  2. 懒汉式单例
  3. 双检锁单例
  4. 内部静态类单例

之所以分为这么多类型,主要围绕的是两个方面的问题,一是线程安全,二是内存占用。

不同类型的实现方式有利有弊,主要会围绕其实现难度,锁,懒加载,性能开销这四个方面评估,适合使用场景的就是最好的。

  • 饿汉(Eager Initialization)

    - 无锁
    - 类加载时就创建实例,不存在多线程同时访问的情况,避免了同步问题,但会产生内存浪费
    
    public class Singleton{
    	private static Singleton instance = new Singleton();
    	private Singleton (){}
    	public static Singleton getInstance(){
    		return instance;
    	}
    }
  • 懒汉(Synchronized Lazy Initialization)

    - 无锁,线程不安全
    - 懒加载,显式调用时才会进行实例加载,避免内存浪费
    public class Singleton{
    	private static Singleton instance;
    	private Singleton(){}
    	public static Singleton getInstance(){
    		if(instance == null) {
    			instance = new Singleton();
    			return instance;
    		}else {
    			return instance;
    		}
    	}
    }
    
    - 有锁, 线程安全
    - 懒加载
    - 效率低,高并发不适用
    public class Singleton{
    	private static Singleton instance;
    	private Singleton(){}
    	public static Singleton synchronized getInstance(){
    		if(instance == null) {
    			instance = new Singleton();
    			return instance;
    		}else {
    			return instance;
    		}
    	}
    }
  • 双检锁(Double-Checked Locking)

    - 实例化检查+锁检查,线程安全,在多线程环境下避免加锁的性能开销,性能较高
    - 具体性能高低取决于getInstance如何实现
    public class Singleton{
    	/**
    	* volatile关键字用于确保多线程环境下的可见性。
    	* volatile 是 Java 中的关键字,用于声明变量,确保多个线程能够正确地处理该变量。主要有两个作用:
    	* 可见性(Visibility): 当一个线程修改了被 volatile 修饰的变量的值,变化会对其它线程立即可见。背后的实现机制锁会直接控制内存,而非缓存。
    	* 禁止指令重排序(Preventing Instruction Reordering): volatile 修饰的变量的读写操作不能被重排序,确保了在多线程环境下的正确执行顺序。
    	*/
    	private static volatile Singleton instance;
    	private Singleton(){}
    	public static Singleton getInstance(){
    		// 第一次检查,若实例不存在则进入同步
    		if (instance == null) {
    			// 第二次检查,进入锁检查。实现线程安全,确保只有一个实例创建
    			synchronized (Singleton.class) {
    				if(instance == null) {
    					instance = new Singleton();
    				}
    			}
    		}
    		return singleton;
    	}
    }
  • 登记式 / 静态内部类(Static Inner Class)

    - 使用static inner class既实现了Lazy Init 又实现了Lazy Loading,与双检锁不同的是,双检锁会在类装载的同时进行初始化;
    	登记式将其分离,只有显式调用getIns时才会装载Holder并实例化ins,而不是Singleton加载时就完成了实例化。
    - 通过final定义常量实现单例
    - 通过内部类加载实现线程安全与延迟加载
    /**
    * 对于内部类的加载,Java虚拟机会保证在同一时刻只有一个线程对类进行初始化。通过类加载器的锁(Class Initialization Lock)实现。类加载器锁是对类进行初
    * 始化的唯一入口,使得多线程环境下只有一个线程能够操作类的初始化操作。类加载是由类加载器负责的,而类加载器在加载类时会采用一种单一的全局锁,锁是全局单 * 一的,所以不存在竞争问题。
    */
    
    public class Singleton{
    	private static class Holder{
    		private static final Singleton instance = new Singleton();
    	}
    	private Singleton(){}
    	public static final Singleton getInstance(){
    		return Holder.instance;
    	}
    }

结构型

适配器——CLASS

桥接

装饰

组合

外观

享元

代理

*过滤器模式 / 标准模式


行为型

责任链

命令

迭代器

中介者

备忘录

观察者

状态

策略

模板方法——CLASS

访问者

*解释器模式——CLASS

*空对象模式


特殊型

MVC

业务代表

组合实体

数据访问对象

前端控制器

拦截过滤器

服务定位器

传输对象