[讀書心得]深入淺出物件導向分析與設計-良好程式設計基石 OOAD Code設計範例介紹-1

Dotjum要跟大家分享的一個範例,該範例取用 深入淺出物件導向分析與設計 書中,第一個章節,
如果你也曾經跟Dotjum一樣想要瞭解什麼是「物件」的開發或OOAD等,
而常常在開發之後,才發現自己物件概念非常的不太行,而每一次最後都變成副程式的集合體,
那非常建議你可以來看這一本書,因為他所使用的範例及說明,都非常的口語化,
透過簡單的說明及範例,可以讓你瞭解物件的設計是怎麼產生出來。
(若觀念等相關錯誤有錯誤,請務必告訴Dotjum)

Dotjum要跟大家分享的一個範例,該範例取用 深入淺出物件導向分析與設計 書中,第一個章節,
首先先推薦這本書,如果你也曾經跟Dotjum一樣想要瞭解什麼是「物件」的開發或OOAD等,
而常常在開發之後,才發現自己物件概念非常的不太行,而每一次最後都變成副程式的集合體,
那非常建議你可以來看這一本書,因為他所使用的範例及說明,都非常的口語化,
透過簡單的說明及範例,可以讓你瞭解物件的設計是怎麼產生出來。
OK,話不多說,就讓Dotjum來介紹這個所看的第一章的心得。
(若觀念等相關錯誤有錯誤,請務必告訴Dotjum)

情境是一家「吉他店」老闆 Rick要做一個庫存管理的系統,透過目前 Rick 的描述,可以分析出來
需要兩個物件(該範例先不考慮與資料庫互動)可以分析出需要兩個物件
Guitar : 吉他 (包含吉他所需的屬性)
Inventory: 庫存 (增加吉他、取得吉他、搜尋吉他 方法)
image
而程式碼的實現如下 (原書程式碼下載 )
而這邊書籍中用到的Java 語言開發,而這Dotjum這邊轉為.NET 方式,
而Guitar Class 中各項 get方法,就沒有特別轉為.NET的屬性 (Property),還是用方法的方式取值。
Inventory.cs

public class Inventory
{
 
    private List<Guitar> guitars;

    public Inventory()
    {
        guitars = new List<Guitar>();
    }

    public void addGuitar(String serialNumber, double price,
                          String builder, String model,
                          String type, String backWood, String topWood)
    {
        Guitar guitar = new Guitar(serialNumber, price, builder,
                                   model, type, backWood, topWood);
        guitars.Add(guitar);
    }
    public Guitar getGuitar(String serialNumber)
    {
        foreach(Guitar guitar in guitars)      
        {
           
            if (guitar.getSerialNumber().Equals(serialNumber))
            {
                return guitar;
            }
        }
        return null;
    }
    public Guitar search(Guitar searchGuitar)
    {
        foreach (Guitar guitar in guitars)      
        {
       
            // Ignore serial number since that's unique
            // Ignore price since that's unique
            String builder = searchGuitar.getBuilder();
            if ((builder != null) && (!builder.Equals("")) &&
                (!builder.Equals(guitar.getBuilder())))
                continue;
            String model = searchGuitar.getModel();
            if ((model != null) && (!model.Equals("")) &&
                (!model.Equals(guitar.getModel())))
                continue;
            String type = searchGuitar.getType();
            if ((type != null) && (!searchGuitar.Equals("")) &&
                (!type.Equals(guitar.getType())))
                continue;
            String backWood = searchGuitar.getBackWood();
            if ((backWood != null) && (!backWood.Equals("")) &&
                (!backWood.Equals(guitar.getBackWood())))
                continue;
            String topWood = searchGuitar.getTopWood();
            if ((topWood != null) && (!topWood.Equals("")) &&
                (!topWood.Equals(guitar.getTopWood())))
                continue;
            return guitar;
        }
        return null;
    }
}

Guitar.cs

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(float newPrice)
    {
        this.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;
    }
}

在上面的程式碼中,可以得知Guitar透過建構式時,將所有屬性加入,
而Invetory則提供三個方法 addGuitar,getGuitar,search
完成程式後,啟動我們這邊的頁面,來測試我們的程式
 

