---
# System prepended metadata

title: 【程式設計】物件導向筆記
tags: [OOP]

---

# 【程式設計】物件導向筆記

## Introduction
物件導向是程式設計中的一種概念，將程式中的元素視為一種物件，這些物件可以被賦予屬性、方法等等。此物件可以是抽象或現實中的實體概念。例如，車子具備汽油、座位等屬性，而其方法包括向前、向後等，基本上作為程式設計師在撰寫程式碼的時候，我們必須不斷思考所設計物件是否滿足某些條件，如果滿足後，就有可能重新設計類別，例如重新設計基類與衍生類別

## Definition

### Object

對資料或行為的一種封裝，封裝成一個基本的模塊。物件可以是實體或抽象概念。

### Class

描述物件的抽象模板，基本上會定義一個物件有甚麼樣的屬性或方法，其實就是設計圖啦。

### Instance

類別內實體化時的名稱，或者說宣告成立時，對於相同的 class 可以實例化不同的類別屬性。每一個實例的狀態都可以不一樣或相同，彼此獨立。

## 3 Concepts in OOP

### Inheritance 繼承

繼承能夠擴展一個類別的屬性的同時保有原始類別屬性的一個方法。例如，球有很多種，如籃球、棒球、橄欖球，但它們背後都有一個抽象概念，即形狀或大小。因此，可以定義一個類別為形狀，而這些球就是其擴展後的結果。我們稱作子類，而形狀我們就稱作父類。
* 當 `class B` 為 `class A` 的衍生類別，我可以稱作 `B is-a A`
* 當 `class B` 為 `class A` 的屬性，稱作 `A has-a B`

### Encapsulation 封裝

將類別或物件屬性隱藏起來，並且設計可以控制的介面，確保內部狀態的改變必須透過介面才能更改，確保了內部狀態一致性，將方法或屬性設置存取權限，在 C++ 中可以使用 `public` / `private` / `protected` 來控制權限。


`C++` 中藉由 `Access modifiers` ( `public` / `private` / `protected`) 來控制 `struct` / `class`/ `union` 的權限
Access specifiers give the author of the class the ability to decide which class members are accessible to the users of the class (that is, the _interface_) and which members are for internal use of the class (the _implementation_).

