顯示具有 基本功 標籤的文章。 顯示所有文章
顯示具有 基本功 標籤的文章。 顯示所有文章

2014年1月23日 星期四

[Programming] 傳值(pass by value) / 傳參考(pass by reference)

我們在呼叫一些方法(method)的時候,常常會要傳一些參數。然而,傳參數的方法不一樣,對記憶體的操作就不一樣,出來的結果也會有很大的機率是意料之外的不一樣。
.NET所提供傳參數的方式有兩種:

  1. 傳值 (passing by value)
  2. 傳參考 (passing by reference)

 Visual Studio 2010裡,傳值和傳參考的變數型態顏色會不一樣。

傳值:

傳值是很簡單的概念:在進入method之前,程式會在記憶體裡多宣告一塊與參數同樣資料型態的記憶體大小,然後把值複製過去
這塊多出來的記憶體就是這個method專屬使用,method跑完之後就會像垃圾一樣始亂終棄地丟掉了。
不過,並不一定在method跑完之後,這塊記憶體就會馬上被釋放掉。.NET的虛擬機器裡有自己的Garbage Collection機制來處理這些記憶體,理論上而言是不用太擔心這些問題。不過很多系統使用時間一久效能就會由等比級數般的速度變差,不曉得和這部分有沒有關係。


傳參考:

要理解傳參考,得先知道這裡的"參考"是什麼玩意兒。這也是一個我們天天都會用到的東西。
基本上,我們在宣告變數的時候就是在弄一個參考。在沒給值之前這個參考就是指到null,有物件或實值給它的時候它才會把自己到該記憶體的位址上。



知道這個以後,傳參考就變成一件很簡單的概念:它就是把參數的參考複製一份起來給method使用,用完就丟掉。
因為參考實際存的是記憶體位址,所以在method裡操作的參數和呼叫前的物件是同一塊記憶體位址。在Method裡對參數有任何value的變化,都代表著該物件的值是徹底的變了,影響範圍是會擴大到上一層的程式去。



在.NET裡,除了像是int, string, decimal....等諸如此類的最基本的原生資料型態是以傳值方式在做之外,所有物件都是傳參考,包括.NET內建的物件。如果沒掌握好,參數傳一傳很容易出現一些意料之外的bug。
所以如果曾經發生過在傳物件的時候到某某方法去之後值就變了的靈異事件,其實是有科學根據的,沒這麼靈異。


--分隔線--

Q:.NET在傳參考的部分有兩種寫法,如下圖。有什麼差別?


A:
第一種傳參考,編譯器會告訴你在傳參數之前要先經過初始化,不允許傳沒有指到實體物件的"空參考"。
第二種傳參考允許傳空參考進去method,但在離開method之前一定要有給值,不然編譯器會不給過。
除此之外,我不知道有沒有其它實質上的不一樣。



Q:傳參考和C/C++的傳址(Call by Address)有何不同?
A:傳參考是把參考複製一個讓method使用;傳址是更直接地把原本存記憶體位址的那個參考直接拿去給method用傳址是更直接地把原本存記憶體位址直接拿去給method用,而沒有多複製一個新的。

--分隔線--


其實這個觀念我講得有點心虛,當初在學指標的時候也沒有學得很好。如果有觀念上的錯誤請不吝指教。


ref:
[1] 深入淺出C#
[2] C# ref/out 關鍵字與傳遞參考型別參數

2012年10月31日 星期三

[ SQL ] JOIN in T-SQL

最近在某神祕專案裡常常會看到現有的Code在抓報表資料的時候,會讓資料庫一次、一次、一次地取大量資料,然後在server-side程式裡做轉換、統整、用LINQ查詢、統計以後再輸出給client端。
這樣的做法雖然在程式的閱讀上會很明確,哪個欄位代表什麼意義或過濾什麼條件都會很清楚,但是資料量超過一定程度以後,每一次從資料庫取出來的資料量本身就是一個負擔,再加上把資料做型態上的轉換、不斷宣告新物件裝資料、再用LINQ做查詢整合,從下命令到得到結果中間時間會變得非常久。

LINQ很方便,可以用,但不要過度地依賴。把SQL Script的基本功打好,一次把要所需的資料全部精準地過濾、計算,回傳到server-side程式的時候就是一個完整的結果,再來就只要輸出就行了,雖然可能中間會JOIN好幾張表,但只要Index建好,查詢效率就不會太差。

