过早优化是万恶之源
For 20 years I have been teaching software at the University of Buenos Aires. In the software engineering course we teach design patterns and the same “scheme” is always repeated almost like a type of deja vu, the same sequence that I had the opportunity to witness in several of my works and in the free software that I use: The ‘magical’ appearance of the Singleton pattern.
20年来,我一直在布宜诺斯艾利斯大学教授软件。 在软件工程课程中,我们讲授设计模式,并且总是像重复deja vu一样重复相同的“方案”,这是我有机会在自己的几本作品和所使用的免费软件中见证的相同顺序: Singleton模式的“神奇”外观。
邪恶的起源 (The origin of evil)
The pattern has been used in the industry for decades. Its popularity is attributed to the excellent book Design Patterns. There are numerous software frameworks that use it and we rarely find literature that discourages its use.
该模式已在业界使用了数十年。 它的受欢迎程度归功于出色的《 设计模式》一书。 有很多使用它的软件框架,我们很少发现阻碍其使用的文献。
Despite this, in the corresponding Wikipedia entry we can read a Dantesque warning:
尽管如此,在相应的Wikipedia条目中,我们仍可以阅读Dantesque警告:
Critics consider the singleton to be an anti-pattern in that it is frequently used in scenarios where it is not beneficial, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application.
批评者认为单例是反模式 ,因为单例通常在不利的情况下使用,在实际上不需要类的唯一实例的情况下引入不必要的限制,并将全局状态引入应用程序。
Let’s be pragmatic as always, and look at the arguments for and against its use:
让我们一如既往地务实,看看支持和反对使用它的理由:
不使用它的原因 (Reasons not to use it)
1.违反双射原则 (1. Violates the bijection principle)
As we saw in previous articles, every object in our computable model has to be mapped on a 1 to 1 relationship with a real-world entity.
正如我们在前面的文章中看到的那样,可计算模型中的每个对象都必须与现实世界实体以1对1的关系进行映射 。
Singletons are often linked to objects that need to be unique. As usual we will have to distinguish among the objects that are essentially unique (for problem domain drivers) and differentiate them from the accidentally unique ones regarding implementation reasons, efficiency, resource consumption, global access, etc.
单例通常链接到需要唯一的对象。 像往常一样,我们将不得不区分本质上唯一的对象(对于问题域驱动程序),并就实现原因,效率,资源消耗,全局访问等将它们与偶然地唯一的对象区分开。
Most accidentally unique objects are not present in the real world, and we will see later on that the presumably essentially unique ones may not be so if we consider different contexts, environments, or situations.
在现实世界中,大多数偶然的唯一对象都不存在,并且稍后我们将看到,如果我们考虑不同的上下文,环境或情况,那么可能基本上是唯一的对象就不会如此。
2.产生耦合 (2. Generates coupling)
It is a global reference. Again according to Wikipedia:
它是全球参考。 再次根据维基百科 :
An implementation of the singleton pattern must provide global access to that instance.
单例模式的实现必须提供对该实例的全局访问 。
What a priori appears as a benefit for preventing us from having to pass context information, generates coupling. The reference to the singleton cannot be changed according to the environment (development, production), nor can dynamic strategy changes related to the current load be made, it cannot be replaced by a double test and it prevents us from making changes due to the possible ripple effect.
先验的好处是可以防止我们必须传递上下文信息,从而产生耦合。 不能根据环境(开发,生产)来更改对单例的引用,也不能进行与当前负载有关的动态策略更改,不能用双重测试代替它,并且由于可能而无法进行更改。连锁React。
3.它说明了很多(偶然的)执行情况,而很少谈到他的(基本)职责 (3. It says a lot about (accidental) implementation and little about his (essential) responsibilities)
By focusing early on implementation issues (the Singleton is an implementation pattern) we orient ourselves according to accidentality (how) and underestimate the most important thing of an object: the responsibilities it has (what).
通过尽早关注实现问题( Singleton是一种实现模式),我们根据偶然性 ( 如何 )来定位自己,并低估了对象最重要的事情:对象所承担的责任( 什么 )。
When carrying out premature optimization in our designs, we usually award a concept that we have just discovered as Singleton.
在设计中进行过早优化时,我们通常会授予刚刚发现为Singleton的概念。
class Singleton {
private static $instance = null;
private function __construct(){
}
public static function getInstance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
}
4.它阻止我们编写好的单元测试 (4. It prevents us from writing good unit tests)
The aforementioned coupling has as a corollary; the impossibility of having full control over the side effects of a test to guarantee its determinism. We must depend on the global state referenced by the Singleton.
上述耦合具有必然性; 不可能完全控制测试的副作用以确保其确定性。 我们必须依靠Singleton引用的全局状态。
5.不节省内存空间 (5. Does not save up memory space)
The argument used to propose its use is to avoid the construction of multiple volatile objects. This supposed advantage is not real in virtual machines with efficient garbage collection mechanisms.
提出其用途的论点是避免构造多个易失性对象。 这种假定的优势在具有有效垃圾收集机制的虚拟机中并不真正。
In such virtual machines, used by most modern languages, keeping objects in a memory area whose Garbage Collector algorithm is a double pass (mark & sweep) is much more expensive than creating volatile objects and quickly removing them.
在大多数现代语言使用的此类虚拟机中,将对象保留在垃圾收集器算法为两次通过( mark&sweep )的内存区域中比创建易失对象并快速删除它们要昂贵得多。
6.它阻止我们使用依赖注入 (6. It prevents us from using dependency injection)
As good solid design advocates, we favor inversion of control through dependency injection to avoid coupling. In this way the service provider (formerly a hardcoded Singleton) is decoupled from the service itself, replacing it with an injectable dependency that meets the defined requirements, coupling us to what and not how.
作为优秀的实体设计倡导者,我们主张通过依赖注入避免对控制的反转。 通过这种方式,服务提供商(前身是硬编码辛格尔顿)从服务本身分离,使用符合规定要求的注射依赖替换它,连接我们什么 ,而不是如何 。
7.它违反了实例化合同 (7. It violates the instantiation contract)
When we ask a class to create a new instance we expect the contract to be honored and give us a fresh new instance. However, many Singleton implementations hide the creation omission silently, rather than failing quickly to indicate that there is a business rule that instances should not be arbitrarily created.
当我们要求一个班级创建一个新实例时,我们希望合同能够兑现并给我们一个新的实例。 但是,许多Singleton实现都默默地隐藏了创建遗漏,而不是无法Swift表明存在不应随意创建实例的业务规则。
final class God extends Singleton {
}
$christianGod = new God();
A better answer would be to show with an exception it is not valid to create new instances in this execution context.
更好的答案是显示一个例外,即在此执行上下文中创建新实例无效。
class Singleton {
private function __construct(){
throw new Exception('Cannot Create new instances');
}
This will force us to have a private constructor to use it internally. Thus violating the contract that all classes can create instances. Another code smell.
这将迫使我们有一个私有的构造函数在内部使用它。 因此违反了所有类都可以创建实例的约定。 另一个代码的气味 。
8.它迫使我们明确地耦合到实施 (8. It forces us to explicitly couple to implementation)
When invoking a class to use it (again, to use its what), we will have to couple with the fact that it is accidentally a Singleton (its how), generating a relation that, when trying to break it, would produce the much-feared ripple effect.
当调用一个类使用它时(再次使用它的what ),我们将不得不加上一个偶然的事实,即它是一个Singleton(它的how ),并生成一个关系,当试图打破它时,会产生很多恐惧的涟漪效应。
$christianGod = God::getInstance();
//Why should us be aware of getInstance when creating an object ?
9.它阻碍了自动测试的创建 (9. It hinders the creation of automated tests)
If we use the TDD development technique, objects are defined purely and exclusively based on their behavior. Therefore, in no case, the construction of software using TDD will arise the Singleton concept. If business rules state that there must be a single provider of a certain service, this will be modeled through a controlled access point (which should not be a global class, much less a Singleton).
如果我们使用TDD开发技术,则仅基于对象的行为来定义对象。 因此,在任何情况下,使用TDD构建软件都不会出现Singleton概念。 如果业务规则规定某个服务必须有一个提供者,则将通过受控访问点(不应是全局类,更不用说Singleton )来建模。
Trying to create unit tests in an existing system coupled to a Singleton can be an almost impossible task.
在与Singleton耦合的现有系统中尝试创建单元测试几乎是不可能的任务。
10.独特的概念是与上下文相关的 (10. Unique concepts are contextual)
When the pattern is stated it is usually accompanied by some idea that in the real world seems rather unique. For example, if we want to model the behavior of God according to the vision of Christianity, there could not be more than one God. But these rules are relative to the context and subjective vision of each religion. Various belief systems may coexist in the same world with their own gods (some monotheistic and other polytheistic beliefs).
陈述模式时,通常伴随着一些想法,即在现实世界中似乎相当独特。 例如,如果我们想根据基督教的远见来塑造上帝的行为,那么就不会有一个以上的上帝 。 但是这些规则与每种宗教的背景和主观视野有关。 各种信仰体系可能与自己的神共存于同一世界(一些一神论和其他多神论信仰)。
11.在多线程环境中很难跟上 (11. It is difficult to keep up in multi-threaded environments)
Pattern implementation can be tricky in programs with multiple threads. If two execution threads try to create the instance at the same time and it does not exist yet, only one of them should succeed in creating the object. The classic solution to this problem is to use mutual exclusion in the class creation method that implements the pattern, to make sure it is reentrant.
在具有多个线程的程序中,模式实现可能很棘手。 如果两个执行线程尝试同时创建实例,但该实例尚不存在,则只有其中一个可以成功创建对象。 解决此问题的经典方法是在实现该模式的类创建方法中使用互斥 ,以确保其可重入。
12.累积垃圾占用内存空间 (12. Accumulates garbage that takes up memory space)
Singletons are references attached to classes, just as classes are global references these are not reached by the garbage collector. In case the Singleton is a complex object, this entire object, in addition to the transitive closure of all its references, will stay in memory throughout the execution.
单例是附加到类的引用,就像类是全局引用一样,垃圾回收器也无法访问它们。 如果Singleton是一个复杂的对象,则整个对象,除了其所有引用的传递性关闭之外,还将在整个执行过程中保留在内存中。
13.累积的垃圾状态是单元测试的敌人 (13. The accumulated garbage state is the enemy of unit tests)
The persistent state is the enemy of unit tests. One of the things that makes unit tests effective is that each test must be independent of all the others. If this is not true, then the order in which the tests are run may affect the test results and the tests become non-deterministic. This can lead to cases where tests fail when they shouldn’t, and worse, can lead to tests that pass only in the order they were performed. This can hide mistakes and is very bad.
持久状态是单元测试的敌人。 使单元测试有效的原因之一是每个测试必须独立于所有其他测试。 如果不是这样,则测试的运行顺序可能会影响测试结果,并且测试变得不确定 。 这可能导致测试在不应该进行的情况下失败,更糟糕的是,可能导致测试仅按执行顺序通过。 这会掩盖错误并且非常糟糕。
Avoiding static variables is a good way to prevent the state from being preserved between tests. Singletons, by their very nature, depend on an instance that is kept in a static variable. This is an invitation for the dependency test.
避免使用静态变量是防止在测试之间保留状态的好方法。 就其本质而言, 单例依赖于保存在静态变量中的实例。 这是依赖性测试的邀请。
14.限制新对象的创建违反了单一责任原则。 (14. Limiting the creation of new objects violates the single responsibility principle.)
The single responsibility of a class is to create instances
类的唯一职责是创建实例
Adding any other responsibility to any class implies violating the single responsibility principle (the S for Solid). A class should not worry about being or not being a Singleton. They should only be responsible for their commitments to business rules. In case of needing the uniqueness of these instances, this would be the responsibility of a third object in the middle such as a Factory or a Builder.
将任何其他责任添加到任何类别中都意味着违反了单一责任原则 (S表示实体)。 上课不必担心是否是单身汉 。 他们仅应对自己对业务规则的承诺负责。 在需要这些实例的唯一性的情况下,这将由中间的第三个对象(例如工厂或建造者)负责。
15.拥有全球参照物的代价不仅是耦合 (15. The cost of having a global reference is not just the coupling)
Singletons are frequently used to provide a global access point to some service. What ends up happening is design dependencies are hidden within the code and are not visible when examining the interfaces of their classes and methods.The need to create something global to avoid passing it explicitly is a code smell. There are always better solutions and alternatives to using a global reference that do not require passing all collaborators between methods.
单例通常用于为某些服务提供全局访问点。 最终发生的事情是设计依赖项隐藏在代码中,并且在检查其类和方法的接口时不可见。需要创建全局的东西以避免显式传递它是一种代码味道 。 使用全局引用始终有更好的解决方案和替代方法,它们不需要在方法之间传递所有协作者。
16.他是聚会上的好朋友 (16. He’s the easy friend from the party)
Many singletons are themselves abused as a global reference repository.The temptation to use the singleton as an entry point for new references is huge.
许多单例本身被滥用为全局参考资料库。使用单例作为新参考的切入点的诱惑很大。
There are many examples where a Singleton is used as a quick-reach reference container.
有很多示例将Singleton用作快速访问参考容器。
As if it was not enough to be the root of all evil he is also the easy friend of the party. In large projects, it just accumulates garbage to get out of trouble.
似乎还不足以成为万恶之源,他还是政党的好朋友。 在大型项目中,它只是堆积垃圾以摆脱麻烦。
Since it does not have a corresponding entity on the bijection, adding responsibilities that do not correspond to it, is like adding one more stain to the tiger. Apparently without doing damage but generating ripple effect when wishing to do a healthy decoupling.
由于它在双射对象上没有对应的实体,因此添加与其不对应的职责就像在老虎上添加一个污渍。 显然没有造成损坏,但希望进行健康的去耦时会产生波纹效应。
使用理由 (Reasons to use it)
Having stated the arguments against Singleton let’s try to see the possible benefits:
陈述了反对Singleton的论点之后,让我们尝试看看可能的好处:
1.它可以节省内存 (1. It allows us to save up memory)
This argument is fallacious according to the current state of the art of languages with a decent virtual machine and garbage collector. It is enough to carry out a benchmark and look for evidence to convince us.
根据当前语言水平的说法,使用合适的虚拟机和垃圾收集器,这种说法是错误的。 进行基准测试并寻找证据说服我们就足够了。
2.对独特的概念建模很有用 (2. It’s good for unique concepts modeling)
The Singleton can be used to guarantee the uniqueness of a concept. But it is not the only way or the best.
Singleton可用于保证概念的唯一性。 但这不是唯一的方法,也不是最好的方法。
Let’s rewrite the previous example:
让我们重写前面的示例:
final class GodFactory {
private $godInstance;
function create(){
throw new Exception('Cannot create new God instances');
}
private function __construct(){
self->$godInstance = new God();
}
}
Access and creation of the single instance are not coupled. Creation is done through a factory and direct references to classes are decoupled. Furthermore, the factory can be easily mocked in test cases.
单个实例的访问和创建没有耦合。 创建是通过工厂完成的,对类的直接引用是分离的。 此外,可以在测试用例中轻松模拟工厂。
3.它防止我们重复昂贵的初始化 (3. It prevents us from repeating expensive initializations)
There are objects that require a certain cost of resources to create. If this cost is large, we will not be able to generate them constantly. One possible solution is to use a Singleton and have it available all time. As always we will focus on what and we will look for some other hows generating less coupling. If we need a single control point or a cache we will have to access a known object related to a certain context (and easily replaceable according to the environment, the test setup, etc.). Certainly a Singleton will not be our first choice.
有些对象需要一定的资源成本才能创建。 如果成本很高,我们将无法持续产生它们。 一种可能的解决方案是使用Singleton并使其始终可用。 与往常一样,我们将重点放在什么 ,我们会找一些其他的怎么样了产生更少的耦合。 如果需要单个控制点或缓存 ,则必须访问与特定上下文相关的已知对象(并且可以根据环境,测试设置等轻松替换)。 当然, 辛格尔顿不是我们的首选。
解决方案 (Solutions)
There are multiple techniques to gradually remove the (ab)use of Singletons. In this article we list some of them:
有多种技术可以逐渐消除对Singleton的滥用。 在本文中,我们列出了其中一些:
结论 (Conclusions)
The disadvantages listed in this article are much greater than the advantages, and the evidence from the examples in the industry should be a strong indicator of the non-use of the evil pattern in any case. As our profession matures, we will leave behind these kinds of bad solutions.
本文列出的缺点远大于优点,并且在任何情况下,行业示例的证据都应成为不使用邪恶模式的有力指示。 随着我们行业的成熟,我们将抛弃这些糟糕的解决方案。
Part of the objective of this series of articles is to generate spaces for debate and discussion on software design.
本系列文章的目的之一是为软件设计的辩论和讨论创造空间。
We look forward to comments and suggestions on this article!
我们期待对本文的评论和建议!
This article is also available in Spanish here.
本文也可以在西班牙语中找到 。
翻译自: https://codeburst.io/singleton-the-root-of-all-evil-8e59ca966243
过早优化是万恶之源