---
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