一個案子裡的程式開發人員能力有高有低,不是每個人都能夠寫出DBA等級的查詢句,再加上時程壓力,很多開發人員都會不願意動腦子想SQL該如何下比較好,而是急著把成果生出來而以最直覺的方式去下指令,然後就會寫出在迴圈裡把資料庫的連線開開關關,然後抓出來的資料還要轉成特別的物件再用LINQ去下過濾條件的程式,按下查詢之後可能天都黑了才生出報表(前提是不會跑出奇奇怪怪的Exception)。
這樣的情況一路開發到後期,或是再下一期兩期,後面的每一隻程式都是複製貼上,大部分的查詢都是用同樣的方式在開開關關建建放放,系統負擔不大才怪。

根據這樣的假設和跳躍式思考,我認為如果能夠活用各種JOIN的方式,把所需的資料一口氣全找出來,能夠減少資料庫的負擔,進而增加系統處理的效能。

聽說這篇原本是要寫JOIN?

--廢話終於講完了的分隔線--

簡單來說,JOIN就是把兩個集合(資料表),透過不同的方式得到不同的集合。而在T-SQL(SQL Server)中,常用到的JOIN方式不外乎那幾種:
  1. INNER JOIN:取出來的資料為兩個集合都有的資料。
  2. LEFT (RIGHT) OUTER JOIN:以左邊(或右邊)的集合為基底,查詢與另一張表相對應的欄位。
  3. FULL OUTER JOIN:左外關聯和右外關聯的聯集。
  4. CROSS JOIN :兩個集合的每一筆資料都會被取出來。

來用範例讓這中間的差異變得更明顯。



現在,我們有一些資料表和資料....


這是會員,裡面只有簡單的ID、姓名和職業,其中職業是以代碼來表示,來源是另一種表。
在這裡,我特別設計了一個會員,假設他用了一些特殊的方式寫入這個系統,使他允許在「職業」那一欄沒有輸入東西。


職業,就只有ID和名稱而已,超級基本的代碼表。



產品,內容有ID、名稱、類型代碼和庫存量,和會員那裡一樣的是:類型代碼的來源也是另一張表。


老梗的代碼表,這是產品類型


我的例子還需要一些「交易記錄」才完整,長得就像上面一樣。我要記錄的資料並不多,只要知道「哪個會員在什麼時間買了什麼東西,買了幾個」就可以了。所以設計出來就如圖所示。
會員就存會員ID、產品就存產品ID、交易時間就存寫入資料表當下的系統時間,還有一個整數的數量。


就這樣,我們就建好了簡單的環境,再來就是仔細的看一下各種JOIN方法會呈現什麼結果。



INNER, LEFT, RIGHT JOIN:

假設今天我要找出會員及其職業名稱,那我可以這樣下句子,並且得到以下的結果.....


我也可以這樣下....


我還可以這樣下....


差異在哪裡?
INNER JOIN所抓出來的,會是JOIN左右兩邊資料表都有互相關聯的資料。這個環境裡,沒有會員是「無業」,也有一個戳戳的傢伙沒有輸入職業,這兩種東西就不會顯示出來。
LEFT JOIN就是LEFT OUTER JOIN。它就會以JOIN左邊的Member當底,然後把JOIN右邊的Job相對應的資料撈出來顯示。重點來囉!JOIN左邊的Member資料,只要沒有下特別的條件去過慮,那每一筆資料都會被取出來,即使Job裡沒有相對應的欄位,它也會弄一筆資料出來,空的欄位就用null來表示。
RIGHT JOIN和LEFT JOIN不一樣的地方,只有方向而已,把上面那段的左邊改成右邊就行了。範例就是…沒有無業的人,它還是會把無業抓出來,然後把前面的欄位放null。

還有一種特別的JOIN叫作FULL OUTER JOIN,其實就是....LEFT JOIN和RIGHT JOIN有出現過的資料全放一起就對了,專業的名詞叫作「聯集」。瞧,例子在下面。LEFT JOIN和RIGHT JOIN有出現過的資料全出現在下面。


CROSS JOIN:
說起來CROSS JOIN是一種滿特別的JOIN方式:它不管JOIN左右兩邊資料表的資料有沒有任何關聯,它都會把每一組配對組合資料都顯示出來。下面那張圖,得到的結果就是職業和產品類型的各種配對。


