Creation type: singleton design is not friendly
Catalog introduction
- 01. A brief introduction to the frontier
- 02. Singletons are not OOP friendly
- 03. Hidden dependencies between classes
- 04. Code scalability is not friendly
- 05. Testability is not friendly
- 06. Argument constructors are not supported
- 07. What are the alternative solutions
01. A brief introduction to the frontier
- Although singleton is a very common design pattern, and we do use it often in actual development, some people think that singleton is an anti-pattern and it is not recommended to use it.
- So, let's talk about these questions in detail in response to this statement: What are the problems with the singleton design pattern? Why is it called an anti-pattern? How can I represent a globally unique class without using a singleton? What are the alternative solutions?
02. Singletons are not OOP friendly
The four major characteristics of OOP are encapsulation, abstraction, inheritance, and polymorphism. The singleton design pattern does not support abstraction, inheritance, and polymorphism. Why do you say that? We still use the example of IdGenerator to explain.
public class Order { public void create(...) { //... long id = IdGenerator.getInstance().getId(); //... } } public class User { public void create(...) { // ... long id = IdGenerator.getInstance().getId(); //... } }
The way IdGenerator is used violates the design principle based on interface rather than implementation, and also violates the abstract characteristics of OOP in a broad sense. If one day in the future, we hope to use different ID generation algorithms for different businesses. For example, order IDs and user IDs are generated using different ID generators. In order to cope with this change in demand, we need to modify all the places where the IdGenerator class is used, so the code changes will be relatively large.
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(); } }
- In addition, the singleton support for inheritance and polymorphism is not friendly. The reason why the word "unfriendly" is used here instead of "completely unsupported" is because theoretically, singleton classes can also be inherited and polymorphic, but it will be very strange to implement, which will lead to The readability of the code gets worse. People who don't understand the design intent will feel inexplicable when they see such a design. Therefore, once you choose to design a class as a singleton class, it means giving up the two powerful object-oriented features of inheritance and polymorphism, which is equivalent to losing the scalability that can cope with future changes in demand. .
03. Hidden dependencies between classes
- Readability of code is very important. When reading the code, we hope to see the dependencies between classes at a glance, and figure out which external classes this class depends on. The dependencies between classes declared through constructors, parameter passing, etc., can be easily identified by looking at the definition of the function. However, the singleton class does not need to be explicitly created, does not need to rely on parameter passing, and can be called directly in the function. If the code is more complex, this calling relationship will be very hidden. When reading the code, we need to carefully look at the code implementation of each function to know which singleton classes this class depends on.
04. Code scalability is not friendly
- A singleton class can only have one object instance. If one day in the future, we need to create two or more instances in the code, it will require a relatively large change to the code. You might say, is there such a need? Since singleton classes are mostly used to represent global classes, how come two or more instances are needed?
- In fact, such needs are not uncommon. Let's take the database connection pool as an example to explain.
- In the early stage of system design, we felt that there should only be one database connection pool in the system, which would facilitate us to control the consumption of database connection resources. Therefore, we designed the database connection pool class as a singleton class. But then we discovered that some SQL statements in the system were running very slowly. When these SQL statements are executed, database connection resources are occupied for a long time, causing other SQL requests to fail to respond. To solve this problem, we want to execute slow SQL in isolation from other SQL. In order to achieve this purpose, we can create two database connection pools in the system, one database connection pool for slow SQL, and another for other SQL, so as to avoid slow SQL from affecting the execution of other SQL.
- If we design the database connection pool as a singleton class, it is obviously unable to adapt to such changes in requirements, that is to say, the singleton class will affect the scalability and flexibility of the code in some cases. Therefore, it is best not to design resource pools such as database connection pools and thread pools as singleton classes. In fact, some open source database connection pools and thread pools are indeed not designed as singleton classes.
05. Testability is not friendly
- The use of the singleton pattern affects the testability of the code. If the singleton class depends on heavy external resources, such as DB, when we write unit tests, we hope to replace it by mocking. The hard-coded use of singleton classes makes it impossible to implement mock replacement.
- In addition, if the singleton class holds member variables (such as the id member variable in IdGenerator), it is actually equivalent to a global variable that is shared by all code. If this global variable is a mutable global variable, that is, its member variables can be modified, then when we write unit tests, we also need to pay attention to modifying the singleton class between different test cases. The value of the same member variable, which leads to the problem that the test results affect each other.
06. Argument constructors are not supported
- Singletons do not support constructors with parameters. For example, when we create a singleton object of a connection pool, we cannot specify the size of the connection pool through parameters. For this problem, let's take a look at what solutions are available.
The first solution is to call the init() function to pass parameters after the instance is created. It should be noted that when we use this singleton class, we must call the init() method first, and then call the getInstance() method, otherwise the code will throw an exception. The specific code implementation is as follows:
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();
The second solution is to put the parameters in the getIntance() method. The specific code implementation is as follows:
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);
I don't know if you have noticed that the above code implementation is slightly problematic. If we execute the getInstance() method twice as follows, the obtained paramA and paramB of singleton1 and signleton2 are both 10 and 50. That is to say, the second parameter (20, 30) does not work, and the construction process does not give prompts, which will mislead the user. How to solve this problem?
Singleton singleton1 = Singleton.getInstance(10, 50); Singleton singleton2 = Singleton.getInstance(20, 30);
The third solution is to put the parameters in another global variable. The specific code is implemented as follows. Config is a global variable that stores the values of paraMA and paramB. The values in it can either be defined by static constants like the code below, or loaded from a configuration file. In fact, this method is the most recommended.
public class Config { public static final int PARAM_A = 123; public static fianl 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; } }
07. What are the alternative solutions
- Mentioned a lot of problems with singletons, you may say that even if there are so many problems with singletons, I don't have to. There is a need to represent a globally unique class in business. If you don't use a singleton, how can you ensure that the object of this class is globally unique?
In order to ensure global uniqueness, in addition to using singletons, we can also use static methods to achieve this. This is also an implementation idea that is often used in project development. For example, the example of the ID unique incremental generator mentioned in the previous lesson, implemented in a static method, is as follows:
// 静态方法实现方式 public class IdGenerator { private static AtomicLong id = new AtomicLong(0); public static long getId() { return id.incrementAndGet(); } } // 使用举例 long id = IdGenerator.getId();
However, the implementation idea of static method does not solve the problems we mentioned earlier. In fact, it is more inflexible than singleton, for example, it cannot support lazy loading. Let's see if there is any other way. In fact, there is another way to use a singleton in addition to the way we mentioned before. The specific code is as follows:
// 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);
- Based on the new usage, we pass the object generated by the singleton as a parameter to the function (or pass it to the member variable of the class through the constructor), which can solve the problem that the singleton hides the dependencies between classes. However, other problems with singletons, such as being unfriendly to OOP features, scalability, and testability, are still unsolvable.
more content
- GitHub: https://github.com/yangchong211
- Blog: https://juejin.cn/user/1978776659695784
- Blog summary: https://github.com/yangchong211/YCBlogs
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。