--- tags: HeadFirstOOAD, AbstractClass --- # Head First OOA&D - Ch5(1) - 感謝[Coder Clark](https://medium.com/@CoderClark)大神指導,參考大神文章讓code寫更好! - [完整實作](https://github.com/bobolee1239/HeadFirstOoadCpp/tree/main/RickShop) ## GOAL 客戶除了賣吉他之外也想賣Mandolin,希望我們可以擴充App ## Key Idea > 1. Abstract classes are placeholders for actual implementation classes. > 2. Abstract class defines "behavior", and the subclasses "implement" that behavior. ## Design 考慮每樣樂器都會有自己的spec且有相同的價錢與serial number欄位,為了方便擴充與增加的code reusibility,決定拉出兩個abstract class: `Instrument`, `InstrumentSpec`存放所有樂器(spec)共同有的特性,新的樂器都需要繼承這樣兩個class並實作對應的compare method,倉庫(`Inventory`)只存放parent class的參考,尋找時用多型來找合適的spec。 **Origin Class Diagram** ```mermaid classDiagram Inventory --> "*" Guitar : stock Guitar o-- "1" GuitarSpec : spec class GuitarSpec { Builder build Type type Wood topWood Wood backWood String model Int numString Builder getBuilder() String getModel() Type getType() Wood getBackWood() Wood getTopWood() Int getNumStrings() bool equal(GuitarSpec) } class Guitar { String serialNumber Double price GuitarSpec getSpec() String getSerialNumber() Double getPrice() setPrice(Double) } class Inventory { addGuitar(String, Double, GuitarSpec) Guitar get(String) Guitar[*] search(GuitarSpec) } ``` **New Class Diagram** ```mermaid classDiagram Inventory --> "*" Instrument : stock Instrument o-- "1" InstrumentSpec : spec GuitarSpec --|> InstrumentSpec MandolinSpec --|> InstrumentSpec Guitar --|> Instrument Mandolin --|> Instrument class InstrumentSpec { <<abstract>> Builder build Type type Wood topWood Wood backWood String model Builder getBuilder() String getModel() Type getType() Wood getBackWood() Wood getTopWood() virtual bool equal(InstrumentSpec) } class Instrument { <<abstract>> String serialNumber Double price InstrumentSpec getSpec() String getSerialNumber() Double getPrice() setPrice(Double) } class GuitarSpec { Int numString Int getNumString bool equal(InstrumentSpec) } class MandolinSpec { Style style Style getStyle() bool equal(InstrumentSpec) } class Inventory { addInstrument(String, Double, InstrumentSpec) Instrument get(String) Guitar[*] search(GuitarSpec) Mandonlin[*] search(MandolinSpec) } ``` ## Cpp Implementation (First Version) 由於 `InstrumentSpec` 是 abstract class 因此 `Instrument` 只能存放其參考,並於derived class存放spec實體,在construct時再將spec參考傳給base class。 ```c++ class Instrument { public: Instrument( const std::string &serialNumber, const double price, InstrumentSpec &spec ) : serialNumber(serialNumber), price(price), spec(spec) {} const InstrumentSpec& getSpec() const {return spec;} private: std::string serialNumber; double price; InstrumentSpec &spec; }; class Guitar : public Instrument { public: Guitar( const std::string &serialNumber, const double price, GuitarSpec &spec ) : Instrument(serialNumber, price, static_cast<InstrumentSpec&>(this->spec)), spec(spec) {} private: GuitarSpec spec; }; ``` Usage ```c++ class Inventory { // using example (add) int addInstrument( const std::string &serialNumber, const double price, InstrumentSpec &&spec ); std::list<const Instrument*> search(const InstrumentSpec& target) { std::list<const Instrument*> matches; for (auto& pItem : guitarStock) { // using example (search) if (pItem->getSpec() == target) { matches.push_back(static_cast<const Instrument*>(pItem)); } } return matches; } }; // using example (add) inventory.addInstrument("11277", 3999.95, GuitarSpec(COLLINGS, "CJ", ACOUSTIC, 6, INDIAN_ROSEWOOD, SITKA )); ``` ## Cpp Implementation (Update Version) ### (1) Reference to Abstract Class 先前parent class存放參考,卻沒有在code裡面明確地限制只能參考derived class的實體,會有幾個明顯缺點: 1. Derived class 有機會不按照預設邏輯,傳入其他spec的參考。 2. 其他人extend `Instrument`時,必須要參考比如`Guitar`實作才會知道應該要傳入derived class 的spec實體參考。 **目標:** 參考Coder Clark大神精準法第三招[畫地自限](https://medium.com/@CoderClark/b0003-%E6%98%93%E8%AE%80%E7%A8%8B%E5%BC%8F%E7%A2%BC%E7%9A%84%E7%89%B9%E6%80%A7-%E7%B2%BE%E6%BA%96-135ec08a8961),限制`Instrument`只能reference到子類別的spec。 #### The First Idea 第一個改進的想法是如果讓`InstrumentSpec`不是abstract class這樣`Instrument`就可以存放`InstrumentSpec`實體,construct時再將實體傳入copy一份,但這樣會衍生其他問題: 1. Derived class 也會存放其對應的spec, e.g. `GuitarSpec`。`GuitarSpec` 又是繼承 `InstrumentSpec` 等於存放了兩個實體。 > **[Note]** > 如果`Spec`體系不是繼承,而是`InstruemntSpec`存放common資料,`GuitarSpec`存放額外的資料,或許可以考慮這樣做。 #### The Second One 第二個想法是因為最終parent class還是只能有參考而實體必須存在derived class內,所以如果跳脫`Instrument`必須要包含`InstrumentSpec`的框架,就能考慮把回傳參考的實作職責下放給derived class,而base class只需要訂好介面並強迫子類別實作(pure virtual method),也呼應到Key Idea的第二點! > **心得:** > 如果不適合在 abstract class 中創建placeholder,不如換個想法讓abstract class定義behavior,把實作權責下放給derived classes。 ```c++ class Instrument { public: Instrument( const std::string &serialNumber, const double price ) : serialNumber(serialNumber), price(price) {} virtual const InstrumentSpec& getSpec() const = 0; private: std::string serialNumber; double price; }; class Guitar : public Instrument { public: Guitar( const std::string &serialNumber, const double price, GuitarSpec &spec ) : Instrument(serialNumber, price), spec(spec) {} const InstrumentSpec& getSpec() const {return static_cast<InstrumentSpec&>spec;} private: GuitarSpec spec; }; ``` // TODO: visiter to improve polymorphism of compare func