「這種沒有關聯的兩張表,配對出來的結果有什麼用?」

如果你想知道「各種職業購買各種產品的記錄筆數總和」之類的東西,這就有用了。
如此一來,我就不用在程式裡跑迴圈下查詢查到死。




--好睏哦....--

善用JOIN,你的人生將會一片光明 (超敷衍)。
至少在面對統計報表或是神祕的實體關係時比較不會有所畏懼。


是的,我最近在搞報表,快被搞死了。


[範例Code,含建置和查詢]

2012年9月27日 星期四

[狗大便] 不容易被發現哪裡出問題的問題 (2) - Int to StringFormat

只要扯到字串,事情就會變得很麻煩。
有學過C就知道,我們看到的字串(string)都是由一組字元(char)陣列組合而成的,而字串最後還有一個'/0'的符號作為字串的結尾。至於要怎麼去操作字串,加大或縮小,複製或貼上,都得用控制陣列的index和動態記憶體配置來實現,而在約耳的書裡也提到,用程式來調配記憶體是最麻煩的一件事。你就知道以前在搞字串的時候有多麻煩。

然而,現在許多高階語言(.NET, Java, ....等)都有字串的原生資料類別,還有武功高強的virtual mechine在OS之上、程式碼之下運作。
那真是一項偉大的發明。不僅減少了開發人員許多麻煩,讓開發應用程式的人越來越不需要考慮太多字元與字元間和記憶體的問題,同時也減少了開發人員靈活動腦的機會(越來越笨囉)


總而言之,這次的問題就是跟字串有關。我相信這雖然似乎在我的部落格中是第一篇和字串有關而且容易debug沒留意到的問題,但不會是最後一篇。因為字串太麻煩了。


--廢話分隔線--


一般人的邏輯裡沒有什麼"整數"和"字串"之間的分別,嘴巴裡講出來的數字能加能減能排序都非常的直覺。但是在寫程式的時候,編輯器可沒這麼聰明。一開始宣告他是哪一種資料型態(data type),它就是該型態,沒給它做特別的動作的話,它就不會變態 (喂)。

今天的問題是什麼呢?反正就是跟資料型態有關。

需求:
我希望在開一張新訂單的時候,訂單編號能夠由系統自己產生,格式為兩位數的西元年份加三位數該年度的訂單數量+1,中間用dash隔開(yy-ccc),如:12-001

聽起來很容易吧?而且需求也非常的明確,應該是不會有什麼大問題?
問題都藏在開發者內心的空隙,也就是粗心啦!

這個簡單的需求分了兩部分:兩位數的西元年份和三位數的訂單數量。我們一個一個來看。

1. 西元年份
.NET的DateTime直接呼叫Now,就會自己回傳本機的系統時間。我們取其中的年(預設為西元年),再取後兩位,就可以得到我們要的部份。
想法很簡單,但是那是聰明如我們才這麼覺得。沒做什麼特別的編程,程式是不會做任何跳躍式的思考的。所以,我們在中間還得再考慮資料型態的問題。

直接用DateTime.Now,取出來的系統時間,資料型態為DateTime。
再拿其中的年,可以得到以int來表示的西元年。
我們要拿西元年的後兩位,必須先把取得的int西元年用ToString()轉成字串以後,再用Substring(2, 2)來取後兩位。
看見沒有?短短一個「兩位數的西元年份」,做了幾次的轉型?串起來的結果就像這樣…

  string strYear = DateTime.Now.Year.ToString().Substring(2, 2);

不過這個問題倒還好。反正中間如果轉型沒轉好,在IDE那裡按下play的時候也不會給過。


2. 訂單數量
訂單數量要從Database裡去得到明確的值,所以現在已假設有一個DAO能回傳正確的當年度訂單數量,我們得到這個值之後再加一,就是我們要的值了。


  string strCount = (dao.getCount() + 1).ToString();


就這麼簡單,就把我們要的搞定了。

事情結束了嗎?



--還沒結束的分隔線--




還沒結束是因為輸出並不如預期。

  string strOutput = strYear + "-" + strCount;

就這樣輸出,結果會變成 12-1, 12-2, 12-3...
關鍵就在於數量被轉成字串的時候沒有特別給他既定的格式,所以直接輸出成沒有補0或其它格式的樣子。

