Markdown

物件導向基礎


tags: DesignPatterns

Object-Oriented Programming

什麼是物件導向? 根據 WIKI 中的定義如下 :
物件導向程式設計(英語:Object-oriented programming,縮寫:OOP)是種具有物件概念的程式程式設計典範,同時也是一種程式開發的抽象方針。它可能包含資料、屬性、程式碼與方法。物件則指的是類別的實例。它將物件作為程式的基本單元,將程式和資料封裝其中,以提高軟體的重用性、靈活性和擴充性,物件裡的程式可以存取及經常修改物件相關連的資料。在物件導向程式程式設計裡,電腦程式會被設計成彼此相關的物件。

りしりこんさ小 ???

簡單的來說,物件導向設計可以視為一種思考方式,將所有問題做抽象化思考。
將每件事情分解成物件,每個物件又有各自的行為和狀態。透過這些狀態間的訊息傳遞,互相依賴,來達成設計上的需求,比起傳統的程序導向思考,物件導向這種抽象化思考更能夠便於管理物件以及開發解決問題。

大綱

  • 類別 (Class) 與 物件 (Object)

    • 類別描述物件而物件則是類別的實例。建立物件的動作稱為「實例化」。 以藍圖作比喻,若類別是藍圖,物件就是根據藍圖所蓋的建築物。
  • 封裝(Encapsulation):

    • 將物件內部的資訊隱藏起來,限制外部的直接存取,並提供一組或多組介面提供外部存取。
  • 抽象資料型態 (abstract data types):

    • 在電腦科學中,抽象資料型態代表的是某種特定的資料結構或者數學模型,例如,抽象的堆疊(stack)由3個操作定義:推入 push,彈出 pop,檢視堆疊頂端資料 peek。
  • 狀態(State)的保存 :

    • 物件狀態的處理
  • 物件特性 :

    • 物件本身的屬性?
  • 訊息(Messages) :

    • 物件與物件之間透過訊息傳遞彼此協作。
  • 繼承(Inheritance)

    • 又稱為一般化(Generalization)與特殊化(Specialization)的關係,A繼承B乃指物件類別A是物件類別B的一種,因此物件類別A可以繼承物件類別B的屬性與方法。兩個以上類別一般化後之類別稱為超類別(Superclass)。
  • 多型(Polymorphism) :

    • 一個訊息的意義是由接收者決定而不是發送者,例如發動引擎對汽車跟飛機來說實現的方式就不一樣。
  • 一般化(Generality) :

    • 一般化指的是將共通特性從兩個或以上的類別中提取出來並定義一個擁有這些特性的超類別的過程,其中共通的特性可以是屬性、關係、方法等。

類別與實體

物件是一個獨立自主的實體,用一組可識別的特性和行為來表示。

貓叫

function moew() {
    echo 'moew';
}

moew();

其他地方也可能用到貓叫這個功能

function shout() {
    moew();
}

shout();

貓叫這個 function 應該是屬於貓的

class Cat 
{
    public function shout() {
        echo 'moew';
    }
}

類別就是具有相同之屬性和功能的物件的抽象集合

Class 表示定義類別的關鍵字

Cat 就是類別的名稱

Shout 就是類別的方法

對外公開的方法使用 public 屬性修飾

今天要使用貓這個類別的時候,只要將類別實體化就可以了
什麼叫做實體化?
實體,就是一個真實的物件,例如 我 就是 人 這個類別的實體,
而使實體化就是建立物件的過程,使用 new 關鍵字來建立。

// 定義類別
class Cat {
    public function shout() {
        echo 'moew';
    }
}
// 實體化
$cat = new Cat();
// 呼叫類別方法
$cat->shout();

建構式

建構式,又叫建構函式,其實就是對類別進行初始化。建構式與類別同名,無返回值,在 new 時調用。

所有的類別都有建構式,如果你沒有做任何定義,系統會自動產生空的建構式,若你有定義的建構式,那麼預定地建構式就會生效了。

<?php

class Cat {
    private $name = '';
    
    function __construct($name = '') {
        $this->name = $name;
    }
    
    public function moew() {
        echo $this->name . ' moew';
    }
}

$cat = new Cat('nyan cat');

$cat->moew();

// output : nyan cat moew

