俄羅斯方塊in MATLAB——從Singleton開始
但既然是自己挑的Supervisor,含著淚也得把他布置的俄羅斯方塊擼完。上回我簡要說過俄羅斯方塊在MATLAB中一種可能的數據結構。於是我沿用這個思路,開始寫用來表示俄羅斯方塊的類。目前為止我的代碼是長這樣的。聰明的讀者要是嫌在這裡看得太麻煩,可以去我Supervisor的Github看。當然由於我寫代碼的速度很快(並沒有),忙碌的讀者看到時可能已經更新了好幾個版本了(並不會)。
classdef (Sealed) TetrisBoard < handle % Singleton n properties (Access = private)n pHeight;n pWidth;n pBoardMatrix;n end % End of private propertiesn n n methods(Static)n function boardSingleton = createBoard(height, width)n persistent localObj;n if isempty(localObj) || ~isvalid(localObj)n localObj = TetrisBoard(height, width);n endn boardSingleton = localObj;n end n end % End of static methodsn n n methods (Access = public) % Mostly gettersn function [nrows, ncols] = getSize(obj)n nrows = obj.pHeight;n ncols = obj.pWidth;n endn n function aMatrix = getBoardMatrix(obj)n aMatrix = obj.pBoardMatrix;n endn end % End of public methodsn n n methods (Access = private)n function boardObj = TetrisBoard(height, width)n boardObj.pHeight = height;n boardObj.pWidth = width;n boardObj.pBoardMatrix = zeros(height, width);n end % End of private ctorn end % End of private methodsnend % End of classdefn
咦,好像看到了奇怪的東西。那個classdef後面的Sealed是什麼東西?那個神秘的handle是什麼意思?注釋里的Singleton又代表了什麼?歡迎大家來到走近科學,我們將為你一一解答。
讓我們首先對焦在handle上——主要是因為它跟Singleton沒什麼關係。Handle是MATLAB對pass by reference的實現。如果好奇的讀者想詳細了解MATLAB中的pass by reference和pass by value,可以參考這篇文檔。後續文章里我會提到為什麼這個類用pass by reference會比較方便。
那Singleton又是什麼呢?顧名思義,就是一個只能有一個實例(single instance)的類。那怎麼才能保證只有一個實例被創建呢?更具體地,當團隊來了咖喱國的小夥伴之後,如何保證這個類還能正確地維持著只有一個實例呢?當然我們可以在程序里搜索看看這個類被用了幾次,如果超過一次實習生的Supervisor就會被扣獎金;但鑒於Supervisor要養老婆養孩子養他們家門口那隻大黃,我決定還是善良地Google一下:
MATLAB singleton
果然一搜就搜到了!傳送門在這裡:
Control Number of Instances
嗯聰明的讀者你一定已經發現了,我基本上就是對著文檔里的例子抄的。但是為什麼要這麼做呢?我們知道,每個實例被創建的過程中都會調用一個叫做構造函數的函數。為了不讓實例被隨意創建,我們不讓用戶調用這個函數不就行了嗎?所以,對於類Foo,就有:
classdef Foon % Other stuffn methods (Access = private)n function obj = Foon % Function bodyn endn endnendn
把構造函數的access變成private,咖喱國小哥就沒法用啦!
但是總得有個辦法創建這個類吧?既然不能讓用戶直接用構造函數,提供一個靜態函數來調用構造函數總行吧,反正在一個類里可以隨便調用。現在我們唯一要做的,就是在靜態函數里統計一下有沒有實例被創建,如果有,就不給再建了,所謂一之謂甚,其可再乎。這其實就是Supervisor常常教導我的:
"All problems in computer science can be solved by another level of indirection"
由於構造函數一旦被調用,一個實例就被創建了(MATLAB是這樣的,但是一些語言如C++,構造函數調用結束實例才真正創建完成),於是聰明的Singleton發明者(我沒Google到是誰)就在構造函數外麵包了一層與實例無關只與類有關的靜態函數,然後就可以用它來記錄各種關於實例的信息了。比如如果你覺得singleton過於孤單,可以設計一個Doubleton來陪它:
classdef (Sealed) DoubletonClass < handle % Doubletonn methods(Static)n function aDoubleton = createDoubletonn persistent objCounter;n if isempty(objCounter)n objCounter = 0;n endnn if (objCounter < 2)n aDoubleton = DoubletonClass;n objCounter = objCounter + 1;n elsen error(Sorry, youve reached the quota for today)n endn end n endn n n methods (Access = private)n function aObj = DoubletonClassn % Code heren end % End of private ctorn end % End of private methodsnend % End of classdefn
那麼為什麼還要把這個類變成Sealed的呢?文檔上說,Sealed的類是不能被繼承的。其實不Sealed也可以(你在逗我?),只不過如果真有類繼承Singleton類,這個類首先得是Singleton,而且它的構造會非常麻煩而且容易出錯。另外,由於繼承類包含基類,一旦繼承類被創建,基類就再也無法被創建了。所以我想想既然在可以預料的將來(也就是我實習的這幾個月時間)我應該也不可能設計一個繼承類,我就乾脆把它封上了。
好了,俄羅斯實習生要繼續週遊世界的網吧了。Ciao!
參考資料:
[1] Comparison of Handle and Value Classes
[2] Control Number of Instances
[3] Class Constructor Methods
[4] Static Methods
[5] Class Attributes
推薦閱讀:
※MATLAB高級數據結構連載5: table 2
※面向對象(一)|面向對象概念及優點(以py為例)
※怎麼能提高自己代碼的可讀性與重用性?
※C++通過基類指針delete派生類數組,析構函數是虛函數,程序為什麼會崩潰?