數量被轉成字串之後也來不及再做轉型了。你可能會直覺地去改輸出格式:
  string strOutput = string.Format("{0:00}-{1:000}", strYear, strCount);
輸出結果還是12-1, 12-2, 12-3...
因為字串資料不會因為在format裡面給了哪些pattern而格式特別的變化。


所以,要輸出像是12-001, 12-002這樣的組合字串,要嘛就是在數量被轉成字串之前就先給他pattern
  string strCount = string.Format("0:000", dao.getCount() + 1);
要嘛就是最後輸出的時候再轉成字串。
  int iCount = dao.getCount() + 1;
  string strOutput = string.Format("{0}-{1:000}", strYear, iCount);


這個東西不難,寫過點.NET的學生其實都會。就怕常常一時粗心,這樣的問題卡了三、四百塊

2012年7月3日 星期二

[狗大便] 不容易被發現哪裡出問題的問題 (1) - DateTime

去你媽的這篇文居然在電腦跑很慢的時候被吃掉了,害我又得再重po一次,幹你媽的!

--幹你媽的分隔線--

問題背景:
以下是source code。
這段method的主要動作,是檢查兩個實體物件的內容是否完全相同。
像這種左右對稱的寫法,看起來好像沒什麼問題,常理來說應該可以達到我預期的目的。

但是實際在運作的時候,就是會有時候和預期中的運作結果不一樣。有時候可以很正常,有時候就會錯,搞得全等都不全等了。
那麼,到底是哪裡出了問題?















--解答在此--

資料型態的轉換,中間會有一個讓人頭疼的風險:資料的失真,尤其是失真的過程還讓人不知不覺的時候。

那像這種資料,在什麼情況下會失真?

實驗的結果為:把從C#程式裡取得的DateTime寫入SQL Server的DateTime的過程
DateTime是一種用來表示日期和時間的類別,在寫應用程式的時候十分的泛用。但是各家的定義為何,就不太一定了。

大方向上當然是不會有什麼問題,問題是出在Ticks這種微小精細的東西上。.NET的Ticks定義為從0001-01-01 00:00開始的計算100奈秒的差距量;而SQL Server的Ticks則是1970-01-01 00:00開始。定義上有差,跑出來的數字當然就不一樣。

想當然爾,如果把一個從.NET取得的DateTime存進SQL Server,再原封不動地拿出來和原本的值做全等比較(==),自然就會有讓人感受不出來的差異。 

所以,我把程式稍做修改,世界就和平了~

ref:
[1] http://hi.baidu.com/raybook/blog/item/6702d32a98ce88315343c18b.html
[2] http://seesharper.wordpress.com/2008/07/08/sql-server-datetime-vs-net-datetime-battle-of-accuracy/

2012年3月14日 星期三

[Programming] 物件導向 (5) - 介面

什麼是介面 (interface) ?這個名詞有沒有很熟悉?還是覺得很陌生?還是覺得很熟悉常常聽到又不知道具體的意義是什麼?
我們常常在講的使用者介面 (user interface) 或人機互動 (human-machine interaction),從維基百科上已有具體的文字定義[1]:
簡單來說,就是一個能夠有效控制機器,或是從機器裡得到回應的空間。廣義來說,作業系統、程序控制器、重機械控制器、汽車方向盤和計速器…等,都算是使用者介面。
我個人是將它想像成人和機器互動的中間那一層,而人可以從中間那一層去控制機器,機器會透過中間那層來傳達讓人知道的訊息

那在物件導向程式設計裡,介面又代表什麼?
這裡所說的介面,並不是指「使用者點了什麼按鈕,然後程式就會執行什麼動作」的那種介面,這是被歸類在使用者介面的範圍裡,和OOP中的介面不一樣。
這裡所謂介面,指的是一種特別的抽象類別,裡面有一個(或一群)方法,讓不同物件實現這些方法的時候,能做不同的事
這是不是聽起來很熟悉?現實出來的效果是不是有點像多型?沒錯~它是一種多型的實現方式(請參考三相之力[4])。

定義講多了,就好像都在嘴炮理論一樣。我們來看一下實際的例子。
有一個類別圖如下。反正就是有一個系統設計者,丟了這樣的類別圖給你,要你做出一個interfaceAction的介面,然後讓Fighter和Archer這兩個類別可以實現它。
這就是介面的長相:
public interface interfaceAction {
    void Run();
    void Walk();
    void Jump();
    void Sit(); 
}
這是Java語法。其實C#、PHP等OO語言都差不多,而且這裡要注意的不是語言上的差異。
一個介面,這樣寫就算完成了。眼睛一看就會發現它沒有定義各個方法實際要做哪些事,就只是把方法名稱列出來而已,等其它類別來實現 (realize) 它

