2

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

 2 years ago
source link: https://blog.51cto.com/u_14954803/5510886
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

大家好,我是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. 静态内部类

应用场景

  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. 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

结论

枚举方式创建

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中添加的枚举实现单例模式,不仅能避免多线程同步问题,而且还能防止发序列化重新创建新的对象

结论 :推荐使用

了解设计模式的七大原则,首先介绍了23种设计模式的的单例模式,从众多单例模式的写法中推荐使用 懒汉式(线程安全,双重检查)静态内部类方式枚举方式创建。不仅保证系统安全问题,还能解决无效占用内存。

下一篇聊聊设计模式中的工厂模式,工厂模式分两种:抽象工厂模式简单工厂模式


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK