Design Patterns are among the bread and butter of the good developer's cookbook. Basically, any serious job interview you will have as a Junior to Mid-level programmer, will have a question on Design Patterns in it. They are very important in many regards and essential in others. But to me at least, one has stood above all of the others and that is the Singleton Design Pattern.
设计模式是所有开发者日常开发不可缺少的内容(原文:bread and butter,不可缺少,日常必备). 基本上,面试的时候,由初级到中级的程序员都会遇到关于设计模式的问题。对于开发而言,设计模式非常重要。但是对于我来说,设计模式中我会列出的第一条就是单例模式(Singleton Design Pattern).
Very rarely have I seen a project that has no implementation of this whatsoever. In this article, I will explain the importance of using this Design Pattern and how to implement it.
在我所看到的项目中,很少有没有实现单例模式的。这篇文章中,我会想你解释单例模式的重要性以及怎么样实现单例。
Singleton单例
那么什么是单例呢?单例允许我们严格控制类有且仅有一个实例化对象在整个程序运行的生命周期内。简单来说,就是在整个程序中都可以访问到这个单例对象,不需要创建新的实例对象。So what is really the Singleton? Well, it is a Design Pattern that allows us to restrict the class that implements it to have just one instance of itself at all times. In plain terms this means that if we implement Singleton on a class, it will always have just one instance. That's it. That instance will be accessible throughout your code but you will no longer be able to create new instances of the class.
Why should we use it?为什么要使用单例?
- 你创建的类中需要一个单一封装的模块使你不需要改变代码逻辑的作用域范围就可以动态地做出改变。
- 代码中利用单例可以清晰地管理资源。
- 减少实例对象个数,单例模式让我们可以更轻松地在运行时使用已经封装在类中的方法或者成员。
There are many reasons. Here is a short list of some “why we should do it” stuff:
- You will have one singular encapsulation module for the logic of your class that will serve in a way that you can do easy dynamic changes without changing the scope of your programming logic.
- You can manage a single resource clearly through your code.
- As the Singleton Design Pattern implements the lazy instantiation, it allows us to get and use it's encapsulated logic as it is needed at runtime.
When do we need Singleton 为什么我们需要单例?
项目中需要实现实例的地方时要保证实现的一致性和通用性。例如游戏中主屏幕分数板上显示玩家的分数,整个游戏体验中都需要的,不管它分数增加到多少如何变化,最后你还是需要用它来统计玩家等级以及计算最高得分等等。对于此,实现一个scoreboard类的单例就非常理想。分数增加,就会存在就会保存在scoreboard类的单例对象中,这样随时在程序中需要计算得分的时候,我们就可以轻松的调用。
An implementation of Singleton is needed on classes that would represent something that you use in one form and consistently throughout your project. That would be per say your in-game's scoreboard that monitors your current point count. It's something that you would need during your gameplay experience to increment your score. However, you would need it at the end of your level to calculate your high-score. In that regard, implementing Singleton to your scoreboard class would be perfect. You increment your score and it is stored in the single scoreboard object that you have. When the time comes for us to calculate your in-game high score, we simply access this object and see what you've accumulated.
另外一个经典的场景是保存和进入棋盘游戏的规则。通常,如果你开发的是一个普通的棋盘游戏,你的规则可能游戏过程中都是一致的。每次游戏回合你都不需要重新为此创建一个新的rule类对象,无论从性能角度还是数据丢失方面去考虑,都不需要。
Another classic case would be to store and access the rules of a board game. Normally, if you are developing a regular board game, your rules should be the same all together. There is absolutely no reason for you to create a new object every time and for every game you play. There is no point to have this both from a performance standpoint and from data loss perspective.But why use Singleton and not a class with static methods?
为什么不能用类的静态方法来代替单例模式?
单例不能替代一个定义静态方法的类,两者是完全不同的, 使用的情形也不同。
Singleton is not a replacement for a class with static methods. It's a totally different thing. It's also used in different cases.
你可以使用一个类的静态方法的情况下,你不需要在所有实例。正常情况下,静态方法是将要使用的操作对象是类的外部。一般来说,静态方法应该在一个更通用的方式来实现。他们很可能会使用多个逻辑段完全不同的参数。对于单例模式,你有一个类,应该像一个正常的工作。就是这样。喜欢在上面的例子中,它不是仅仅是一类包含一些方法,将要被作为工具使用稍后。它要包含自身的编程逻辑。
You would use a class with static methods in situations where you do not need an instance at all. Normally, the static methods are going to be used to do operations with objects that are outside of the class. Generally, the static methods should be implemented in a more generic way as well. They are probably going to be used by multiple logic segments with different parameters altogether. With Singleton, you have a class that should work like a normal one. That's it. Like in the examples above, it's not merely a class containing some methods that are going to be used as tools later on. It is going to contain programming logic on its own.
所要讲的例子 - 你需要在你的棋盘游戏的规则和单例,在你的游戏中的记分牌。您将需要一个静态类,如果你想临时的动态创建可重用UI元素,不属于你的核心接口。So to talk in examples – you will need Singleton in your board game’s rules and in your in-game scoreboard. You would need a static class if you plan to dynamically instantiate reusable UI elements that serve only temporary purpose and are not a part of your core interface.
Singleton Implementations 单例实现
Here we will take a look at a real implementation of Singleton in Java.
public class Scoreboard { public int currentScore; //we will use this for the score later on private static Scoreboard singleton = null; //this we will need for the implementation protected Scoreboard(){ //nothing here really. At least for this example. } public static Scoreboard instance(){ //this is the method that we are going to use to return the one instance of this class. if(singleton == null){ singleton = new Scoreboard(); } return singleton; } }
Now to use this implementation in another class in order for you to see how it's actually going to happen.
public class HelloScoreboard{ public static void main(String[] args){ Scoreboard.instance().currentScore = 10; //here we call and set ÃÃÃÃÃÃÃÃâcurrentScoreÃÃÃÃÃÃÃÃâ System.out.println(Scoreboard.instance().currentScore.toString()); } }
如下面的例子,我们在定义的单例类Scoreboard中, 定义了instance()方法。通过这个方法,我们可以获取一个该类的实例对象。任何对这个类的调用都必须通过那个方法。现在,如果我们看一下currentScore变量,我们互看到在HelloScoreboard的主方法中我们设置的value是10,根据这一点,每次我们引用这个变量的时候,这个值都是10,至少我们改变它的时候,这个值才会改变。
As seen in the example above, we have the Singleton class Scoreboard. It has its instance() method. Through this method we will get the one instance of this class. Any calls we want to make to the class have to be done through this method. Now, if we take a look at the currentScore variable, we will see that in the main method of HelloScoreboard we are setting it to the value of 10. From this point on, every time we refer to this variable, its value is going to be 10, at least until we change it for some reason. Again, its value can be changed wherever we please.
Singleton Issues 单例问题
实现单例模式也必须注意一些问题,如果没有正确的处理,你可能会遇到更多的问题。There are some major issues with the Singleton Design Pattern to look out for. If not implemented correctly and in turn, not handled correctly, you may be getting more issues then you would wish.
Multithreading 多线程
这可能是单例模式会遇到一个最大的问题之一。问题大致可以描述成这样:你有两个线程都需要调用我们单例的对象,由于是独立的两个线程,我们会获取一个逻辑悖论(logical paradox) - 单例对象的两个实例化对象。我们可真的不想遇到这样的问题,我们只想实现一个实例化封装这个单例对象的资源和功能。对于多线程的问题,我们尽量避免使用单例模式。
This is quite possibly one of the biggest issues out there. The problem here is something like this - let's say that you have two threads that for some reason are calling our Singleton object. Since they are two separate threads, we would get a logical paradox - two instances of the Singleton object. That's just not what we want at all. What we want is to have one instance that encapsulates a singular resource and functionality. By having the multithread issue, we completely make the point of the Singleton useless.
如何“解决”这个问题?或许说“修正”这个问题,我们可以对单例对象进行同步/锁的操作。
Well, generally this is not a problem that can be truly "fixed". It can merely be "mended". We can do that by synchronising/locking the Singleton object.Here is a Java implementation for this:
public class Scoreboard { public int currentScore; //we will use this for the score later on private static Scoreboard singleton = null; //this we will need for the implementation protected Scoreboard(){ //nothing here really. At least for this example. } public static Scoreboard instance(){ //this is the method that we are going to use to return the one instance of this class. if(singleton == null){ synchronized(Scoreboard.class) { if(singleton == null){ singleton = new Scoreboard(); } } } return singleton; } }
So this is what basically is happening here - on entry, the first thread will check if the singleton instance is null . If it is, then we go to the synchronized lock. When the other thread enters, the creation of the instance is locked. At this point we have another failsafe as well. If/when the second thread manages to get past the lock, when the first thread is done, we have a new check. At this point we block out the issue with the multiple instances.
Unit Testing 单元测试
这是另一个主要单例问题。这是因为我们需要测试Singleton实例,每一个类,使用它在一个这样或那样的。这只是没有单元测试的想法。我们要测试的只有一件事 - 没有一件事和单例。这使得两件事情,这也使得corrupt 测试。这里要注意的是另一件事Singleton对象的事实,有一种“状态”。在任何时候,你想改变什么,你改变单例的价值,这样的 - 它的当前状态。这使得它几乎不可能跟踪状态和大量的测试案例中的测试数据。
This is another major Singleton issue. That's because we would need to test the Singleton instance with every single class that uses it in one way or another. That's just not the idea of the Unit Tests. We want to test just one thing - not one thing and the Singleton. That makes two things and it also makes a corrupt test. Another thing to note here is the fact that the Singleton object has a "state" of a sort. At any time you change something, you change the Singleton's values and thus - it's current state. That makes it near impossible to track the states and the test data in a massive test case.Debugging and Solving Issues
Since the Singleton can be called at any one point in your code, this makes it really hard to track when your instance got it's values/logics messed up beyond expectations.
The resolution here should really be to be careful why, when and where you call your Singleton object but mostly - you need to watch out if your implementation of this pattern is really needed. If you are making too many calls to the Singleton object, you might want to consider another approach.
The Singleton "knows" Too Much
Some times, the deviation of the Sinleton pattern from the traditional OOP understanding is making it an issue of what kind of things it can manipulate. We should not put calculations or any process that has a major role in our code. That's to say that if you have some basic and driving logic, you should by all means NOT include any parts of it inside the Singleton implementation.
Dependency Issues
This is a real blocker as well. When we use the Singleton Design Pattern, we take the risk of making the code too dependent on the object's state/values/logic. Let's say we expect the Singleton object to behave in a certain fashion. We base our logic on that behavior and we think that things are going to go smooth. And it does. Everything is going ok and you are happy that your code works as expected. Well, that's up until you totally forget what you've done with this. And then you decide to make some minor changes here and there. Now, your Singleton object does not behave the way you thought it would. Not only that, now you are totally not in controll of what's happening.
Interesting Points
I chose Java for the examples in the previous chapter because I find it to be really easily portable to other programming languages. If you are doing work with C++ or C# you should have no problem in porting this code. I myself use C# more often then Java nowadays and in C# I prefer to use a property instead of an instance() method as this is visible by the name I gave it in the example above. Normally, the general consensus between the Java developers, especially in the Enterprise industry, is that the convention for this method is getInstance() . It does not matter how you are going to call it really. I prefer it to be short and I find it fitting for this example.
Conclusion
Using Design Patterns in your code is really something from which you can benefit a lot. Singleton is quite possibly the most common of them all along side with the Factory Design Pattern .
Knowing how to implement Singleton can serve you really well.
本文中的所有译文仅用于学习和交流目的,转载请务必注明出处和本文链接