然後如果有其它類別要使用這個介面的時候,它就會這樣去做:
public class Fighter implements interfaceAction {
    public Fighter(){
        System.out.println("Fighter Creation!");
    }

    @Override
    public void Run(){
        System.out.println("Fighter running...");
    }
    
    @Override
    public void Walk(){
        System.out.println("Fighter walking...");
    }
    
    @Override
    public void Jump(){
        System.out.println("Fighter jump once!");
    }

    @Override
    public void Sit(){
        System.out.println("Fighter sitting!");
    }
}
這段程式第一行比較要注意的是,在Java裡要完成一個介面時,中間要掛implements關鍵字,然後在後面加interface的名稱。假如程式很複雜,需要implemets兩個以上的interface,那就把interface名稱用逗點隔開就行了。
然後,這個實現interfaceAction的類別,要逐一實現interfaceAction裡的每一個動作,不然基本上編譯器不會給過。然而,每個要implements interfaceAction的類別,裡面的方法動作可以不一樣,多型就這麼實現了。
基本上使用上就是如此。如果有其它類別,比方說是Archer類別,要實現interfaceAction,就用同樣的方法來實現介面,並且在要把interfaceAction裡的方法也都寫過一次。

你可以把它想像成一個使用者介面(像是遙控器的按鈕和面板),上面每個哪裡有幾個按鈕或哪裡顯示東西都是固定好的,然後把它接到不同的機器元件上面之後,它的按鈕動作和顯示出來的東西就不一樣。OOP的介面只是把這種概念實現出來而已。


啊?你說每個動作都一樣,重寫一遍太麻煩?那就不要用interface啊!
我這樣講並不是不負責任的說法,不過這是設計上的問題,下次再說。你在這裡只要先知道:實現interface的元件,必須把所有interface裡的方法都實現出來
也許你會在程式書上面看到「abstract和interface之間的比較」之類的章節[3],我不打算在這裡提。因為abstract和interface之間雖然行為上有這麼一點相似,但是它們的概念卻是大大的不同。我認為,直接從這兩種東西的原始定義來看程式和用法,會容易理解許多。



ref:
[1] User interface from Wikipedia
[2] Interface (computing) from Wikipedia
[3] Java Gossip: 介面(interface)型態
[4] [Programming] 物件導向(3) - 三相之力

2012年3月3日 星期六

[Programming] 物件導向(4) - 抽象

我們常常會對一種模糊不清的東西,以「抽象」來形容它。而在OOP裡,也有抽象的類別可以定義。正如「模糊不清」的形象,抽象類別本身並不能被實體化,而是需要被能夠實體化的類別繼承了以後,才能真正使用。抽象的概念不是指「模糊不清」的東西(模糊不清的東西是要怎麼寫成程式?),而是一種有範圍性的大方向,或是一種大種類的東西。

以下是範例,就和很多教科書一樣,只寫怎麼使用,感受不出它的威力。
public abstract class abstractPerson {
    protected String Name;
    protected boolean Sex;
    protected String Email;
    protected String Offer;
}

public class Engineer extends abstractPerson {
    private LinkedList Skills;
    public Engineer(){
        Name = "";
        Sex = true;
        Email = "";
        Offer = "";
        Skills = new LinkedList();
    }
}

上面那個例子,我設計"人"的抽象類別,然後讓"工程師"來繼承它,若我還有"行政作業員"或"財會小姐"之類的類別,也同樣可以透過這樣的方式繼承"人"。
其它例子....像是"財產"可以做成抽象類別,而"土地財產"、"建物財產"、"運輸設備"這些類別就可以繼承"財產";
或是"棋子"可以做成抽象類別,"國王"、"皇后"、"主教"....各別做成繼承"棋子"的實體類別。

還有另一種思考的方向是.....抽象類別可以視為繼承它的實體類別的大種類,用白話文來說,可以講成"是一種 (is a kind of)"。像前面財產的例子,就可以翻譯成土地財產是一種財產運輸設備也是一種財產…等。用這種方式來思考,是比較容易將抽象類別應用在系統設計上。