![image](https://hackmd.io/_uploads/rk4rAlt6A.png)
* `public` : 任何地方都可以存取類別的公有成員，包含使用者、衍生類別、以及類別自身
* `private` : 私有成員只能在類別自身內部存取，無法被外部使用者或衍生類別存取
* `protected` : 保護只能在類別內部及其衍生類別中存取，使用者無法存取保護成員


### Polymorphism 多型

對於一個方法，是否有不同的結果。在射擊遊戲中，左鍵為射擊，右鍵為開鏡。同樣是滑鼠點擊的事件，但效果卻不一樣，這就是多型

* **Function Overriding (覆寫)** 
    允許子類重新覆蓋父類方法。
* **Function Overloading (重載)** 
    允許函數可以有多個不同輸入。
* **Virtual Function(虛擬函數)** 
    函數可以在執行時期被覆寫、與動態調度使用

## SOLID Principles
* 五種字母組成
    * **[S](#)RP** 單一職責原則
    * **[O](#)CP** 開放-封閉原則
    * **[L](#)SP** 里氏替換原則
    * **[I](#)SP** 介面隔離原則
    * **[D](#)IP** 依賴反向原則
* 軟體設計需要符合兩件事
    * 讓電腦的行為符合需求
    * 讓電腦的行為可以被工程師設計與改變
    * 也就是城市要寫得夠簡單，操作與介面分離

### SRP 單一職責原則 (Single Responsibility Principle)

一個**模組**應該只對唯一的一個**角色負責**，基本上就是專責，舉例來說，在公司中，會計就是做帳務的任務，不會帳務弄一弄就跑去修電腦、修網路
* ❌ 壞例子 : 一個 `Report` 類別同時負責產生報表和把報表寄出
* ✅ 好例子 : 建立 `ReportGenerator` 負責產生報表，`ReportSender` 負責寄送報表

### OCP 開放-封閉原則 (Open-Closed Principle)

軟體實體 ( 類別、模組、函數等 )，對於擴展必須是開放，但對於修改是封閉的，當你需要根據不同需求修改方法時，就可能需要重構方法然使其符合 OCP 原則，軟體實體之間是具備低偶合性質的，相依性是低的，假設我們出國，當地插頭與本國插頭不一樣，我們一定是買一個轉接頭來接上插頭，而不是直接把插座拆掉換成本國插頭吧，這就是 OCP 最核心概念
* ❌ 壞例子 : 如果要計算形狀的面積，每次新增形狀 ( 圓形、三角形、矩形 ) 都要回去修改 `AreaCalculator`
* ✅ 好例子 : 建立 `Shape` 介面，每個形狀自己實作 `getArea()`，這樣 `AreaCalculator `不用改，只要新增形狀類別即可。



### 里氏替換原則（Liskov Substitution Principle，LSP）

子類別應該可以替換其父類別並且不會破壞系統的正確性，也就是不能違反父類別當初的設計原因，如果你有一個「交通工具」的程式，`Car`、`Bus`、`Bike` 都應該能安全替換「交通工具」使用。如果新增一個 Horse 結果沒油門卻還要 `startEngine()`，就違反了 LSP !

### 介面隔離原則（Interface Segregation Principle，ISP）

客戶端不應該被迫依賴於它不使用的接口，假設你去健身房辦會員卡，結果規定你必須同時繳「游泳池費用」，但你根本不游泳。這就是「被迫依賴不需要的功能」

* ❌ 壞例子 : `Printer` 介面同時有 `print()`、`scan()`、`fax()`，如果一台印表機只會印刷，卻被迫實作 `scan()`、`fax()`，就違反了 ISP。
* ✅ 好例子 : 拆成 Printable、Scannable、Faxable 三個小介面，依需求實作。

### 依賴反轉原則（Dependency Inversion Principle，DIP）

高層模組不應該依賴於低層模組，高層與低層都應該依賴於抽象，藉此降低類別之間的耦合性，舉例來說就像你用「插座」而不是直接接電線，因為插座（抽象）能支援各種電器。如果你直接把電線接到冰箱，換一台洗衣機就得重接線

## Abstraction 抽象

Abstraction，將一個程式或資料的操作方式隱藏起來，不讓使用者知道其實作細節，只要知道怎麼操作就好，在如同 `C++` 中 STL library一樣，我們不需要知道其怎麼實作的，只要能夠學會操作他就好

## Design Pattern 設計模式

### Strategy Pattern 策略設計

> In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives runtime instructions as to which in a family of algorithms to use
> 維基百科上說明已經蠻清楚的，也就是可以在執行程式碼時，可以自由地選擇需要的演算法，而不是透過指令或更改程式碼來達成

![image](https://hackmd.io/_uploads/Sycmw-Ly1l.png)


### State Pattern 狀態設計模式

![image](https://hackmd.io/_uploads/Bkbxl6iqge.png)


### Singleton Pattern 單例模式

將某個類別限制在整個程式中實體化時只有一個實例
1. Ensure they only have one instance
2. Provide easy access to  that instance
3. Control their instantiation

## 靜態工廠函數 ( Static Factory Method Pattern )

在 base class 中建立靜態創建函數，用於創建不同 derived class ，這種方式可以讓創建邏輯集中在 base class 中，能夠簡化創建類別時的複雜度

```cpp
Source *Source::Create(const std::string &name) {
    if (...) {
        return new ImageSource(absPathStr);
    } else if (...) {
        return new VideoSource(absPathStr);
    } else {
        throw std::invalid_argument("Unsupported source type");
    }
}
```

## 依賴注入 (Dependency Injection, DI)

依賴注入就是不要讓物件自己去建立它需要的東西，而是「從外部」把需要的東西給它，以車子來說，車子都會有引擎吧，我們在要開車的時候才會請師傅裝引擎嗎 ? 肯定不會吧，一定是在拿到車子的時候就裝好了，這個過程可以理解為在建構車物件的時候將引擎就設定好，也就是讓我們在建構車子時，就先決定好引擎是什麼，有點像替換某個類別中的屬性等等，可以做到自由替換
```cpp
class Engine {
public:
    virtual void start() = 0; // 讓引擎可替換
    virtual ~Engine() {}
};

class GasEngine : public Engine {
public:
    void start() override {
        std::cout << "Gas engine starts\n";
    }
};

class ElectricEngine : public Engine {
public:
    void start() override {
        std::cout << "Electric engine starts silently\n";
    }
};

class Car {
    Engine* engine; // 依賴抽象，不自己 new
public:
    // Constructor Injection：從外部傳入依賴
    Car(Engine* eng) : engine(eng) {}

    void drive() {
        engine->start();
        std::cout << "Car is driving\n";
    }
};
```

## [Mixin](https://zh.wikipedia.org/zh-tw/Mixin)

Mixin 是一種類別，提供了方法的實現，可以很好讓其他類別繼承後來使用該實現方法，與其他類別關係為 include 或者 -able 關係，通常做為功能模組使用，就是額外開一個類別繼承，具備擴展功能性質

## Dispatch

在 pytorch 與 torchvision 中，在 nn 模組、或者 transform 模組，都會看到他們將核心功能與邏輯功能分開，並透過 Dispatch 讓函數可以針對不同 input 來自動選擇適合的方法

Example
[torchvision/transforms/functional.py](https://github.com/pytorch/vision/blob/main/torchvision/transforms/functional.py)
```python!
from . import _functional_pil as F_pil, _functional_tensor as F_t
def solarize(img: Tensor, threshold: float) -> Tensor:
    """
    Solarize an RGB/grayscale image by inverting all pixel values above a threshold.
    """
    if not torch.jit.is_scripting() and not torch.jit.is_tracing():
        _log_api_usage_once(solarize)
    if not isinstance(img, torch.Tensor):
        return F_pil.solarize(img, threshold)

    return F_t.solarize(img, threshold)
```