屬性與修飾子

屬性是一個方法或一對方法,但在調用它的程式碼看來,它是一個欄位,即屬性適合於以欄位的方式使用方法調用的場合。

欄位式儲存類別要滿足其設計所需要的資料,欄位是與類別相關的變數。

例如:

private $name = 'nyan';

const 常數

不變的量可以用常數宣告,在定義和使用常數的時候不需要使用 $ 宣告。(介面中也可以定義常數)

class MyClass {
    const CONST_VALUE = 'A constant value';
}
echo MyClass::CONST_VALUE; // A constant value

static 變數

使用時不用建立物件就可以用

ClassName::$staticVariable

public 變數

要建立物件後才可以用,但可以在類別以外的地方使用

$test = new Test();
$test->pubilcVariable;

protected 變數

必須建立物件後才可以用,不可以在類別以外的地方做使用,但可以被子類別使用。

private 變數

必須建立物件後才能使用,只能在類別內使用且不能被繼承。

<?php class MyClass { const CONST_VALUE = 'A constant value'; } $classname = 'MyClass'; echo $classname::CONST_VALUE; // As of PHP 5.3.0 echo "\n"; echo MyClass::CONST_VALUE; echo "\n"; class OtherClass extends MyClass { public static $my_static = 'static var'; public static function doubleColon() { echo parent::CONST_VALUE . "\n"; echo self::$my_static . "\n"; } } $classname = 'OtherClass'; $classname::doubleColon(); // As of PHP 5.3.0 OtherClass::doubleColon(); /* A constant value A constant value A constant value static var A constant value static var */
<?php class Test { const CONST_VALUE = 'CONST VALUE'; static $staticValue = 'staticValue'; public $publicValue = 'publicValue'; private $privateValue = 'privateValue'; protected $protectedValue = 'portectedValue'; } echo Test::CONST_VALUE; // const 常數不需要建立物件也不需要$字號就可以直接使用 echo "\n"; echo Test::$staticValue; // static 不需要建立物件就可以直接使用 echo "\n"; $test = new Test(); echo $test->publicValue; // public 建立物件後可以讓外部調用 echo "\n"; class Test2 extends Test { function testExtendGetPublic() { echo $this->pubilcValue; } function testExtendGetProtected() { echo $this->protectedValue; } function testExtendGetPrivate() { echo $this->privateValue; } } $test2 = new Test2(); $test2->testExtendGetPublic(); // Undefined property echo "\n"; $test2->testExtendGetProtected(); echo "\n"; $test2->testExtendGetPrivate(); // Undefined property echo "\n"; /* CONST VALUE staticValue publicValue <br /> <b>Notice</b>: Undefined property: Test2::$pubilcValue in <b>[...][...]</b> on line <b>29</b><br /> portectedValue <br /> <b>Notice</b>: Undefined property: Test2::$privateValue in <b>[...][...]</b> on line <b>37</b><br /> */

封裝

每個物件都包含它進行操作所需要的所有資訊,這個特性稱為封裝,因此物件不必依賴其他物件來完成自己的操作。

封裝的好處:

  • 良好的封裝能夠減少耦合
  • 類別內部的實現可以自由地更改
  • 類別具有清晰的對外界介面

繼承

物件的繼承代表了一種 is-a 的關係,如果兩個物件 A 和 B,可以描述為 B 是 A,則表明 B 可以繼承 A。
例如: 貓是哺乳動物。

繼承者還可以理解為是對被繼承者的特殊化,因為他除了具備了被繼承者的特性外,還具備自己獨有的個性。

繼承定義了類別如何互相關聯,共用特性。繼承的工作方式是,定義父類別和子類別,或叫做基礎類別和衍生類別,其中子類別繼承父類別的所有特性。子類別不但繼承了父類別的所有特性,還可以定義新的特性。

繼承的幾個特性:

  1. 子類別擁有父類別非 private 的屬性和功能。
  2. 子類別具有自己的數性和功能,即子類別可以擴展父類別沒有的屬性和功能
  3. 子類別可以用自己的方法實現父類別的功能。

對於建構式繼承的問題,在 PHP 之中說明如下
https://www.php.net/manual/en/language.oop5.decon.php