2012年3月2日 星期五

[Programming] 物件導向(3) - 三相之力


三相之力。(From 魯阿魯)
好吧,我承認我很宅。


三相之力是LOL裡三種不同類型的中等武器合成的高級武器。會用三相之力來當副標是其來有自的。物件導向程式設計的概念中,有三種強大的能力,算是當年程式設計中跨時代的發明,分別是封裝(Encapsulation)、繼承(inheritance)、多型(polymorphism)。


封裝
如果要以一句話概括封裝的話,應該可以這麼說:外部的程式不能直接去存取物件裡的成員,而是必須透過特定的public存取權限的成員來存取物件內容。如果沒有封裝的概念,物件的意義就變得不怎麼重要了,C語言的struct也做得到同樣的事。把原本散成一團的程式封裝成一個一個的物件,就是為了讓程式裡物件和物件之間的依賴性變小,也就是隅合度變低,讓程式碼不會像一團陽春麵一樣,抓一條麵就把整團揪結在一起的麵全抓出來。
這應該不難理解。試著想像一下,如果你的封裝特性做得確實,外部程式在存取物件的時候就能透過幾個特定的方法而已,而這個物件實際經過哪些動作,外部的程式根本不需要知道。這時候,如果有什麼地方要改,看是物件裡面的動作,或是外部程式要改,而不至於需要把整隻程式和所有動作全部改掉。
然而,封裝只是建構物件導向設計的第一步而已....


繼承
有的時候,或很多時候,你會發現你需要用到的物件和其它物件只差一點點東西而已,只要稍微將原有的物件加以改寫或加工就能滿足你的需求。這時候,你不需要從頭到尾重新建置一遍,只要建立一個新類別,然後繼承那個需要加工的物件,就可以開始加工新類別來滿足自己的需求,而且不用動到一分一毫原有的類別(要動到的話也是可以)。
假如今天B類別繼承了A類別,我們會稱A類別是B類別的父類別(superclass)、B類別是A類別的子類別(subclass),此時,正如前述所說,B類別會擁有A類別的一切特徵,然後再看你要怎麼去寫B類別。
上述那使用繼承的案例,比較明顯的動機是重新使用(reuse)。事實上,繼承更強大的威力不只是reuse而已。透過繼承抽象類別,就能夠把不同的類別整合起來,產生一個更有彈性的架構。比方說,我有人、狗、貓三種類別,我就能夠把這三種類別相似的地方抽離出來,另外做出一個「動物」的抽象類別,並且讓人、狗、貓這三個類別繼承動物,之後,人狗貓這三種類別,同時也有動物的類別特性,之後在呼叫「動物」的時候,是能夠代入這三種類別的。幾乎所有設計模式(design patterns)都會用到繼承,而且都會用到抽象類別或介面的繼承。當我們日後介紹到抽象或設計模式的時候再詳細描述吧!
要注意的是,C++可以允許子類別繼承多個父類別,而Java和C#不行,詳細的原因不太清楚,只知道這種多重繼承會讓程式的複雜度提高很多,就算允許也不建議使用。


多型
簡單來說,它允許一個類別的同一個函數,在不同物件(被實體化的類別)裡做的事情不一樣,但是必須透過繼承才能達到這個目的。所以,先有繼承的概念,才能實現多型。
舉例:既然要繼承,當然必須有一個基層父類別A,還有繼承出來的子類別B和C。此時B和C就能夠透過override把原本A類別裡的方法a',根據自己的需求做改寫。讓B和C各別的a'執行自己的動作。
後來在拜讀了搞笑談軟工裡所介紹的多型,才明白其原理:今天如果呼叫一個物件裡的函式,並不是由呼叫函式的那一端 (sender) 來決定呼叫到的行為,而是由接收端來解釋。當我們由物件D裡呼叫B和C的a',執行的行為並不是由D來決定,而是由B和C來決定,所以,我們會在物件D裡得到B和C各別執行完a'的結果。




透過這三種概念,物件導向的發展空間開始擴大,設計出有彈性、可擴充、容易維護的軟體架構。
只要你會用的話。


ref:
[1] 思考物件導向(1)物件導向與封裝
[2] 搞笑談軟工

2012年2月17日 星期五

[Programming] 物件導向(2) - 名詞定義

