透過.NET C# 程式實作 OO 建國科技大學資管系饒瑞佶
物件導向程式設計 OOP 只要可以依據前述類別與物件方式設計程式的語言都可以來實現 OO 包括 :JAVA C++ C#... 也就是 OO 是設計方式, 不專屬於哪個程式語言 這裡選用.NET C#
開始前 -C# 程式語法與觀念對應 觀念 語法關鍵字 類別 class 實做類別 / 建立物件 new 繼承 : 不可以繼承 final 公開 public 不公開 private 不可 instance 的抽象類別 abstract 不可實際定義的抽象類別 interface 覆寫 override 當然還有更多
Visual C# 語法大規範 OOP 程式語言 大小寫有別 需要 ; 結尾 區段都是用 {} 包圍 都是 class 組成, 透過 name space 管理 // 或 /* */ 或 /// 作為註解
來實際作個範例 Class Car 屬性 + 方法 繼承 inheritance Class SubCar 屬性 + 方法
定義 Class Car 透過 Visual Studio Windows Form 操作
設定類別名稱為 Car
新建立的 Car 類別 透過 namespace 引用其他類別 類別所在位置 類別範圍
大家如果看預設就有的 Form1 透過 namespace 引用其他類別 類別所在位置 類別範圍
定義 Class Car 屬性 : 方法 :
建構子 constructor 類別中的一個特殊方法 透過這個類別建立物件時, 自動會執行的方法 方法名稱定義成與 Class 名稱一樣就可以 // 屬性 String car_type; // 車輛種類 int car_cc; // 車輛 cc 數 // 方法 // 建構子 public Car() { car_type = "Luxgen"; car_cc = 1800; }
其他方法 (1) 1 需要傳入兩個參數 public void setcar(string newcar_type, int newcar_cc) { // 設定屬性 car_type = newcar_type; car_cc = newcar_cc; }
2 其他方法 (2) 回傳值型態 public String getcar_type() { return car_type; } 需要回傳值
3 其他方法 (3) 回傳值型態 public int getcar_cc() { return car_cc; } 需要回傳值
完整的 Class Car 2 個屬性 1 個建構子 3 個方法
使用預設的 Form1.cs 來使用 Class Car 類別物件名稱 =new 類別名 ( 參數 );
透過 button1_click 事件呼叫取回預設的車樣式與 cc 數 private void button1_click(object sender, EventArgs e) { Car mycar = new Car(); // 顯示車輛內容 label1.text = " 車輛樣式 =" + mycar.getcar_type(); label2.text = " 車輛 CC 數 =" + mycar.getcar_cc(); }
Result
設定屬性資料並顯示 private void button2_click(object sender, EventArgs e) { // 透過類別 Car 建立物件 mycar Car mycar = new Car(); // 設定車輛種類與 cc 數 mycar.setcar(textbox1.text, Convert.ToInt16(textBox2.Text)); }
Result 結果不是我設定的資料? 4 1 3 因為沒有使用同一個物件 2
修改 button1_click 與 button2_click 只建立 1 份物件 取消分別建立的物件
結果正確了! result
設定整個 class 為 static 其他共用方式? 設定 class 物件為 static, 透過另一個 class
接著定義 Class SubCar(1) 繼承於前面建立的類別 Car
接著定義 Class SubCar(2) 繼承於前面建立的類別 Car
接著定義 Class SubCar(3) 繼承於前面建立的類別 Car
看一下 Car 類別
繼承 Class Car 建立 Class SubCar
先不在 SubCar 加任何屬性與方法 (1) 直接修改原 Form1 介面 加入一個按鈕
先不在 SubCar 加任何屬性與方法 (2) 直接修改原 Form1.cs 程式碼 建立 mysubcar 物件 private void button3_click(object sender, EventArgs e) { SubCar mysubcar = new SubCar(); label1.text = " 車輛樣式 =" + mysubcar.getcar_type(); label2.text = " 車輛 CC 數 =" + mysubcar.getcar_cc(); }
result
加入方法到 Class SubCar SubCar 中才有的方法 // 依據車種類回傳對應網站 public string gotourl(string car_type) { // 設定回傳值 string URLstring = ""; switch (car_type) { case "Nissan": URLstring = "http://new.nissan.com.tw/nissan"; break; case "Toyota": URLstring = "https://www.toyota.com.tw/"; break; } return URLstring; }
再加入同樣名稱方法到 Class SubCar OverLoading 覆載參數型態不同 public string gotourl(int car_cc) { // 設定回傳值 string URLstring = ""; switch (car_cc) { case 1: URLstring = "http://new.nissan.com.tw/nissan"; break; case 2: URLstring = "https://www.toyota.com.tw/"; break; } return URLstring; }
加入方法到 Class SubCar 使用 Overriding 覆寫 Override 取代 Car 類別中原有的方法 setcar // 覆寫 override 類別 Car 內的同名方法 public void setcar(string newcar_type, int newcar_cc) { // 設定屬性 car_type = " 從 SubCar 設定的車種 =" + newcar_type; car_cc = newcar_cc; } 變數的保護層級不正確
修改類別 Car 內的變數 ( 屬性 ) 保護層級 加入 public 修飾詞
完整的 Class SubCar 3 個方法 (overloading & override)
使用 Class SubCar 內的 setcar 方法 新加入的程式碼 private void button3_click(object sender, EventArgs e) { SubCar mysubcar = new SubCar(); // 呼叫 mysubcar 內的 setcar 方法 mysubcar.setcar(textbox1.text, Convert.ToInt16(textBox2.Text)); label1.text = " 車輛樣式 =" + mysubcar.getcar_type(); label2.text = " 車輛 CC 數 =" + mysubcar.getcar_cc(); }
result 2 1
呼叫 gotourl 方法 webbrowser
呼叫 gotourl 方法 private void button3_click(object sender, EventArgs e) { SubCar mysubcar = new SubCar(); // 呼叫 mysubcar 內的 setcar 方法 mysubcar.setcar(textbox1.text, Convert.ToInt16(textBox2.Text)); label1.text = " 車輛樣式 =" + mysubcar.getcar_type(); label2.text = " 車輛 CC 數 =" + mysubcar.getcar_cc(); webbrowser1.url = new Uri(mysubcar.gotoURL(1)); }
result
namespace 如果變動 Class Car 的 namespace, 那會影響什麼?
Form1 程式的改變 加入明確的 namespace 與類別名稱
Class SubCar 的改變 加入明確的 namespace 與類別名稱
讓 Class 不能被繼承 加入 sealed 關鍵字, 讓 Car 類別不能被繼承
幾個特殊的類別
有時候只定義而不實作 (implement) 抽象概念, 例如動物 現實講動物時是沒有意義的, 我們會問是人 狗 獅子或老虎, 也就是動物並不是具體可操作的 ( 物件 )( 無法物件化 ), 所以動物只要規範大方向, 例如動物都會移動, 那只要屬於 ( 繼承 ) 動物的都被要求要具體定義 (override 實作 ) 怎麼移動 稱為抽象類別
抽象類別 可以透過 abstract 語法定義 裡面可以定義具體方法 或抽象方法 抽象類別不可以用 new 建立物件 可以透過 interface 語法定義 (WCF 中就使用 ) 裡面不能定義具體方法 可以被多重繼承 可以用在 partial class 開發上
新加入一個 abstract class Animal
設定 class Animal 為抽象類別 abstract 設定為抽象類別
設定 class Animal 的方法 具體方法 abstract class Animal { // 具體方法 public string getdata(){ return " 來自 Animal 類別的資料 "; } // 抽象方法 public abstract string run(int how); } 抽象方法
加入 Form2
從 Form1 跳到 Form2 private void button4_click(object sender, EventArgs e) { Form2 f2 = new Form2(); f2.show(); f2.owner = this; // 設定 f2 的擁有者是 form1 this.hide(); }
從 Form2 使用類別 Animal private void button1_click(object sender, EventArgs e) { Animal myanimal = new Animal(); } 要如何使用 Animal 類別?
先使用一個類別 UseAnimal 繼承 Animal
在 UseAnimal 類別中會被要求實作 abstract 方法 使用 override class UseAnimal : Animal { public override string run(int how) { return " 通用的 run 方法 "; throw new NotImplementedException(); } }
此時就可以透過 UseAnimal 類別建立物件再回到 Fomr2 private void button1_click(object sender, EventArgs e) { UseAnimal myanimal = new UseAnimal(); // 呼叫 Animal 類別內的 getdata 方法 label1.text = myanimal.getdata(); }
result
接著加入一個 interface-plant
interface 不能定義具體方法 interface Plant { // 抽象方法 string getdata(); } 抽象方法
繼承實作 interface
繼承實作 interface 繼承 interface 實作 getdata 方法需要有 public 修飾詞 class UsePlant : Plant { public string getdata() { return " 來自 Plant 的資料 "; throw new NotImplementedException(); } }
Fomr2 private void button2_click(object sender, EventArgs e) { UsePlant myplant = new UsePlant(); label2.text = myplant.getdata(); }
result
Abstract vs. Interface 都可以只定義, 不實作 abstract 屬於 is-a 的概念, 例如定義一個鳥的 abstract 類別, 裡面有個 abstract 方法 飛, 那繼承鳥這個類別, 應該是老鷹或麻雀, 都會有飛這個方法, 也就是老鷹或麻雀是一種 (is-a) 鳥 Interface 屬於 有這種動作或行為 概念, 例如定義一個 飛翔 的 Interface, 裡面定義 飛 的方法, 那這個 Interface 可以被繼承做出飛機 鳥 火箭等類別或物件, 因為他們都有飛的行為 (is-a-part-of)
使用既有 class 例如按鈕
建立 Button 物件 使用既有的 Button 類別 建立方式 : 類別物件名稱 =new 類別名 ( 參數 ); Button bt1 = new Button(this);
建立與顯示按鈕物件 // 建立按鈕動態介面設計的基礎 Button bt = new Button(); bt.text = "ccc"; // 按鈕的位置 ( 正中央 ) bt.location = new Point((this.ClientSize.Width bt.width) / 2, (this.clientsize.height bt.height) / 2); // 按鈕加入 windows form this.controls.add(bt);
result
加入對應的事件 委派機制 // 按鈕加入事件 bt.click += new EventHandler(this.Btn_Click); void Btn_Click(Object sender, EventArgs e) { MessageBox.Show("test"); }
產生 Button 物件陣列 多個按鈕 -Button Array 委派 + 匿名函數 (lambda)
Button Array 事件
private void button3_click(object sender, EventArgs e) { Button[] buttonarray = new Button[3]; for (int i = 0; i< buttonarray.length; i++) { int index = i; buttonarray[i] = new Button(); buttonarray[i].location = new Point((i+1)*70,100+(i+10)*5); buttonarray[i].name = "button" + (i+1); buttonarray[i].text = "button" + (i + 1); buttonarray[i].size = new Size(70, 23); buttonarray[i].click += (sender1, e1) => this.display(index + 1); this.controls.add(buttonarray[i]); } } public void Display(int i) { MessageBox.Show("Button No :" +i); }
區別每個物件 private void button3_click(object sender, EventArgs e) { Button[] buttonarray = new Button[3]; for (int i = 0; i< buttonarray.length; i++) { int index = i; buttonarray[i] = new Button(); buttonarray[i].location = new Point((i+1)*70,100+(i+10)*5); buttonarray[i].name = "button" + (i+1); buttonarray[i].text = "button" + (i + 1); buttonarray[i].size = new Size(70, 23); //buttonarray[i].click += (sender1, e1) => this.display(index + 1); buttonarray[i].click += new System.EventHandler(this.Display); this.controls.add(buttonarray[i]); } } public void Display(object sender, EventArgs e) { var obj = sender as Button; MessageBox.Show("Button No :" + obj.name); }
多型 Polymorphism
多型 教科書定義 : Polymorphism means that the sender of a stimulus does not need to know the receiving instance s class. The receiving instance can belogin to an arbitrary class. If an instance sends a stimulus to another instance, but does not have to be aware of which class the receiving instance belongs to, we say that we have polymorphism. 一個訊息 (message or event or stimulus) 的意義是由接收者 (The receiving instance) 所解釋, 而不是由訊息發出者 (sender) 來解釋 也就是當程式在執行時, 只要接受者換成不同的物件或是 instance, 系統的行為就會改變, 具有這樣的特性就稱之為 polymorphism
例如 例如在一個班級中, 老師請學生搬桌子, 不論是請學生甲或是學生乙來搬, 老師 (sender) 同樣都會說 : 把桌子搬走 (message) 但學生甲 (receiving instance) 跟學生乙 (receiving instance) 搬桌子的方法並不相同, 學生甲也許是整張桌子抬起來, 學生乙可能是將桌子拖著走 ( 意義是由接收者 (The receiving instance) 所解釋 )
多型其他定義 當子類別的物件宣告或轉型成父類別的型別時, 還可以正確執行該子類別的行為時就具備多型 物件導向的多型機制, 是指當兩個以上的類別繼承同一種父類別時, 我們可以用父類別型態容納子類別的物件, 真正進行函數呼叫時會呼叫到子類別的函數, 此種特性稱之為多型
多型 以往程式在撰寫時就決定了要呼叫的動作或函式 但是在 OO 中設計了多型機制, 允許程式到執行階段才決定實際要呼叫哪一個動作或函式 例如動物 class 定義一個 run() 方法, 而動物可以被繼承實作成馬 獅子 老虎等動物, 每個都有 run 方法 等系統開始執行後, 在執行時若為獅子 ( 訊息接收者 ) 那就動態呼叫獅子的 run(), 以此類推
Animal 父類別 public string run() { return "Animal Run"; }
繼承 Animal 建立 AnimalCat 子類別 class AnimalCat : Animal { public string run() { return "Cat run"; } }
繼承 Animal 建立 AnimalDog 子類別 class AnimalDog : Animal { public string run() { return "dog run"; } }
使用多型呼叫
不使用多型呼叫 建立 2 個物件 設計階段就決定 決定要執行哪個物件
private void button1_click(object sender, EventArgs e) { // 建立 mycat 物件 AnimalCat mycat = new AnimalCat(); // 建立 mydog 物件 AnimalDog mydog = new AnimalDog(); int whichone = 1; switch (whichone) { case 1: label1.text= mycat.run(); break; case 2: label1.text = mydog.run(); break; } }
使用多型呼叫 執行階段決定 //// 第一種寫法 //AnimalCat cat = new AnimalCat(); //AnimalDog dog = new AnimalDog(); //showrun(dog); //// 第二種寫法 //AnimalCat cat = new AnimalCat(); //AnimalDog dog = new AnimalDog(); //Animal[] allanimal = {cat,dog }; //showrun(allanimal[0]); // 第三種寫法 Animal[] allanimal = new Animal[2]; allanimal[0] = new AnimalCat(); allanimal[1] = new AnimalDog(); showrun(allanimal[0]); private void showrun(animal obj) { label1.text = obj.run(); }
發現結果並不正確 答案永遠都是 "Animal Run 傳入 showrun 的都是型別為 Animal 的 AnimalDog 與 AnimalCat 物件, 雖然三者都有 Run, 但此時是以 Animal 父類別的 Run 為主
修改 Animal AnimalCat 與 AnimalDog 透過覆寫來達成
改得更直覺點
code 未來如果有其他動物要加入我們只要完成該動物的類別後在需要的地方建立物件並呼叫 showrun 就可以不需要再加入一堆 if 的判斷敘述維護上較為便利
差異? 多型的寫法 一般的寫法 如果只有幾個物件, 邏輯簡單, 那確實沒有差異但當物件數量變多, 邏輯變複雜時, 那就可以看到多型的好處
意思是 如果用一般寫法當物件變多時, 之後如果要修改顯示的方式, 那麼就需要到每個物件所在處修改 label1.text 這裡的語法, 維護上是一個負擔 使用多型寫法如果有修改, 我們只需要修改 showrun 內這一個地方的程式就可以 另外如果有額外的邏輯要處理, 我們也只要在 showrun 內進行異動就可以
所以, 使用多型時 程式碼可以寫的比較簡潔, 例如用一個迴圈來處理陣列等等方式, 避免寫一大堆的 if 或 switch 等等判斷式 以後需要加新工作時, 就在物件陣列中增加一個新物件, 不用再去更改 if 或 switch 區段程式, 這樣的寫法比較有彈性 當工作類型愈來愈多時, 此方法的效益就會很明顯 C# 中的委派也有類似機制, 針對的對象是方法