Note: Parent constructors are not called implicitly if the child class defines a constructor. In order to run a parent constructor, a call to parent::__construct() within the child constructor is required. If the child does not define a constructor then it may be inherited from the parent class just like a normal class method (if it was not declared as private).

<?php
class BaseClass {
    function __construct() {
        print "In BaseClass constructor\n";
    }
}

class SubClass extends BaseClass {
    function __construct() {
        parent::__construct();
        print "In SubClass constructor\n";
    }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();
?>

繼承的缺點:

  1. 當使用繼承時,父類別變動,子類別也不得不變
  2. 繼承會破壞封裝,父類別實現細節暴露給子類別,增加了類別間的耦合

當兩個類別之間具備了 is-a 的關係時,就可以考慮使用繼承。

多型

多型表示不同的物件可以執行相同的動作,但要透過他們自己的實現程式碼來執行

工廠方法中每個工廠都各自實作相同的方法,
在客戶端中實際上客戶只要知道他調用了工廠方法,但由哪個工廠內部如何實做方法以及執行細節都不需知道。

<?php

interface ShapesInterface{
    public function getArea();
}


class Rectangle implements ShapesInterface{
    public function getArea(){
        return "Rectangle Area";
    }
}

class Polygon implements ShapesInterface{
    public function getArea(){
        return "Polygon Area";
    }
}

class SimpleShapeFactory
{
    public function createShape($shape = 'Rectangle')
    {
        return new $shape();
    }
}

$shapeFactory = new SimpleShapeFactory();
$shape = $shapeFactory->createShape('Rectangle');
$area = $shape->getArea(); // "Rectangle Area"

抽象類別

  1. 抽象類別不能實體化
  2. 抽象方法是必須被子類別重寫的方法
  3. 如果類別中包含抽象方法,那麼類別就必須定義為抽象類別,不論是否還包含其他一班方法。

抽象類別擁有盡可能多的共同程式碼,擁有盡可能少的資料。

抽象類別通常代表一個抽象概念,它提供一個繼承的出發點,當設計一個新的抽象雷別實,一定是用來繼承的,所以,在一個以繼承關係形成的等級結構裡面,樹葉節點應當是具體類別,而樹枝節點均應當是抽象類別。

介面

介面是把隱式公共方法和屬性組合起來,以封裝特定功能的一個集合。一旦類別實現了介面,類別就可以支援介面所指定的所有屬性和成員。宣告介面在語法上與宣告抽象類別完全相同,但不允許提供介面中任何成員的執行方式。

介面不能實體化,不能有建構式、欄位,不能有修飾子

實現介面的類別必須要實現介面中的所有方法和屬性。

一個類別可以支援多個介面

抽象類別 & 介面

型態上來說:

  1. 抽象類別可以給出一些成員的實現,介面卻不包含成員的實現
  2. 抽象類別的抽象成員可以被子類別部分實現,介面的成員需要實現類別完全實現。
  3. 一個類別只能繼承一個抽象類別,一個類別可以實現多個介面。

意義上來說:

  1. 類別是對物件的抽象; 抽象類別是對類別的抽象(對類別整體例如欄位、屬性、方法進行抽象); 介面是對行為的抽象(對類別的局部行為)。
  2. 如果行為跨越不同類別的物件,可使用介面;對於一些像似的類別物件,用繼承抽象類別。(實現界面和繼承抽象並不衝突,超人可以繼承人類再實現飛行介面)
  3. 從設計角度來說,抽象類別是從子類別中發現了公共的東西,泛化出父類別,然後子類別繼承父類別,而介面是根本不知子類別的存在,方法如何實現還不確認,預先定義。
  4. 抽象類別是自底而上抽象出來的(往往透過重構時發現),而介面則是自頂向下設計出來的。

https://code.tutsplus.com/tutorials/understanding-and-applying-polymorphism-in-php--net-14362

https://newaurora.pixnet.net/blog/post/190301967-static變數、public變數、private變數、protected變

https://www.php.net/manual/en/language.types.callable.php

http://yes.nctu.edu.tw/vc/ref/oop/oop.pdf

搞笑談軟工 - 什麼是物件導向(2):Object, Class, Instance

很棒的舉例

https://sourcemaking.com/uml/modeling-it-systems/structural-view/generalization-specialization-and-inheritance

留言