自從在案子裡教了程式開發的教育訓練以後,就不斷在寫程式開發的文.....


有鑑於在物件導向程式設計裡,比起原先的基本程式開發的專有名詞還要再更多一點,而且根據地區性的不同還有不同的翻譯,為了避開這種語意上的混淆,我決定先整理一下物件導向程式設計裡會用到的專有名詞和其意義。

所謂的類別 (class),就是像我們上一篇所提到的Student那樣,可以把它想像成一種自己設計的特殊變數型態。就像標準C語言中的int, char....這種型態的意義是一樣的;也可以把它想像成是一種建構物件的藍圖,程式能夠以這個藍圖,產生出物件和藍圖一模一樣的物件。很多時候類別都會包含一個建構子 (constructor) ,用來定義該類別被實體化後的初始狀態。

物件 (object),就是已經用new和該類別的建構子將類別實體化後產生出來的東東。
上面這張圖是我在做專案的程式開發教材時所畫的圖,版權沒有。
如上述的圖所示,當宣告一個Guy類別,變數名稱為joe的時候,只會先產生一個叫做joe的Guy類別參考,還沒有實體記憶體可以來存資料。等到經過new以後,才會在實體記憶體裡抓一塊空格出來讓程式存東西,然後把joe這個參考指到這塊記憶體裡,這個動作稱為實體化,抓出來這塊記憶體,就稱之為物件(object),或稱之為實踐(instance)
所以,物件是真的有實體的。就像現實生活中,眼睛看到的每個東西都算是物件,而它們都有實體,而不是抽象的概念。

現實生活中的各種物件都有它自己的動作 (operation),或是被使用後產生什麼變化或結果。在物件導向程式設計裡,物件自發性的動作,我們稱之為方法 (method),以C語言的概念來說,也有人稱它為函式 (function)
像上一篇中Student類別裡的SexTransform()就是方法,做法是讓學生女變男或男變女。.....咳嗯,SexTransform(性轉換)嘛....自發性的動作...嗯。
總而言之,像鴨子會飛或是會呱呱叫,或是人會走路會跑步會跳會攻擊等等的,可以對該類別做成一個一個方法來使用,所製造出來的物件就不光只是個裝資料的容器而已,它還會動。不過有一點要注意的是,這些方法,因為是寫在類別裡面,所以是針對物件去做處理的方法,所以在還沒建一個實體之前,這些方法是不能用的。

當然,也是有一種類別或方法它是不用先把實體建構出來就可以呼叫方法的函式。這種東西,我們稱為靜態方法 (static method)。這種方法在一開始被呼叫後,就會把記憶體空一塊空間出來處理方法裡面要做的事,執行完了之後,空出來的記憶體不會被釋放掉,所以裡面的區域變數,狀態都會被存下來,不會不見。
所以,假如你的類別裡有一個靜態方法,然後程式在執行的過程用這個類別宣告了好幾個物件,這好幾個物件,都會共用同一個靜態方法,連方法裡的變數都是共用的。這個部分如果沒有掌握好,在debug的時候會把頭髮抓掉一半都不知道問題出在什麼地方。

有靜態方法,當然也有所謂的靜態類別 (static class),原理上和靜態方法是類似的:就是呼叫完之後記憶體空間不會不見。它和一般類別不一樣的地方在於:它不能被實體化,裡面也不能包含實體方法。其實這種靜態類別,通常被用於定義整個系統都會去使用,或是共用的函式集,像是存取資料表的動作、設計模式中的獨體模式 (Singleton) ...等。

以下程式碼為靜態類別和靜態方法的例子,保證不能使用,但是大致上來說就是如此而已。
static class DB
{
    private static SqlConnection sqlConn;
    private static SqlCommand sqlComm;
    private static SqlTransaction sqlTrans;
    
    public static bool Open(){
        try {
            sqlConn = new SqlConnection("");
            sqlConn.Open();

            sqlComm = new SqlCommand();
            sqlComm.Connection = sqlConn;

            sqlTrans = sqlConn.BeginTransaction();
            sqlComm.Transaction = sqlTrans;

            return true;
        }
        catch{
            return false;
        }
    }

    public static bool Update(string strSQL) {
        try
        {
            sqlComm.CommandText = strSQL;
            sqlComm.ExecuteNonQuery();
            return true;
        }
        catch {
            Close(false);
            return false;
        }
    }