{
    protected void Page_Load(object sender, EventArgs e)
    {
        Inventory inventory = new Inventory();
        //給予Inventory數據
        initializeInventory(inventory);
        //輸入要搜尋的Guitar
        Guitar whatErinLikes = new Guitar("", 0, "fender", "Stratocastor", 
                                      "electric", "Alder", "Alder");
        //傳入search
            Guitar guitar = inventory.search(whatErinLikes);
            if (guitar != null) {
              Response.Write("Erin, 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() + "!");
            } else {
                Response.Write("Sorry, Erin, we have nothing for you.");
            }
       

    }

    private static void initializeInventory(Inventory inventory)
    {
        inventory.addGuitar("11277", 3999.95, "Collings", "CJ", "acoustic",
                            "Indian Rosewood", "Sitka");
        inventory.addGuitar("V95693", 1499.95, "Fender", "Stratocastor", "electric",
                            "Alder", "Alder");
        inventory.addGuitar("V9512", 1549.95, "Fender", "Stratocastor", "electric",
                            "Alder", "Alder");
        inventory.addGuitar("122784", 5495.95, "Martin", "D-18", "acoustic",
                            "Mahogany", "Adirondack");
        inventory.addGuitar("76531", 6295.95, "Martin", "OM-28", "acoustic",
                            "Brazilian Rosewood", "Adriondack");
        inventory.addGuitar("70108276", 2295.95, "Gibson", "Les Paul", "electric",
                            "Mahogany", "Maple");
        inventory.addGuitar("82765501", 1890.95, "Gibson", "SG '61 Reissue",
                            "electric", "Mahogany", "Mahogany");
        inventory.addGuitar("77023", 6275.95, "Martin", "D-28", "acoustic",
                            "Brazilian Rosewood", "Adirondack");
        inventory.addGuitar("1092", 12995.95, "Olson", "SJ", "acoustic",
                            "Indian Rosewood", "Cedar");
        inventory.addGuitar("566-62", 8999.95, "Ryan", "Cathedral", "acoustic",
                            "Cocobolo", "Cedar");
        inventory.addGuitar("6 29584", 2100.95, "PRS", "Dave Navarro Signature",
                            "electric", "Mahogany", "Maple");
    }
}

image
經過測試,卻沒有找到所輸入的Guitar,而程式所需改的地方在哪裡呢?

而這個章節特別提到,好的軟體因該要
1.作客戶要做的事情。
2.設計良好(Well-Degigned)、Well-Code、並好維護、重利用及擴展。
所以就可以知道好的軟體的三個步驟
1.確認客戶要做的事情。
2.應用基本的OO原則,增加軟體彈性。
3.努力達成可維護、重利用的設計。


而我們回頭來看剛剛上面的程式,我們可以發現雖然是可以運作,但有幾個缺點,
1.String 的比較忽略了大小寫問題。
2.部分固定屬性是否能不使用String的方式,而採用列舉enum方式。
3.Search因該是傳回列表的清單。
而最重要的是一點是,別為了解決舊問題,產生了新問題。
所以我們針對以上三個問題來做修正
第 1 , 3 在Inventory.cs做了修改 ,Guitar.cs這邊則針對建構式做修改
Inventory.cs

{
  
    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)
    {
        Guitar guitar = new Guitar(serialNumber, price, builder,
                                   model, type, backWood, topWood);
        guitars.Add(guitar);
    }
    public Guitar getGuitar(String serialNumber)
    {
        foreach(Guitar guitar in guitars)       
        {
            
            if (guitar.getSerialNumber().Equals(serialNumber))
            {
                return guitar;
            }
        }
        return null;
    }
    public List<Guitar> search(Guitar searchGuitar)
    {
        List<Guitar> matchingGuitars = new List<Guitar>();

        foreach (Guitar guitar in guitars)       
        {
        
            // Ignore serial number since that's unique
            // Ignore price since that's unique
            String builder = searchGuitar.getBuilder();
            if ((builder != null) && (!builder.Equals("")) &&
                (!builder.Equals(guitar.getBuilder())))
                continue;
            //將無法enum的還是透過字串比對但都轉為小寫
            String model = searchGuitar.getModel().ToLower();
            if ((model != null) && (!model.Equals("")) &&
                (!model.Equals(guitar.getModel().ToLower())))
                continue;
            String type = searchGuitar.getType();
            if ((type != null) && (!searchGuitar.Equals("")) &&
                (!type.Equals(guitar.getType())))
                continue;
            String backWood = searchGuitar.getBackWood();
            if ((backWood != null) && (!backWood.Equals("")) &&
                (!backWood.Equals(guitar.getBackWood())))
                continue;
            String topWood = searchGuitar.getTopWood();
            if ((topWood != null) && (!topWood.Equals("")) &&
                (!topWood.Equals(guitar.getTopWood())))
                continue;
            matchingGuitars.Add(guitar);
        }
        return matchingGuitars;
    }
}
Guitar.cs
public class Guitar{

    private String serialNumber, builder, model, type, 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.ToString();
        this.model = model;
        this.type = type.ToString();
        this.backWood = backWood.ToString();
        this.topWood = topWood.ToString();
    }

    public String getSerialNumber()
    {
        return serialNumber;
    }

    public double getPrice()
    {
        return price;
    }
    public void setPrice(float newPrice)
    {
        this.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;
    }
}

而第2項這邊Dotjum做各說明一下,使用enum的方式,dotjum之前在別的系統也有用過,
但本書的範例用的enum用的非常特別的好,但在.NET上似乎有些問題,在後面在特別說明這塊(備1)
而使用enum來避免輸入程式時造成手誤或大小寫不同問題,則增加了
Wood.cs , Type.cs,Builder.cs
public enum Wood{
    INDIAN_ROSEWOOD, BRAZILIAN_ROSEWOOD, MAHOGANY, MAPLE,
    COCOBOLO, CEDAR, ADIRONDACK, ALDER, SITKA
}

