42 - 单例模式是反模式?

2021-09-11  本文已影响0人  舍是境界

尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些人认为单例是一种反模式(anti-pattern),并不推荐使用。所以,本文就针对这个说法详细地讲讲这几个问题:单例这种设计模式存在哪些问题?为什么会被称为反模式?如果不用单例,该如何表示全局唯一类?有何替代的解决方案?

单例存在哪些问题?

  1. 单例对 OOP 特性的支持不友好
public class Order {
  public void create(...) {
    //...
    long id = IdGenerator.getInstance().getId();
    //...
  }
}
public class User {
  public void create(...) {
    // ...
    long id = IdGenerator.getInstance().getId();
    //...
  }
}
public class Order {
  public void create(...) {
    //...
    long id = IdGenerator.getInstance().getId();
    // 需要将上面一行代码,替换为下面一行代码
    long id = OrderIdGenerator.getIntance().getId();
    //...
  }
}
public class User {
  public void create(...) {
    // ...
    long id = IdGenerator.getInstance().getId();
    // 需要将上面一行代码,替换为下面一行代码
    long id = UserIdGenerator.getIntance().getId();
  }
}
  1. 单例会隐藏类之间的依赖关系
  1. 单例对代码的扩展性不友好
  1. 单例对代码的可测试性不友好
  1. 单例不支持有参数的构造函数
public class Singleton {
  private static Singleton instance = null;
  private final int paramA;
  private final int paramB;
  private Singleton(int paramA, int paramB) {
    this.paramA = paramA;
    this.paramB = paramB;
  }
  public static Singleton getInstance() {
    if (instance == null) {
       throw new RuntimeException("Run init() first.");
    }
    return instance;
  }
  public synchronized static Singleton init(int paramA, int paramB) {
    if (instance != null){
       throw new RuntimeException("Singleton has been created!");
    }
    instance = new Singleton(paramA, paramB);
    return instance;
  }
}
Singleton.init(10, 50); // 先init,再使用
Singleton singleton = Singleton.getInstance();
public class Singleton {
  private static Singleton instance = null;
  private final int paramA;
  private final int paramB;
  private Singleton(int paramA, int paramB) {
    this.paramA = paramA;
    this.paramB = paramB;
  }
  public synchronized static Singleton getInstance(int paramA, int paramB) {
    if (instance == null) {
      instance = new Singleton(paramA, paramB);
    }
    return instance;
  }
}
Singleton singleton = Singleton.getInstance(10, 50);
public class Config {
  public static final int PARAM_A = 123;
  public static final int PARAM_B = 245;
}
public class Singleton {
  private static Singleton instance = null;
  private final int paramA;
  private final int paramB;
  private Singleton() {
    this.paramA = Config.PARAM_A;
    this.paramB = Config.PARAM_B;
  }
  public synchronized static Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}

有何替代解决方案?

// 静态方法实现方式
public class IdGenerator {
  private static AtomicLong id = new AtomicLong(0);
  
  public static long getId() { 
    return id.incrementAndGet();
  }
}
// 使用举例
long id = IdGenerator.getId();
// 1. 老的使用方式
public demofunction() {
  //...
  long id = IdGenerator.getInstance().getId();
  //...
}
// 2. 新的使用方式:依赖注入
public demofunction(IdGenerator idGenerator) {
  long id = idGenerator.getId();
}
// 外部调用demofunction()的时候,传入idGenerator
IdGenerator idGenerator = IdGenerator.getInsance();
demofunction(idGenerator);

小结

  1. 单例存在哪些问题?
    • 单例对 OOP 特性的支持不友好
    • 单例会隐藏类之间的依赖关系
    • 单例对代码的扩展性不友好
    • 单例对代码的可测试性不友好
    • 单例不支持有参数的构造函数
  2. 单例有什么替代解决方案?
    • 为了保证全局唯一,除了使用单例,我们还可以用静态方法来实现。不过,静态方法这种实现思路,并不能解决我们之前提到的问题。如果要完全解决这些问题,我们可能要从根上,寻找其他方式来实现全局唯一类了。比如,通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证,由程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)。
    • 有人把单例当作反模式,主张杜绝在项目中使用。这有点极端。模式没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局的类,我们在其他地方 new 的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。
上一篇下一篇

猜你喜欢

热点阅读