天天看点

3万字聊聊设计模式(一)

大家好,我是Leo。

之前聊过了 ​​MySQL​​ 、​​Redis​​、​​RocketMQ​​、​​秒杀系统​​、​​计算机网络​​

由于现阶段工作规划的问题,计算机网络暂时放放,先聊一下设计模式。

七大原则

单一职责原则

对类来说,即一个 类应该只负责一项职责,如类A负责两个不同的职责,我们把职责分为职责1和职责2。当职责1需要变更的时候,我们修改的类A,可能会造成职责2执行错误。

真实场景:货主端的新增司机就应该只是新增司机,即使从类上做不到单一,最起码的底线是要做到方法上的单一。即使当下没有这个需求也不可以。也要考虑扩展性,可读性!

为我们解决了什么

  1. 降低类的复杂度,一个类只负责一项职责
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

接口隔离原则

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

用不到的接口不要实现,可以用抽象解决这一问题。或者定义多个接口解决这一问题

为我们解决了什么

  1. 单一化接口的职责,从而有效地避免接口污染。
  2. 当一个接口的方法过多,往往会造成使用该接口的类中闲置一些方法,造成代码的冗余,通过细分接口可有效避免该现象。
  3. 可以提高代码的灵活性,就好比搭积木一样,我们可以将一个大的接口拆成多个小接口,不同的小接口可以有多种组合。
  4. 促使程序高内聚、低耦合。

依赖倒转原则

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象
  3. 依赖倒转的中心思想是面向接口编程
  4. 依赖倒转原则是基于这样的设计理念;相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口或抽象类,细节就是具体的实现类
  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

为我们解决了什么

  1. 可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。

里氏替换原则

在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。继承实际上是让两个类的耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题(基类)

为我们解决了什么

  1. 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  2. 提高代码的重用性;
  3. 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
  4. 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
  5. 提高产品或项目的开放性。

需要我们注意什么

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
  2. 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
  3. 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果————大段的代码需要重构。

开闭原则

  1. 一个软件实体如类,模块,函数应该对扩展开放,对修改关闭。用抽象构建框架,对实现扩展细节。
  2. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  3. 编程中遵循其他原则以及使用设计模式的目的就是遵循开闭原则

迪米特法则

  1. 一个对象应该对其他对象保持最少的了解(最少知道原则)
  2. 类与类关系越密切,耦合度越大
  3. 对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
  4. 只与直接的朋友通信(B类的调用,局部变量B只能算间接朋友)
局部变量不应该出现

合成复用原则

一个类使用另一个类的代码,尽量不使用继承,而是合成

可以通过传递类或者set类的方式,通过这个类名调用,而不是继承

设计模式

设计模式类型

创建型模式

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

结构型模式

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

行为型模式

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

单例模式

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

应用场景

  1. 需要频繁创建和销毁的对象
  2. 创建对象时耗时过多或耗费资源过多,但又经常用到的对象,工具类对象,频繁范围数据库或文件的对象

饿汉式(静态常量)

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
class Singleton{
    private Singleton(){}
    private final static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}      

优点

  1. 写法简单,类装载时完成实例化,避免线程同步问题

缺点

  1. 在类装载的时候完成了实例化,没有达到Lazy Loading的效果,如果从始至终从未使用过这个实例,则会造成内部的浪费

结论

  1. 饿汉式(静态常量)可以使用,可能会造成内存浪费
饿汉式(静态代码块)与饿汉式(静态常量) 类型

懒汉式(线程不安全)

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
    if (instance == null){
        instance = new Singleton();
    }
        return instance;
    }
}      

优点

  1. 起到了Lazy Loading的效果,但是只能在单线程下使用

缺点

  1. 存在系统安全性问题,并发情况下,容易创建多个实例

结论

  1. 在单线程可以使用,多线程不能使用

懒汉式(线程安全)

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
    if (instance == null){
        instance = new Singleton();
    }
        return instance;
    }
}      

优点

  1. 解决了线程不安全问题

缺点

  1. 效率太低了,每个线程在想获得类的实例时候,执行getInstance方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低

结论

  1. 在实际开发中,不推荐使用这种方式

懒汉式(线程安全,双重检查)

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
class Singleton{
    private static volatile Singleton instance;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
    if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                instance = new Singleton();    
                }
            }
    }
        return instance;
    }
}      

优点

  1. 线程安全,延迟加载,效率高

结论

  1. 在实际开发中,推荐使用这种单例模式

静态内部类方式创建

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
class Singleton{
    private static volatile Singleton instance;
    private Singleton(){}
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static synchronized Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}      
  1. 这种方式采用了类装载机制来保证初始化实例时只有一个线程
  2. 被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮我们保证了线程的安全性,在类初始化时,别的线程是无法进入的。

优点

  1. 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

结论

  1. 推荐使用

枚举方式创建

public static void main(String[] args){
    Singleton instance1 = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance1 == instance2)
}
enum Singleton{
    INSTANCE;
    public void sayOk(){
    System.out.println("sayOk");
    }
}      

优点

  1. 通过借助jdk1.5中添加的枚举实现单例模式,不仅能避免多线程同步问题,而且还能防止发序列化重新创建新的对象

结论 :推荐使用

总结

继续阅读