public enum Type{   ACOUSTIC, ELECTRIC}

public enum Builder{
    FENDER, MARTIN, GIBSON, COLLINGS, OLSON, RYAN, PRS, ANY
}

 
所以在建構Guitar物件時,Wood,Builder,Type 不是輸入字串的方式,而是選擇enum的方式,
 Guitar whatErinLikes = new Guitar("", 0, Builder.FENDER, "Stratocastor",
                                      Type.ELECTRIC, Wood.ALDER, Wood.ALDER);

 
就這樣可以先將已經分類好的參數避免字串來傳的時候有手誤的時候,當然也可以容易在寫程式,
知道目前該類別的enum有哪一些。

完整 default.aspx.cs

{
    protected void Page_Load(object sender, EventArgs e)
    {
        Inventory inventory = new Inventory();
        //給予Inventory數據
        initializeInventory(inventory);
        //輸入要搜尋的Guitar
      //  Guitar whatErinLikes = new Guitar("", 0, "fender", "Stratocastor", 
        //                              "electric", "Alder", "Alder");
        Guitar whatErinLikes = new Guitar("", 0, Builder.FENDER, "Stratocastor",
                                      Type.ELECTRIC, Wood.ALDER, Wood.ALDER);
        
        //傳入search
            List<Guitar> matchingGuitars = inventory.search(whatErinLikes);

          if (matchingGuitars.Count > 0)
          {
              Response.Write("Erin, you might like these guitars:</BR> ");
                foreach (Guitar guitar in matchingGuitars)
                {                 
                    Response.Write("  We have a " +
                    guitar.getBuilder() + " " + guitar.getModel() + " " +
                    guitar.getType() + " guitar:</BR>     " +
                    guitar.getBackWood() + " back and sides,</BR>      " +
                    guitar.getTopWood() + " top.\n  You can have it for only $" +
                    guitar.getPrice() + "!</BR>   ----");
                }
            }
          else 
          {
              Response.Write("Sorry, Erin, we have nothing for you.");
         
 }


    }

    private static void initializeInventory(Inventory inventory)
    {
        inventory.addGuitar("11277", 3999.95, Builder.COLLINGS,
                         "CJ", Type.ACOUSTIC,
                         Wood.INDIAN_ROSEWOOD, Wood.SITKA);
        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);
        inventory.addGuitar("122784", 5495.95, Builder.MARTIN,
                            "D-18", Type.ACOUSTIC,
                            Wood.MAHOGANY, Wood.ADIRONDACK);
        inventory.addGuitar("76531", 6295.95, Builder.MARTIN,
                            "OM-28", Type.ACOUSTIC,
                            Wood.BRAZILIAN_ROSEWOOD, Wood.ADIRONDACK);
        inventory.addGuitar("70108276", 2295.95, Builder.GIBSON,
                            "Les Paul", Type.ELECTRIC,
                            Wood.MAHOGANY, Wood.MAHOGANY);
        inventory.addGuitar("82765501", 1890.95, Builder.GIBSON,
                            "SG '61 Reissue", Type.ELECTRIC,
                            Wood.MAHOGANY, Wood.MAHOGANY);
        inventory.addGuitar("77023", 6275.95, Builder.MARTIN,
                            "D-28", Type.ACOUSTIC,
                            Wood.BRAZILIAN_ROSEWOOD, Wood.ADIRONDACK);
        inventory.addGuitar("1092", 12995.95, Builder.OLSON,
                            "SJ", Type.ACOUSTIC,
                            Wood.INDIAN_ROSEWOOD, Wood.CEDAR);
        inventory.addGuitar("566-62", 8999.95, Builder.RYAN,
                            "Cathedral", Type.ACOUSTIC,
                            Wood.COCOBOLO, Wood.CEDAR);
        inventory.addGuitar("6 29584", 2100.95, Builder.PRS,
                            "Dave Navarro Signature", Type.ELECTRIC,
                            Wood.MAHOGANY, Wood.MAPLE);
    }
}

 
OK,做到這邊,我們再測試一下剛剛的測試,就會透過查詢已經可以查出我們要的資料。
image
而整個Class圖的架構,而主要的改變就是 Guitar 的建構式 Builder,Type,Wood 分類已經不是字串

是透過enum的方式選取,而model比較類似描述性質,所以還是保留字串的形式。


image
image
做到這一步,至少我們已經達成了剛剛所說的好的軟體的三大步驟1.確認客戶要做的事情。
接下來再進行2.應用基本的OO原則,增加軟體彈性。
而第二個步驟也是比較讓Dotjum比較有深刻的瞭解物件的設計。



 
備註1:Java enum 與 .NET 不同,我這邊用另一個文章來記錄 連結
備註2:enum 在 Dotjum 的使用經驗,常用在以知的分類的時候,程式這端來做選擇,

當然分類一定不是很多的狀況下,像是本站的系統 Subtext 在許多功能上,也會透過enum來給

程式做選取,詳細的介紹之後Dotjum有時間在補一些上來。
若要將字串轉完enum則可以參考[C#]將string轉為enum Convert String To Enum