Method使用Singleton物件!
所謂Singleton模式就是獨一物件模式,若一個Class 已經有一個Instance,就無法再new 出第二個Instance。例如:系統只有一個加密器叫OnlyOneCipher,因為不想讓使用者能自行new 出一個新的加密器,所以就把OnlyOneCipher設計成一個Singleton物件。程式碼如下:
public class OnlyoneCipher { private static OnlyoneCipher cipher; private OnlyoneCipher() {} public static OnlyoneCipher getInstance() { if (cipher == null) { cipher = new OnlyoneCipher(); } return cipher; } public String encrypt(String source){ return doEncrypt(source); } }
因為是使用Private 的Constructor(),所以只有自已能夠產出OnlyoneCipher 的Instance。外部要取得OnlyoneCipher 的Instance只能經由getInstance()這個static method。
接下來我們來看其它物件會如何引用這個OnlyoneCipher。假設有一支MessageService是用來做訊息傳遞,當要傳遞加密訊息時需經由encryptMsg()來處理。程式碼如下:
public class MessageService { public String encryptMsg(String source){ return OnlyoneCipher.getInstance().encrypt(source); } }
因為只能經由getInstance()才可取得OnlyoneCipher物件,所以在程式中很直覺的寫法就是OnlyoneCipher.getInstance(),當引用OnlyoneCipher的地方越多,則OnlyoneCipher.getInstance()就會散到整個應用程式中。當有一天想要換更新的Cipher,這時候麻煩就來了,除非要把所有的OnlyoneCipher.getInstance()置換,否則換不掉。另外,在進行unit test 也很難測,因為OnlyoneCipher.getInstance()這種寫法等於是跟encryptMsg()綁死,抽換不掉,如果這個OnlyoneCipher又跟底層或是環境如KEY綁在一起,那就更難測了,在測試環境下,OnlyoneCipher也許會無法正常運作,而Singleton取得物件的模式,會使得測試更難進行。
當然,Singleton 還是可以測,只是寫法要變,而且要使用Mock Framework。因為Singleton 有用到Private Constructor 所以無法extend,因此就無法使用subclass Mock,要使用CGlib 動態產生Mock Class,這時就可以使用JMock、EasyMock、Mockito 等Framework 。利用Mock 來測試Singleton 的程式碼可以改寫如下:
public class MessageService { private OnlyoneCipher cipher; public void setCipher(OnlyoneCipher cipher) { this.cipher = cipher; } public String encryptMsg(String source){ return cipher.encrypt(source); } }
使用EasyMock 對OnlyoneCipher 做Mock,利用此Mock Inject 到MessageService中,以便進行單元測試!
public class SingletonTest { private MessageService service; private OnlyoneCipher cipher; @Before public void setup(){ service=new MessageService(); //對OnlyoneCipher 做Mock cipher=createMock(OnlyoneCipher.class); service.setCipher(cipher); } @Test public void testEncrypt(){ String source="Test String"; String encode="aaBBddaa"; expect(cipher.encrypt(source)).andReturn(encode); replay(cipher); String msg=service.encryptMsg(source); verify(); assertEquals(encode,msg); } }
如果非必要,儘可能少用Singleton 模式,如果想要產生獨一物件,不見得要用Singleton Pattern,可以透過Spring 來提供獨一物件。雖然Spring 控管的Bean 使用者還是可自行new ,但是比起Singleton 模式好做多了。
所以,程式用了很多Singleton,將來要置換會很麻煩而且測試也不容易,難怪會有人說 "Singleton Is Evil"。
沒有留言:
張貼留言