# 物件導向
**function 將概念封裝,將變數區域化**
OOP五大原則看設計模式那篇
https://www.kancloud.cn/webxyl/php_oop/68894
推薦看
https://ms0680146.medium.com/%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E9%96%8B%E7%99%BC%E5%8E%9F%E5%89%87-819ef07aaa54
## 簡介
https://wadehuanglearning.blogspot.com/2017/07/php-oo.html
**物件導向是一種寫程式的方式,它傾向讓開發者把類似或有關聯的工作或屬性,組織到類別(classes)裡面。這可以讓程式保持遵守 不重複原則“don’t repeat yourself” (DRY) ,且更容易維護。**
不重複的程式(DRY)是物件導向最主要的優點之一,
**物件導向允許物件透過 $this 關鍵字來參考自己。物件使用 $this 就如同你直接使用物件名稱來指定物件,如: myClass->prop1**
https://wadehuanglearning.blogspot.com/2017/07/php-oo.html
能複寫就複寫function
不要重購
## 類別(class)、屬性(property)、方法(method)、實體(instance)之間的差別
https://ithelp.ithome.com.tw/articles/10220019

**property**是這個物件應該有的一些變數,property前面的public、private是『 修飾子 』,表示該property的使用權限
**method**則是一些function,是該物件擁有的功能或動作
**class**中定義好了這個物件該有的property、method之後,我們還要將這個class實體化,生成實體(instance)(new一個物件)。
## 物件方法的符號: ->和::
->用來調用一個實體中的method或property
:: 用來調用『未實體化』的物件的內部靜態成員(static function)
動態和靜態的意義為,我們可以說前者是具有狀態的;而後者則無
## 修飾子 static、private、public、protected間的差別
**class裡的成員前面幾乎都會有所謂的修飾子(modifier),若沒有加上修飾子,則預設為public。**
**static**:靜態變數,不須先建立物件就能直接使用
**public**:公有變數,建立物件後才可使用,內部(own class)、實例(instance)可用
**protected**:建立物件後才可使用,內部和繼承類別可用
**private**:私有變數,建立物件後才可使用,且只能在內部中使用
使用範圍由大至小:static > public > protected > private
protected最好用 new不能調用他
只能在類別以及子類別的內部存取
但又沒private嚴格
都用它來宣告變數
## 建構子與解構子
**constructor是class中的一種特殊的method,只有在實體化一個class後(new完後)能被執行,是用來初始化一些class特性的。**
解構子則是將已經用完不需要的物件release掉,會在unset($objectName)後被執行。
```
<?php
class Student
{
static $school = "NCKU";
public $name;
public $score;
private $id;
public function __construct($name, $score, $ID) //建構子
{
$this->name = $name;
$this->score = $score;
$this->id = $ID;
}
public function showScore()
{
echo $this->name . " got " . $this->score . "\n";
}
private function showID()
{
echo $this->name . "'s studentID is ' " . $this->id. "\n";
}
}
echo Student::$school."\n"; // static變數不需要先建立物件就能直接使用
$name = 'Lisa';
$score = 80;
$ID = 123456;
$studentA = new Student($name, $score, $ID);
//實體化一個Student物件,帶的參數會先到constructor做初始化
$studentA->showScore();
$studentA->showID();
//Fatal error:因為showID這個method是private的,無法在class Student範圍以外的地方使用
unset($studentA);//使用完畢,解構$studentA
```
## 封裝與繼承
於繼承,前輩們通常只有一種叮囑:謹慎使用,或是乾脆不要用。
看文章比較好懂
https://ithelp.ithome.com.tw/articles/10221540
https://igouist.github.io/post/2020/07/oo-3-encapsulation/
此外我也看到過販賣機的例子,當你去販賣機買飲料,你也不需要知道裡面的構造,只要知道你選了飲料投了錢,飲料就會跑出來就行。
因此封裝的概念很直覺:將物件視作一個整體,實作內容隱藏起來,讓使用者只需要知道怎麼使用即可。(相似的思路,我們後續的介面會再提到)大概就像懶人包的感覺。
兩個物件之間「知道」得越多,其耦合就越高。替換和修改時互相牽連的機會和規模也越大,因此封裝可以說是物件導向的基石也不為過。
## 多型
https://igouist.github.io/post/2020/07/oo-5-polymorphism/
多型算是比較三特性之中給人感覺比較溫和的了,不如說只要有了繼承,那麼多型的到來就是必然的。多型的定義是:不同的物件能夠做出一樣的行為,但必須由他們自己的程式碼來實作。
白話一點說就是:**一樣的事,不同做法**。
## 抽象、覆寫
當我們在用卡牌的例子時,雖然怪獸卡跟魔法卡都繼承了 Card 這個類別,但是我們仍然能
new Card() 來建立一張新卡牌,那…怪怪的吧,這張卡牌到底是什麼呀,空白的卡片嗎?
又或是動物的例子,我們的狗跟貓都繼承了哺乳類,那我們能實例化一個哺乳類嗎?我們的狗跟鳥都是動物,那我們能實例化一個動物嗎?
小明跟小華都繼承了工程師,那我們能 new 一個工程師嗎…?
有些類別就是這樣,它們負責定義共通的那些特性,然而它們本身不應該被實體化成一個物件,這種類別我們就應該把它們標記為抽象類別。
## 內聚、耦合
https://igouist.github.io/post/2020/09/oo-8-cohesion-and-coupling/
良好的內聚應該只關注在一件事情上,並適時地將不屬於自身職責的工作交給別人(單一原則)
繼承 就是強耦合的代表,而 介面 就是降低耦合的手段之一。
## 魔術函數 Magic Methods in OOP
PHP 提供了幾個 魔術函數 讓操 作物件(object) 變得更簡單,這些魔術函數會在物件發生特定行為時被呼叫
官方魔術方法
https://www.php.net/manual/en/language.oop5.magic.php
## 何謂介面(interface)
http://benyi.logdown.com/posts/2018/02/11/oop-what-is-interface
電腦主機上可能會有很多的插孔,像是滑鼠插孔、光碟機插孔、鍵盤插孔、隨身碟插孔... 等等。而如果今天出現了一個新產品,但是電腦上沒有對應到他的插孔,想要使用這個新產品,你有兩個選擇:
硬生生的把電腦拆開,在已經設計好的主機板想辦法找兩個接點出來,然後在機殼上鑿個洞,把新的插孔裝上去。但是下次又有新產品出現的時候,你就要再鑿一次洞。
買一個新的,有此插孔的電腦,但是下次要用新產品的時候,你就得再買一次新電腦。
上面兩個方法,不管哪一個都是天馬行空,就算真的選了一個方法,又能重複幾次呢?(機殼又能禁得起再挖幾次洞呢?)
**這時候,我們就需要一個統一的標準、統一的產品銜接方法,
這個就叫做「介面」(interface)。**
## 介面與實作(implementation)
介面可以讓我們在設計程式的時候,將**該有的實作方法定義好**,也就是只描述一個**抽象**的方法名稱。換句話說,每個產品都有每個產品各自的特性和他們的特色,但是只要實作這些方法的裝置,都可以叫作 USB 裝置。所以,支援 USB 的電腦,無論換了什麼東西,只要插得進 USB 插座的,都可以使用。(電腦依賴在 USB 的介面(interface)上,而不是依賴在 USB 滑鼠、USB 鍵盤… 等裝置上。)
當定義好 介面(interface)之後,接下來我們要做的,就是在每個我們生產的裝置上實作這些方法。反過來說,只要實作這些方法的裝置,都可以稱作 USB 介面
## 抽象化(abstraction)與物件導向
注意
**(這跟抽象類別 (Abstract Class)不一樣喔 ,抽像類別只能繼承)**
由於電腦認得 USB 介面,未來只要有新的裝置插上電腦的時候,可能會先呼叫isConnected() 方法檢查是否有接上成功,直接呼叫每個裝置的 getPower() 方法取得電源,接著呼叫 boot() 方法讓它們開機等等,等到使用完畢之後,最後呼叫 shutdown() 方法讓這個裝置關機。
發現了嗎?在我所描述的上面這個過程中,有沒有提到「是什麼裝置」?有沒有提到這個裝置的 getPower()(取得電源)方法應該怎麼做? 沒有。這個就是我們利用介面把程式進行抽象化(abstraction)。
抽象化指的是「只描述一個大概的流程跟邏輯,真正實現的方法交由底下各個裝置去做」。
正確的作法,應該是讓電腦依賴在 USB 這個 介面(interface)上,而不是單一的裝置,接著,在控制邏輯中呼叫他們各別的方法即可。
**步驟1:先定義一個 USB 的介面 USBInterface**
```
interface USBInterface
{
/** 裝置開機 */
public function boot();
/** 裝置是否有連線 */
public function isConnected();
/** 裝置連線不成功的錯誤訊息 */
public function getErrorMessage();
/** 裝置取得電源 */
public function getPower();
/** 裝置取得需要使用到的資料 */
public function getData();
/** 裝置把資料儲存起來 */
public function saveData();
/** 裝置關機 */
public function shutdown();
}
```
注意到了嗎? 雖然定義了一個 USB 裝置該有哪些方法,但並沒有寫任何的程式碼,也就是不包含實作(這樣才可以分離抽象的邏輯,而不是綁死在某一個裝置上)
**步驟2:實作各個裝置來自介面的方法**
```
class Mouse implements USBInterface
{
/** 實作 USB 滑鼠的開啟方法 */
public function boot()
{
if ( $this->isBoot() ) {
return "滑鼠開啟成功";
} else {
$this->bootRetry();
}
}
/** 實作其他方法 (略) */
...
}
class Keyboard implements USBInterface
{
/** 實作 USB 鍵盤的開機方法 */
public function boot()
{
if ( $this->bluetoothConnect() )
{
return "已連線到藍芽鍵盤";
} else {
if ( $this->bootKeyboard() )
{
return "鍵盤開啟成功"
}
}
}
/** 實作其他方法 (略) */
...
}
```
在這個步驟,只要把每個我們想新增的裝置,都宣告實作一個 USBInterface 介面,然後開始寫我們每個裝置裡面該有的實現方法。
你可以看到,雖然都是 boot() 這個方法,但是 Mouse 與 Keyboard 的實作方法不同,這個就是定義介面帶給我們的好處,如同我們在 USBInterface 這個介面中描述「身為一個 USB 裝置應該要有什麼方法」,往後這兩個裝置想要成為 USB 裝置的時候,就必須實作這些方法。
**步驟3:撰寫主程式邏輯**
```
class Computer
{
public function __construct(USBInterface $device)
{
$this->device = $device;
}
/**
* 連接到裝置
**/
public function connectDevice()
{
/** 裝置開機 */
$this->device->boot();
/** 如果未連線成功的話,傳回各裝置的錯誤訊息 */
if ( !$this->device->isConnected() ) {
return $this->device->getErrorMessage();
}
/** 取得電力與資料 */
$this->device->getPower();
$this->device->getData();
/** 儲存資料成功,關機 */
$result = $this->device->saveData();
if ( true === $result ) {
$this->device->shutdown();
}
}
}
```
我們在 Computer 這個類別依賴了 USBInterface 這個介面。當未來我們要連接到新的裝置時,建立 Computer 實體所需要的 $device 參數,必須是實作 USBInterface 這個介面的類別。
**例如,電腦今天要連接滑鼠上來,我們會這樣呼叫**
```
/** 建立一個 USB 滑鼠的實體 */
$device = new Mouse;
/** 建立一個電腦的實體 */
$computer = new Computer($device);
/** 連接到裝置 */
$computer->connectDevice();
```
而我們如果要改成連接其他的裝置,也只要修改 $device 這個實體,無論是什麼,符合 USBInterface 介面的,都可以傳遞進來使用不用怕出錯,因為只要是實作 USBInterface 這個介面的類別,都一定會有 boot()、getPower() … 等等這些方法。主程式的邏輯中,也只要負責呼叫,管好自己的流程就行,不需要管到哪個裝置、怎麼實作的。
### 結論
定義 介面 interface 可以讓我們把程式邏輯跟實作分離(抽象化),主程式不用管類別怎麼實作,只要顧好自己的流程,呼叫他們定義在介面中的方法就行。
未來在修改程式方面,也只需要實作該介面,變成一個新的類別,就可以無痛擴充現有的程式,而不用回去改到主程式流程。
主程式應該要依賴 介面,而不是依賴某一個類別。如果依賴某一個類別(例如電腦只依賴滑鼠),會造成耦合度太高,抽換不易,擴充困難。反而是依賴在一個抽象的介面上,只需要實作它的方法,就可以拿進來程式使用。
## 抽象類別(Abstract Class)與介面(Interface)比較
父类是将子类所共同拥有的属性和方法进行抽取,这些属性和方法中,有的是已经明确实现了的,有的还无法确定,那么我们就可以将其定义成抽象,在后日子类进行重用,进行具体化。
https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/109476/
**抽象類別(Abstract Class)使用時機**
當「多個類別(Class)」之間有**共同**的方法(function)或屬性(attribute)時,可以將這些共用的地方寫成「抽象類別(Abstract Class)」,讓其他的「子類別(Class)」去繼承
**介面(Interface)使用時機**
當「多個類別(Class)」之間有**共同**的方法(function),但方法實做的方式有差異,可以將這些共用「方法」寫成「介面(Interface)」,讓其他的「子類別(Class)」去實做這個介面
### 共同
(1)兩者都是抽象類,都不能例項化。
(2)interface 實現類及 abstract class 的子類都必須要實現已經宣告的抽象方法。
### 差異
(1)、interface 需要實現,要用 implements ,而 abstract class 需要繼承,要用 extends 。
(2)、一個類可以實現多個 interface ,但一個類只能繼承一個 abstract class 。
(3)、interface 強調特定功能的實現,而 abstract class 強調所屬關係。
(4)、儘管 interface 實現類及 abstract class 的子類都必須要實現相應的抽象方法,但實現的形式不同。
interface 中的每一個方法都是抽象方法,都只是宣告的 (declaration, 沒有方法體 ) ,實現類必須要實現。而 abstract class 的子類可以有選擇地實現。
這個選擇有兩點含義:
a) abstract class 中並非所有的方法都是抽象的,只有那些冠有 abstract 的方法才是抽象的,子類必須實現。那些沒有 abstract 的方法,在 abstract class 中必須定義方法體;
b) abstract class 的子類在繼承它時,對非抽象方法既可以直接繼承,也可以覆蓋;而對抽象方法,可以選擇實現,也可以留給其子類來實現,但此類必須也宣告為抽象類。既是抽象類,當然也不能例項化。
(5)、abstract class 是 interface 與 class 的中介。 abstract class 在 interface 及 class 中起到了承上啟下的作用。一方面, abstract class 是抽象的,可以宣告抽象方法,以規範子類必須實現的功能;另一方面,它又可以定義預設的方法體,供子類直接使用或覆蓋。另外,它還可以定義自己的例項變數,以供子類通過繼承來使用。
(6)、介面中的抽象方法前不用也不能加 abstract 關鍵字,預設隱式就是抽象方法,也不能加 final 關鍵字來防止抽象方法的繼承。而抽象類中抽象方法前則必須加上 abstract 表示顯示宣告為抽象方法。
(7)、介面中的抽象方法預設是 public 的,也只能是 public 的,不能用 private , protected 修飾符修飾。而抽象類中的抽象方法則可以用 public , protected 來修飾,但不能用 private 。
### 兩個重點整理區
https://blog.xuite.net/coke750101/networkprogramming/58008523
**Abstract Class** 中, method 的定義方式有兩種,以下是有實作的 method :
`public function method1() { // ... code ... }`
值得注意的是,就算大括號中沒有程式碼,也算是有實作的 method ,我稱之為空實作。
另一種是抽象方法,只要在 function 前加上 abstract 關鍵字,**再拿掉大括號**並加上分號:
`abstract public function method2();`
這樣的抽象方法,就會要求 sub class 在繼承之後要實作該方法的細節。
**Interface** 中所有方法都是 abstract 的
**抽象方法不會在抽象類別中被實現,只能於繼承抽象類別的子類別上進行實作。**
## 多態
https://www.pilishen.com/posts/Understanding-and-Applying-Polymorphism-in-PHP
Polymorphism describes a pattern in object oriented programming in which classes have different functionality while sharing a common interface. 多态,作为面向对象编程中的一种设计模式,指的是通过遵循同一个interface,类可以有不同的功能实现(相当于说,有多种形态)。
多态的一大魅力就是,我们不需要知道背后具体操作的是哪一个具体的class,虽然它们各自功能不同,只要它们都是实现了同一个interface,那么使用起来就相互没什么两样。
在物理现实中,我们可以把多态想象成“按按钮”——是个人就知道怎么去触碰一个按钮,尽管不同的按钮功能不一样,但只要它是个按钮,操作起来就非常简单
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态最常见的2种实现方式:
1 覆蓋
2 重載
覆盖指子类重新定义父类方法,(js)这正好就是基于prototype继承的玩法,这不就多态了么。
重载是指多个同名但参数不同的方法,这个JavaScript确实没有。所以多态还是有的,只不过重载在JavaScript中没有实际对应的语法概念,你得绕个很大的弯子来玩这事
簡單來說就是策瑞模式
同一個動作不同人去做
所以同一介面 同一的動作
用一個interface去解決
千套 new ->做事的多層問題
###### tags: `PHP`