Design Patterns: Singleton Basics 設計模式遊戲中運用:單例基礎
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.
本文中的所有譯文僅用於學習和交流目的,轉載請務必註明出處和本文連結