紀錄 O'REILLY 深入淺出物件導向分析與設計 (Head First Object-Oriented Analysis & Design) 的讀後心得,並將範例轉為 C#.Net Code。
情境
客戶:Rick 的亂彈吉他店
需求:建立吉他庫存管理應用程式,並提供搜尋功能,為客戶配對心目中的理想吉他。
廠商:「低階編程」軟體公司
顧客:Erin (來到 Rick 的店尋找心目中理想的吉他)
版次:v0.1
【類別圖】
【程式碼】
Guitar.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Guitar
{
private string serialNumber, builder, model, type, backWood, topWood;
private double price;
public Guitar(string serialNumber, double price, string builder, string model, string type, string backWood, string topWood)
{
this.serialNumber = serialNumber;
this.price = price;
this.builder = builder;
this.model = model;
this.type = type;
this.backWood = backWood;
this.topWood = topWood;
}
public string getSerialNumber()
{
return serialNumber;
}
public double getPrice()
{
return price;
}
public void setPrice(double newPrice)
{
price = newPrice;
}
public string getBuilder()
{
return builder;
}
public string getModel()
{
return model;
}
public string getType()
{
return type;
}
public string getBackWood()
{
return backWood;
}
public string getTopWood()
{
return topWood;
}
}
}
Inventory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Inventory
{
private List<Guitar> guitars;// = new List<Guitar>();
public Inventory()
{
guitars = new List<Guitar>();
}
public void addGuitar(string serialNumber, double price, string builder, string model, string type, string backWood, string topWood)
{
guitars.Add(new Guitar(serialNumber, price, builder, model, type, backWood, topWood));
}
public Guitar getGuitar(string serialNumber)
{
return guitars.First(guitar => guitar.getSerialNumber().Equals(serialNumber));
}
public Guitar search(Guitar searchGuitart)
{
//return guitars.First(guitar => guitar.Equals(searchGuitart));
for (int i = 0; i < guitars.Count; ++i)
{
Guitar guitar = guitars[i];
string builder = searchGuitart.getBuilder();
if ((builder != null) && (!builder.Equals("")) && (!builder.Equals(guitar.getBuilder())))
{
continue;
}
string model = searchGuitart.getModel();
if ((model != null) && (!model.Equals("")) && (!model.Equals(guitar.getModel())))
{
continue;
}
string type = searchGuitart.getType();
if ((type != null) && (!type.Equals("")) && (!type.Equals(guitar.getType())))
{
continue;
}
string backWood = searchGuitart.getBackWood();
if ((backWood != null) && (!backWood.Equals("")) && (!backWood.Equals(guitar.getBackWood())))
{
continue;
}
string topWood = searchGuitart.getTopWood();
if ((topWood != null) && (!topWood.Equals("")) && (!topWood.Equals(guitar.getTopWood())))
{
continue;
}
return guitar;
}
return null;
}
}
}
Tester.cs (測試程式表單)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ricksGuitars_start
{
public partial class fmFindGuitarTester : Form
{
Inventory inventory = new Inventory();
Guitar whatErinLike = new Guitar("", 0, "fender", "Stratocastor", "electric", "Alder", "Alder");
public fmFindGuitarTester()
{
InitializeComponent();
inventory.addGuitar("V95693", 1499.95, "Fender", "Stratocastor", "electric", "Alder", "Alder");
inventory.addGuitar("V9512", 1549.95, "Fender", "Stratocastor", "electric", "Alder", "Alder");
}
private void fmFindGuitarTester_Load(object sender, EventArgs e)
{
Guitar guitar = inventory.search(whatErinLike);
string sRet = "Sorry, we have nothing for you.";
if (guitar != null)
{
sRet = "You might like this " + guitar.getBuilder() + " " + guitar.getModel() + " " +
guitar.getType() + " guitar : \n" + guitar.getBackWood() + " back and sides,\n" +
guitar.getTopWood() + " top.\nYou can have it for only $" + guitar.getPrice().ToString() + "!";
}
tbMsg.Text = sRet;
}
}
}
【測試結果】
Why ?? 明明有將符合搜尋條件的 Guitar 加入 Inventory,為何會搜尋不到呢?
(明明在 FmFindGuitarTester.cs 的 fmFindGuitarTester() 中,有加入兩筆 Guitar 資料加入 Inventory 中)
(為何在 Inventory 中找不到 whatErinLike 這把 Guitar 呢?)
顯然 Inventory 的 Search() 有問題,但原因似乎不是很明顯。
看出來了嗎?庫存紀錄裡的兩筆資料,builder 皆為 "Fender",但是 Erin 輸入做搜尋的條件是 "fender",原因似乎有點愚蠢,解決方法很簡單,只要將 Search() 內的字串比對,採用不分大小寫的方式即可。
但是,真的就這樣解決了就好嗎? 問題背後的問題似乎沒這簡單。 代誌嘸像憨人想的那麼簡單!!
由於此問題的產生,引發了幾個觀點:
- 「看看那些 string !實在真恐怖...不能用常數或物件代替嗎?」
- 「哇...從老闆的筆記看來,他想讓客人有多重選擇。Search() 方法難道不該傳回一個包含所有符合條件的項目清單嗎?」
- 「這個設計真恐怖!Inventory 與 Guitar 類別互相依賴太深,我無法相信這是有經驗的軟體人員所以建立的架構。」
相信大家想寫出「偉大軟體」,但什麼是「偉大軟體」? 又該從何處下手呢?
- 對客戶友善的程式設計師說:「偉大軟體總是做客戶要它做的事。即使客戶突發奇想,以新方式使用軟體,它還是能交付客戶預期的結果。」
- 物件導向的程式設計師說:「偉大軟體是物件導向的程式碼。因此沒有一堆重複的程式碼,每個物件將自己的行為控制得當,擴展也相當容易,因為你的設計既穩固又有彈性。」
- 設計大師說:「偉大軟體使用經過千錘百練的設計模式與原則。物件保持鬆散耦合,程式碼「"禁止修改而關閉,允許擴展而開放」。讓程式碼更能重複利用,不必重作每一件事,可以一次又一次的運用程式零件來完成。」
「偉大軟體」的意義為何?
- 偉大軟體必須讓客戶滿意,做客戶要它做的事。贏得客戶芳心,讓客戶認為他是偉大的。
- 偉大軟體是設計良好、編程良好、易於維護、可重用與擴展。讓你的程式與你一樣聰明。
偉大軟體的三步驟
Step 1:確認你的軟體做客戶要它做的事。
把重點放在客戶上,確保應用程式做它應該先做的事。這裡是收集需求與分析工作所著力之處。
Step 2:應用基本的 OO 原則,增加軟體的彈性。
一旦軟體可正常運作後,找出重複的程式碼,運用良好的 OO 編成技術改善它。
Step 3:努力達成可維護、可重用的設計。
運用設計模式與 OO 原則,讓它經得起時間的考驗。
版次:v0.2
開始朝向「偉大軟體」之路邁進吧~
第一站:讓軟體能正常運作
要達到這個目的,程式碼必須做下列修改:
- 丟棄 string 比較。增加 Builder、Type 與 Wood 列舉。
- 搜尋結果必須能傳回符合客戶需求的所有 Guitar 清單。
【類別圖】
【程式碼】
Guitar.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public enum Builder { FENDER, MARTIN, GIBSON, COLLINGS, OLSON, RYAN, PRS, UNSPECIFIED };
public enum Type { ACOUSTIC, ELECTRIC, UNSPECIFIED };
public enum Wood { INDIAN_ROSEWOOD, BRAZILIAN_ROSEWOOD, MAHOGANY, MAPLE, COCOBOLO, CEDAR, ADIRONDACK, ALDER, SITKA, UNSPECIFIED };
public class Guitar
{
private string serialNumber, model;
private Builder builder;
private Type type;
private Wood backWood, topWood;
private double price;
public Guitar(string serialNumber, double price, Builder builder, string model, Type type, Wood backWood, Wood topWood)
{
this.serialNumber = serialNumber;
this.price = price;
this.builder = builder;
this.model = model;
this.type = type;
this.backWood = backWood;
this.topWood = topWood;
}
public string getSerialNumber()
{
return serialNumber;
}
public double getPrice()
{
return price;
}
public void setPrice(double newPrice)
{
price = newPrice;
}
public Builder getBuilder()
{
return builder;
}
public string getModel()
{
return model;
}
public Type getType()
{
return type;
}
public Wood getBackWood()
{
return backWood;
}
public Wood getTopWood()
{
return topWood;
}
}
}
Inventory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Inventory
{
private List<Guitar> guitars;
public Inventory()
{
guitars = new List<Guitar>();
}
public void addGuitar(string serialNumber, double price, Builder builder, string model, Type type, Wood backWood, Wood topWood)
{
guitars.Add(new Guitar(serialNumber, price, builder, model, type, backWood, topWood));
}
public Guitar getGuitar(string serialNumber)
{
return guitars.First(guitar => guitar.getSerialNumber().Equals(serialNumber));
}
public List<Guitar> search(Guitar searchGuitart)
{
List<Guitar> matchingGuitars = new List<Guitar>();
matchingGuitars.Clear();
for (int i = 0; i < guitars.Count; ++i)
{
Guitar guitar = guitars[i];
if (searchGuitart.getBuilder() != guitar.getBuilder())
{
continue;
}
string model = searchGuitart.getModel();
if ((model != null) && (!model.Equals("")) && (!model.ToLower().Equals(guitar.getModel().ToLower())))
{
continue;
}
if (searchGuitart.getType() != guitar.getType())
{
continue;
}
if (searchGuitart.getBackWood() != guitar.getBackWood())
{
continue;
}
if (searchGuitart.getTopWood() != guitar.getTopWood())
{
continue;
}
matchingGuitars.Add(guitar);
}
return matchingGuitars;
}
}
}
Tester.cs (測試程式表單)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ricksGuitars_start
{
public partial class fmFindGuitarTester : Form
{
Inventory inventory = new Inventory();
Guitar whatErinLike = new Guitar("", 0, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
public fmFindGuitarTester()
{
InitializeComponent();
inventory.addGuitar("V95693", 1499.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
inventory.addGuitar("V9512", 1549.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
}
private void fmFindGuitarTester_Load(object sender, EventArgs e)
{
List<Guitar> matchingGuitars = inventory.search(whatErinLike);
string sRet = "Sorry, we have nothing for you.";
if (matchingGuitars.Count > 0)
{
sRet = "";
foreach (Guitar guitar in matchingGuitars)
{
string ss = "You might like this " + guitar.getBuilder().ToString() + " " + guitar.getModel() + " " +
guitar.getType().ToString() + " guitar : \r\n" + guitar.getBackWood().ToString() + " back and sides, " +
guitar.getTopWood().ToString() + " top.\r\nYou can have it for only $" + guitar.getPrice().ToString() + "!\r\n\r\n";
sRet += ss;
}
}
tbMsg.Text = sRet;
}
}
}
【測試結果】
版次:v0.3
第二站:增加軟體彈性
這裡我們來探討一下,Invertory 類別裡的 search() 方法,當使用者要搜尋心目中離想吉他時,需要傳入整個吉他物件,但是序號(serialNumber)與價格(price)並不需要提供,序號對每一把吉他是唯一的,且 Rick 並不想將價格列入搜尋條件之一,因此使用者只需要提供要做比對的規格即可,並不需要提供整的吉他物件。因此,我們需要增加一個 GuitarSpec 的類別,來作為使用者搜尋吉他時,傳入 search() 方法的物件。
但是,這樣 GuitarSpec 的部份內容,不就會與 Guitar 重複了嗎?
這時就要利用基礎封裝技巧,將程式分成一組一組合乎邏輯的零件。原則如下:
- 物件應該做其名稱所指之事。
- 每個物件應該代表單一概念。
- 未使用的特性(屬性 property)是無用的贈品。
封裝讓你將應用程式零件的內部工作隱藏起來,但是讓零件在做什麼更顯得清楚。
因此,重新修改如下:
【類別圖】
【程式碼】
GuitarSpec.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public enum Builder { FENDER, MARTIN, GIBSON, COLLINGS, OLSON, RYAN, PRS, UNSPECIFIED };
public enum Type { ACOUSTIC, ELECTRIC, UNSPECIFIED };
public enum Wood { INDIAN_ROSEWOOD, BRAZILIAN_ROSEWOOD, MAHOGANY, MAPLE, COCOBOLO, CEDAR, ADIRONDACK, ALDER, SITKA, UNSPECIFIED };
public class GuitarSpec
{
private string model;
private Builder builder;
private Type type;
private Wood backWood, topWood;
public GuitarSpec(Builder builder, string model, Type type, Wood backWood, Wood topWood)
{
this.model = model;
this.builder = builder;
this.type = type;
this.backWood = backWood;
this.topWood = topWood;
}
public Builder getBuilder()
{
return builder;
}
public string getModel()
{
return model;
}
public Type getType()
{
return type;
}
public Wood getBackWood()
{
return backWood;
}
public Wood getTopWood()
{
return topWood;
}
}
}
Guitar.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Guitar
{
private string serialNumber;
private double price;
private GuitarSpec spec;
public Guitar(string serialNumber, double price, Builder builder, string model, Type type, Wood backWood, Wood topWood)
{
this.serialNumber = serialNumber;
this.price = price;
this.spec = new GuitarSpec(builder, model, type, backWood, topWood);
}
public string getSerialNumber()
{
return serialNumber;
}
public double getPrice()
{
return price;
}
public void setPrice(double newPrice)
{
price = newPrice;
}
public GuitarSpec getSpec()
{
return spec;
}
}
}
Inventory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Inventory
{
private List<Guitar> guitars;
public Inventory()
{
guitars = new List<Guitar>();
}
public void addGuitar(string serialNumber, double price, Builder builder, string model, Type type, Wood backWood, Wood topWood)
{
guitars.Add(new Guitar(serialNumber, price, builder, model, type, backWood, topWood));
}
public Guitar getGuitar(string serialNumber)
{
return guitars.First(guitar => guitar.getSerialNumber().Equals(serialNumber));
}
public List<Guitar> search(GuitarSpec searchGuitart)
{
List<Guitar> matchingGuitars = new List<Guitar>();
matchingGuitars.Clear();
for (int i = 0; i < guitars.Count; ++i)
{
Guitar guitar = guitars[i];
GuitarSpec guitarSpec = guitar.getSpec();
if (searchGuitart.getBuilder() != guitarSpec.getBuilder())
{
continue;
}
string model = searchGuitart.getModel();
if ((model != null) && (!model.Equals("")) && (!model.ToLower().Equals(guitarSpec.getModel().ToLower())))
{
continue;
}
if (searchGuitart.getType() != guitarSpec.getType())
{
continue;
}
if (searchGuitart.getBackWood() != guitarSpec.getBackWood())
{
continue;
}
if (searchGuitart.getTopWood() != guitarSpec.getTopWood())
{
continue;
}
matchingGuitars.Add(guitar);
}
return matchingGuitars;
}
}
}
Tester.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ricksGuitars_start
{
public partial class fmFindGuitarTester : Form
{
Inventory inventory = new Inventory();
GuitarSpec whatErinLike = new GuitarSpec(Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
public fmFindGuitarTester()
{
InitializeComponent();
inventory.addGuitar("V95693", 1499.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
inventory.addGuitar("V9512", 1549.95, Builder.FENDER, "Stratocastor", Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
}
private void fmFindGuitarTester_Load(object sender, EventArgs e)
{
List<Guitar> matchingGuitars = inventory.search(whatErinLike);
string sRet = "Sorry, we have nothing for you.";
if (matchingGuitars.Count > 0)
{
sRet = "";
foreach (Guitar guitar in matchingGuitars)
{
GuitarSpec guitarSpec = guitar.getSpec();
string ss = "You might like this " + guitarSpec.getBuilder().ToString() + " " + guitarSpec.getModel() + " " +
guitarSpec.getType().ToString() + " guitar : \r\n" + guitarSpec.getBackWood().ToString() + " back and sides, " +
guitarSpec.getTopWood().ToString() + " top.\r\nYou can have it for only $" + guitar.getPrice().ToString() + "!\r\n\r\n";
sRet += ss;
}
}
tbMsg.Text = sRet;
}
}
}
【測試結果】
版次:v1.0
第三站:讓程式易於重用與擴展
現在,我們再來看一下 Inventory 類別的 search() 方法,此時會發現如果 Rick 需要變更搜尋條件或增加項目時,要更動的地方還真不少。
例如 Rick 想要再吉他規格 (GuitarSpec) 中增加一個弦數 (numStrings) 作為搜尋條件時,會需要更動哪些地方呢?
- 在 GuitarSpec 類別中增加一個 int numStrings 的屬性。
- GuitarSpec 類別的建構子也要增加 int numStrings 參數作為輸入。
- 在 GuitarSpec 類別中增加 getNumStrings() 方法以取得吉他弦數。
- Guitar 類別的建構子也要增加 int numStrings 參數作為輸入。
- 在 Inventory 類別中的 addGuitar() 方法中增加 int numStrings 參數作為輸入。
- 在 Inventory 類別中的 search() 方法內容也要修改,增加吉他弦數的比對。
從以上幾點發現了兩個重大問題:
- 原本只是想在 GuitarSpec 類別中增加一個屬性,卻至少要修改五個地方(上述的 1~5 點),而且除了動到本身 GuitarSpec 類別外,還要動到 Guitar 與 Inventory 類別。
- Inventory 類別中的 seach() 方法無法重複利用,實做的方法太過於依賴其他類別。
天啊~僅僅是要為吉他規格 (GuitarSpec) 增加一個項目,卻要如此勞師動眾,真是牽一髮而動全身啊~這樣的程式叫人如何維護,想到就頭皮發麻~ (好熟悉的內心吶喊~T.T)
如何挽救這程式呢?其實方法很簡單,只要為程式做下列調整,以後就不必擔心一樣的問題囉~
- 首先,因為我們是要新增一個 int numStrings 屬性到 GuitarSpec 類別中,所以前面提到的第 1~3 點是免不了的,也是應當要做的。
- 再來,第 4 點要做的應該是 Guitar 類別的建構子參數修改成以 GuitarSpec 物件傳入做初始化。(如此便不會受 GuitarSpec 類別的屬性變更而影響了)
- 第 5 點要做的與第 4 點類似,addGuitar() 方法參數改為以 GuitarSpec 物件傳入。(如此便不會受 GuitarSpec 類別的屬性變更而影響了)
- 第 6 點中的 search() 方法,應該修改成將兩個 GuitarSpec 物件的比較交給 GuitarSpec 類別來處裡,而不是在 Inventory 類別中直接做比較。(如此便不會受 GuitarSpec 類別的屬性變更而影響了)
修改後的類別圖與程式碼如下:
【類別圖】
【程式碼】
GuitarSpec.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public enum Builder { FENDER, MARTIN, GIBSON, COLLINGS, OLSON, RYAN, PRS, UNSPECIFIED };
public enum Type { ACOUSTIC, ELECTRIC, UNSPECIFIED };
public enum Wood { INDIAN_ROSEWOOD, BRAZILIAN_ROSEWOOD, MAHOGANY, MAPLE, COCOBOLO, CEDAR, ADIRONDACK, ALDER, SITKA, UNSPECIFIED };
public class GuitarSpec
{
private Builder builder;
private string model;
private Type type;
private int numStrings; //新增吉他弦數屬性
private Wood backWood, topWood;
public GuitarSpec(Builder builder, string model, Type type, int unmStrings, Wood backWood, Wood topWood)
{
this.builder = builder;
this.model = model;
this.type = type;
this.numStrings = numStrings;
this.backWood = backWood;
this.topWood = topWood;
}
public Builder getBuilder()
{
return builder;
}
public string getModel()
{
return model;
}
public Type getType()
{
return type;
}
public int getNumStrings()
{
return numStrings;
}
public Wood getBackWood()
{
return backWood;
}
public Wood getTopWood()
{
return topWood;
}
public bool matches(GuitarSpec otherSpec)
{
if (builder != otherSpec.builder)
{
return false;
}
if ((model != null) && (!model.Equals("")) && (!model.ToLower().Equals(otherSpec.model.ToLower())))
{
return false;
}
if (type != otherSpec.type)
{
return false;
}
if (numStrings != otherSpec.numStrings)
{
return false;
}
if (backWood != otherSpec.backWood)
{
return false;
}
if (topWood != otherSpec.topWood)
{
return false;
}
return true;
}
}
}
Guitar.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Guitar
{
private string serialNumber;
private double price;
private GuitarSpec spec;
public Guitar(string serialNumber, double price, GuitarSpec spec)
{
this.serialNumber = serialNumber;
this.price = price;
this.spec = spec;
}
public string getSerialNumber()
{
return serialNumber;
}
public double getPrice()
{
return price;
}
public void setPrice(double newPrice)
{
price = newPrice;
}
public GuitarSpec getSpec()
{
return spec;
}
}
}
Inventory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ricksGuitars_start
{
public class Inventory
{
private List<Guitar> guitars;
public Inventory()
{
guitars = new List<Guitar>();
}
public void addGuitar(string serialNumber, double price, GuitarSpec spec)
{
guitars.Add(new Guitar(serialNumber, price, spec));
}
public Guitar getGuitar(string serialNumber)
{
return guitars.First(guitar => guitar.getSerialNumber().Equals(serialNumber));
}
public List<Guitar> search(GuitarSpec searchGuitart)
{
List<Guitar> matchingGuitars = new List<Guitar>();
matchingGuitars.Clear();
for (int i = 0; i < guitars.Count; ++i)
{
Guitar guitar = guitars[i];
if (guitar.getSpec().matches(searchGuitart))
{
matchingGuitars.Add(guitar);
}
}
return matchingGuitars;
}
}
}
Tester.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ricksGuitars_start
{
public partial class fmFindGuitarTester : Form
{
Inventory inventory = new Inventory();
GuitarSpec whatErinLike = new GuitarSpec(Builder.FENDER, "Stratocastor", Type.ELECTRIC, 6, Wood.ALDER, Wood.ALDER);
public fmFindGuitarTester()
{
InitializeComponent();
inventory.addGuitar("V95693", 1499.95, new GuitarSpec(Builder.FENDER, "Stratocastor", Type.ELECTRIC, 6, Wood.ALDER, Wood.ALDER));
inventory.addGuitar("V9512", 1549.95, new GuitarSpec(Builder.FENDER, "Stratocastor", Type.ELECTRIC, 6, Wood.ALDER, Wood.ALDER));
inventory.addGuitar("1092", 12995.95, new GuitarSpec(Builder.OLSON, "SJ", Type.ACOUSTIC, 12, Wood.INDIAN_ROSEWOOD, Wood.CEDAR));
inventory.addGuitar("566-62", 8999.95, new GuitarSpec(Builder.RYAN, "Cathedral", Type.ACOUSTIC, 12, Wood.COCOBOLO, Wood.CEDAR));
}
private void fmFindGuitarTester_Load(object sender, EventArgs e)
{
List<Guitar> matchingGuitars = inventory.search(whatErinLike);
string sRet = "Sorry, we have nothing for you.";
if (matchingGuitars.Count > 0)
{
sRet = "";
foreach (Guitar guitar in matchingGuitars)
{
GuitarSpec guitarSpec = guitar.getSpec();
string ss = "You might like this " + guitarSpec.getBuilder().ToString() + " " + guitarSpec.getModel() + " " +
guitarSpec.getType().ToString() + " guitar : \r\n" + guitarSpec.getBackWood().ToString() + " back and sides, " +
guitarSpec.getTopWood().ToString() + " top.\r\nYou can have it for only $" + guitar.getPrice().ToString() + "!\r\n\r\n";
sRet += ss;
}
}
tbMsg.Text = sRet;
}
}
}
【測試結果】
結論
回顧來時路:
Step 1:確認你的軟體做客戶要它做的事。
- 修正了 Inventory 類別中 search() 方法的功能行問題。
- 增加了 Inventory 類別中 search() 方法的功能性,使其能傳回比較結果清單。
Step 2:應用基本的 OO 原則,增加軟體的彈性。
- 將 GuitarSpec 從 Guitar 類別中獨立出來,讓每一類別只做一件事並且把它做好。(單一職責)
Step 3:努力達成可維護、可重用的設計。
- 將比較功能從 Inventory 類別的 search() 方法中,移至 GuitarSpec 類別的 matches() 方法中,讓物件彼此獨立。(避免牽一髮而動全身)
完成偉大軟體,沒有撇步,貫徹上述三步驟,如此而已。
但是,說來容易做來難,知道不一定做得到,上述三步驟是方法,「貫徹」才是重點,做事如此,作人也是如此。