七大设计原则

原则 主要内容 解决什么样的问题 如何解决问题
单一职责 每个类、接口或方法有单独的职责 职责模糊不清,承担多种功能,修改的可能性会变大,耦合更高 将一个类拆分为多个类,接口,方法,具体根据复杂程度决定
接口隔离 只实现需要的接口 实现的接口种包含不必要的方法 将接口拆分为多个,只实现想实现的
依赖倒置 依赖接口,而不是具体实现 依赖关系中,依赖的是具体实现类,而不是接口,耦合度增大 抽取接口,依赖接口
里氏替换 A继承B,且将所有B类的引用换成A类不改变逻辑 修改父类会影响到其他子类 继承关系中,子类尽量只做扩展,不做重写
开闭原则 对扩展开放,对修改关闭 修改会引起其依赖的逻辑改动,为了避免大批量的修改逻辑 修改代码结构,增加接口或抽象类,扩展接口实现类
迪米特法则 最少知道原则,尽量只对外提供public的方法
组合复用 尽量使用依赖,组合,聚合等代替继承关系 继承关系耦合度更高 使用依赖,组合聚合等代替

六种关系

关系
(由低到高)
代码实现 实现形式 uml
依赖 class B {
public A a(A a){}
}
类A中使用了类B,B作为方法参数,返回值,局部变量,静态方法调用等 虚线加箭头
关联 class B {
A a;
}
成员变量 实线加箭头
聚合 class B {
A a;
public B(A a) {
this.a = a;
}
}
整体和个体(零件),整体由个体组成,整体不存在,个体可以依然存在。
代码实现形式通常是构造函数中引入个体类作为参数,整体对象生命周期结束后,个体可以继续存在。
实线加空心菱形
组合 class B {
A a = new A();
/ public B() {
this.a = new A();}
/
}
整体和(组成)部分,整体由部分组成,整体不存在,部分也不存在了。
代码实现形式是直接作为成员变量赋值,不是以参数的形式传入。整体生命周期结束,部分也结束,(级联删除)关联性更强。
实现加实心菱形
实现 class B implements A{} 实现抽象类 虚线加三角形箭头
继承(泛化) class B extends B{} 继承非抽象类 实线加三角形箭头

uml中的虚线与实线的区别 虚线代表:依赖 实线代表:关联 实线通常会带有多重性的表达,一对多,一对一等。

设计模式

某类问题的通用解决方案,不是代码。
设计模式是为了提高代码的维护性,可扩展性和通用性,降低软件的复杂度而存在的。

模式 功能 适用场景 举例 补充
单例
(创建型)
保证整个程序中只能产生一个实例。
1. 频繁进行创建和销毁的对象。
1. 创建对象耗时过多或消耗资源过多
1. 经常用到的对象
1. 工具类对象
1. 频繁访问数据库或文件的对象(SessionFactory,数据源)
hibernate的SessionFactory。
jdk的Runtime方法(饿汉式)。
推荐使用枚举,双重检查。
工厂
(创建型)
不使用new创建对象,而是使用工厂方法创建。将具体的实现放到子类实现,对外提供抽象工厂类。
优点:调用者只需要知道要创建对象名和抽象类;扩展只需要加具体实现类;
缺点:增加新产品时,需要增加相应的接口和实现类。
需要更具不同的条件创建不同的对象时。
抽象出工厂类,通过多个实现类做不同的创建实现。

1. 提供了多个数据库配置,创建数据库连接时根据不同的数据库类型创建不同的连接对象。
1. JDK的Calendar(简单工厂),创建对象时,有参情况下根据不同的后缀确定不同的时区对象.

分为简单工厂和工厂模式。
抽象工厂 围绕一个超级工厂创建其他的工厂。
可以理解成是在工厂模式的基础上在抽象出一层来。
适用于:产品存在多个种类,只消费其中某一类的产品。

分类

  • 创建型

单例,抽象工厂,原型,建造者,工厂模式

  • 结构型

适配器,桥接,装饰,组合,外观,享元,代理模式

  • 行为型

模板方法,命令,访问者,迭代器,观察者,中介者,备忘录,解释器,状态,策略,责任链

创建型

单例

保证整个程序中只能产生一个实例。比如hibernate的SessionFactor。充当数据存储源的代理,负责创建session,只需要存在一个就够了。
适用场景:

  1. 频繁进行创建和销毁的对象。
  2. 创建对象耗时过多或消耗资源过多
  3. 经常用到的对象
  4. 工具类对象
  5. 频繁访问数据库或文件的对象(SessionFactory,数据源)

举例:

  • hibernate的SessionFactory。
  • jdk的Runtime方法(饿汉式)。

    饿汉式(静态常量)
    1. class singleton {
    2. private final static singleton instance = new singleton();
    3. private singleton() {}
    4. public static singleton getInstance() {
    5. return instance;
    6. }
    7. }
    1. 私有的无参构造函数
    2. 私有的静态的final成员变量
    3. 成员变量直接初始化
    4. 公共的静态的获取实例方法。

    为什么要使用私有的无参构造函数? 需要防止通过构造函数创建多个实例。私有的无参构造函数是必须的。 为什么成员变量是静态的final? 静态是因为外部无法通过new创建对象,所以通过static修饰,外部通过类直接获取 final是因为单例对象需要保证只有一个,就要求对象无法被修改。final修饰的对象无法被修改。 为什么要使用静态的获取方法? 因为构造函数是私有的,无法直接创建对象; 但是又必须创建对象。所以返回对象是静态,就要求方法必须是静态的。 final是不是必须要加?

基于类加载机制,在类装载时进行了实例化。
优点:简单,线程安全
缺点:无法做到懒加载,有可能资源浪费;无法防止反射修改;不能确定有其他方式触发类加载。

