# 物件導向 **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`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up