    public static void Close() {
        Close(true);
    }

    private static void Close(bool succeed) {
        if (succeed)
        {
            sqlTrans.Commit();
        }
        else {
            sqlTrans.Rollback();
        }
        sqlConn.Close();
    }
}


ref:
[1] 網站製作學習誌
[2] 程式設計筆記byChris

2012年2月15日 星期三

[Programming] 物件導向(1)


不妨仔細想想,為什麼要用OOP?使用OOP有什麼好處?沒有它難道就會死人嗎?

C語言以前是以程序導向的方式做資料的交換和運算,並沒有什麼模組化、結構化的概念在裡面(其實還是有struct可以用,但是功能不強)。日子久了,同一隻程式不斷被維護、修改,或是又再增加新的需求,只會讓程式變得越來越難懂,越來越難維護,時間久了程式就沒辦法再持續維護下去了,程式就壞掉了。
這種程式呈現出來的樣子,就很像把三百五十種水果一起放到果汁機裡面打成汁再喝下去,你沒辦法知道讓你拉肚子 (bug) 的原因是哪一種水果,或是哪些水果的組合。

而OOP,能夠較容易將現實生活中的一切把它mapping成程式物件,進而描述物件和物件之間的關係,使程式容易和現實業務背景相結合,如此一來,就程式撰寫、閱讀上比較能夠容易被理解。所以,物件不光只是一個可以裝有客製化變數類型的"容器"而已,還有很多更近一步的關係可以建立及使用,以達到提高可維護性和「one rule, one place」的目的。
簡單來說:物件導向程式設計,能夠讓程式較容易被理解,也容易將其模組化

當然,一切都是以「能夠設計出適合的物件關係為前提」。



物件導向程式設計(Object-oriented programming, OOP)已經存在好長一段時間了,十多年前開始接觸VB的時候應該就有這種程式設計概念了,讓寫程式不光是用複雜(或不怎麼複雜)的程式指令來控制變數、計算和輸入輸出,更能讓現實生活中的實體概念直接對應到程式物件裡來,使程式更容易維護和撰寫。

要寫一個物件導向的程式其實沒有很難。就像在現實生活中,看得見的或看不見的實體,都可以把它想像成是一個個的物件,物件中有屬性,然後由外力來控制物件。寫該類程式也是如此。最基本的做法就是把要用程式表達出來的東西寫成類別(class),然後在需要使用到它的時候再宣告成一個(或多個)物件(object)。
一個超級老掉牙的例子:一個學生的類別,要記錄該生的姓名、性別和成績。
這個例子我完全不知道性別和成績之間的關係,但是管它的,反正需求就是這樣開....

class Student{
 private String name;
 private boolean sex;
 private int grade;

 //Constructor
 public Student(String name, boolean sex, int grade){
  this.name = name;
  this.sex = sex;
  this.grade = grade;
 }

 public String getName(){ return name; }
 public int getGrade(){ return grade; }
 public void SexTransform(){ sex = !sex; }
}

這樣寫,Student就非常簡單、草率、無用地完成了(請不要問我一個學生幹嘛要SexTransform()方法,我只能說這很宅)。
然而,如果OO概念只能達到這種程度的功能,那它不會流傳數十年,至今仍被人大量使用(應該吧?)。


つづく......

ref:
[1] 搞笑談軟工

2011年8月27日 星期六

[ SQL ] GROUP BY, HAVING

GROUP BY
如果有需求,是要得到某某分類的資料加總起來的數字是多少,可以利用GROUP BYSUM函式輕鬆得到解法。
比方說,一個資料表裡面有很多各種顏色的椅子,每張椅子都有各自的價錢,那麼我想知道各種顏色椅子的總價...
SELECT chair.color, SUM(chair.price)
FROM chair
GROUP BY chair.color

HAVING
這個語法要跟上面的GROUP BY一起使用。如果要把上述查詢出來的結果再進一步去過濾,就可以利用這個語法設定條件(概念上有點像WHERE)。
承上述例子,我想知道各種顏色椅子總價在30000以上的結果,那麼可以....
SELECT chair.color, SUM(chair.price)
FROM chair
GROUP BY chair.color
HAVING SUM(chair.price) > 30000
這個語法也可以用於過濾文字....
SELECT chair.color, SUM(chair.price)
FROM chair
GROUP BY chair.color
HAVING chair.color = 'Red'