饿汉式(静态代码块)
  1. class Singleton {
  2. private final static Singleton instance;
  3. static {
  4. instance = new Singleton();
  5. }
  6. private Singleton() {}
  7. public static Singleton getInstance() {
  8. return instance;
  9. }
  10. }

将静态常量直接new,改成了在静态代码块中赋值。其他一样。

懒汉式(线程不安全)
  1. class Singleton {
  2. private static Singleton instance;
  3. private Singleton() {}
  4. public static Singleton getInstance() {
  5. if (instance == null ) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }

多线程不安全,在一个线程进行为空判断后,准备创建对象还未创建时,另一个线程同样为空判断为true,这样就会创建多个实例。 实际开发中不会使用。

懒汉式(线程安全)

  1. class Singleton {
  2. private static Singleton instance;
  3. private Singleton() {}
  4. public synchronized static Singleton getInstance() {
  5. if (instance == null ) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. }

线程安全,但是效率不高

双重检查锁
  1. class Singleton {
  2. private volatile static Singleton instance;
  3. private Singleton() {}
  4. public static Singleton getInstance() {
  5. if (instance == null) {
  6. synchronized (Singleton.class) {
  7. if (instance == null ) {
  8. instance = new Singleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }

volatile:为了能够在变量被修改是能够立即更新到主存(可见性,防止指令重排序)。从null变为非空时。 synchronized:为了能够保持线程同步。只允许一个线程执行。 两次非空判断:

  1. 为了不让同步的代码反复的执行。不为null时直接返回实例,为空时需要第一次创建对象,创建对象时给singleton加锁,然后再次判断是否为空。
  2. 为的是检测是否有多线程在第一次判断过程中已经创建了对象。所以需要二次判断。

优点:保证效率的同时,保障了线程安全,推荐使用。

静态内部类
  1. class Singleton {
  2. private Singleton() {}
  3. private static class InnerClass {
  4. private static final Singleton instance = new Singleton();
  5. }
  6. public static Singleton getInstance() {
  7. return InnerClass.instance;
  8. }
  9. }

外部类装载时,静态内部类不会被装载 调用getInstance方法时,静态内部类加载。 静态内部类如何保证线程安全? 1. 静态内部类如何实线懒加载? 静态内部类在外部类加载时不会立即被加载,只有在使用到内部类的时候才会加载。

优点:能够保证线程安全,也能实现懒加载。

枚举
  1. enum Singleton {
  2. INSTANCE;
  3. }

枚举的实现 枚举获取的相同实例就是相同的对象,枚举只拥有一个实例时,就保证了单例。

优点:简单,高效,线程安全,能够防止反序列化重新创建对象。推荐使用。

工厂模式

核心本质是实例化对象不使用new,而是用工厂方法实现。
适用场景:
面对接口的多种实现类(多态),根据不同的条件获取不同对象的方法。改变原来的直接new实现类的方法,将实例化对象的代码提取出来,通过一个抽象接口工厂来统一管理创建不同的对象。达到解耦目的
举例:

  1. jdk的Calendar(简单工厂),创建对象时,有参情况下根据不同的后缀确定不同的时区对象。
  2. 通过工厂模式 + 配置文件的形式能够解除耦合,通过静态代码读取配置文件只执行一次,将键值对进行保存,根据传入的键获取相应的值的对象。

工厂模式三种模式:

  1. 简单工厂模式

根据传入参数类型的判断,来创建不同的对象,代码耦合度较高。
如果需要增加新的产品(对象),需要覆盖原有的代码。
结构简单,但是不符合开闭原则。

  1. //举例,如果需要新增新的类型,则需要修改工厂类的创建方法。
  2. class Factory {
  3. public product create(int type) {
  4. if(type == 1) {
  5. return new A();
  6. } else if(type == 2) {
  7. return new B();
  8. }
  9. }
  10. }
  1. 工厂方法模式

相比简单工厂来说,不需要对原有的代码改动,只进行扩展。
扩展新种类时,只需要实现Base接口,创建新种类的工厂方法,实现工厂接口
优点:符合设计原则,面向对接口,面向对象。
缺点:需要有很多实现类

  1. interface Base{
  2. public void getName();
  3. }
  4. class A implements Base{
  5. public void getName() {
  6. System.out.println("A");
  7. }
  8. }
  9. class B implements Base{
  10. public void getName() {
  11. System.out.println("B");
  12. }
  13. }
  14. interface Factory{
  15. public Base create();
  16. }
  17. class AFactory implements Factory{
  18. public Base create() {
  19. return new A();
  20. }
  21. }
  22. class BFactory implements Factory{
  23. public Base create() {
  24. return new B();
  25. }
  26. }
  27. class Consume{
  28. public static void main(String[] args) {
  29. Base a = new AFactory().create();
  30. Base b = new BFactory().create();
  31. }
  32. }

抽象工厂模式

抽象工厂是工厂的工厂,提供一个创建一系列相关或相互依赖对象的接口。
image.png

原型模式

创建新对象比较复杂时,使用原型模式来简化创建过程。
存在一个对象,可以通过原型模式得到多个相同属性的对象(但不是同一个对象)
类实现cloneable接口,重写clone方法。通过对象.clone()的方法获取相同属性对象。

原对象的基本类型属性值传递
原对象的引用类型属性引用传递。

缺点:

深拷贝与浅拷贝

建造者模式

一种对象构建模式,将复杂对象的建造过程抽象出来,使得这个过程的不同实现方法可以构造出不同的对象。
一步一步创建一个复杂对象,用户只需要知道对象的类型和内容,无需了解具体实现细节。

建造者模式的四个角色