Site icon Soul & Shell Blog

這個物件導向有點依賴!

物件導向程式設計

物件導向程式設計 (Object-Oriented Programming) 至今已經有半個世紀的歷史,它的誕生無非是為了解決程式錯綜複雜的結構設計,它的出現確時大幅提昇程式碼的維護性。物件導向大概有寫過程式的人都聽過,路上隨便抓個會寫程式的大學生,問他什麼是物件導向,比較制式的答案當然就是「封裝 (Encapsulation)、繼承 (Inheritance) 與多型 (Polymorphism)」。但實際上在我們使用物件導向撰寫程式碼時,除了靈活運用封裝、繼承與多型等特性之外,還會運用一些設計模式 (Design Pattern) 來設計系統。有了物件導向為什麼還需要「設計模式」呢?其實這只是為了幫助我們設計出更容易維護的系統,避免落入物件導向的依賴陷阱。

依賴關係是物件導向的不明產物

我們都知道系統的耦合關係是越鬆散越好、越低越好,一個系統最好可以很輕易的切割功能與新增功能。系統耦合性有些抽象,講起來很簡單,做起卻不容易。先講個故事先:

A 程式設計師:我負責的系統最近需要計算腳踏車的速度,這程式可要花上不少時間!

B 程式設計師:哈,A 兄,你賺到了!正巧我上個月才寫完計算機車速度的程式,你拿去用就好,更好的消息是,我用了物件導向,你只要將某某類別 (Class) 實體化之後就可以使用了!程式在這 ... 你整合進去就可以用囉!

A 程式設計師(取得程式後開始整合到自己的系統,編譯時發現這個類別繼承了其他類別 ... GG)

A 程式設計師:感謝 B 大大無私的分享,但我編譯的過程中似乎少了某某類別,可以補給我嗎?

B 程式設計師:哈,我漏了,繼承的類別在這 ... 你應該整合進去就可以用囉!

A 程式設計師(取得繼承類別後開始整合到自己的系統,編譯時發現這些類別還有實做一些介面 ... GG)

A 程式設計師:再次感謝大大無私的分享,但我編譯的過程中似乎少了某某介面的程式碼,可以再補給我嗎?

B 程式設計師:哈,我又漏了,介面程式在這 ... 這次是真的!

A 程式設計師(取得介面後程式編譯還是失敗,因為又發現某些類別使用了其他類別)

A 程式設計師:又 GG 了,好像少了一堆類別與介面等等 ...

B 程式設計師:我整個系統程式都給你好了

A 程式設計師(用很奇怪的方法整合了 B 兄提供的系統,最後因為類別名稱衝突,還是失敗了 ... 最後只好違反 DRY 原則 (Dont Repeat Yourself) Copy 程式碼來達成 ...)

怪了,物件導向不是很好維護嗎?怎麼會一直發生這些蠢事呢?答案其實就是「依賴關係」搞的鬼,我們在設計與實作時忽略了依賴關係這個因子,那什麼是依賴關係呢?其實我們的程式碼中,依賴關係處處可見,對於程式來看所謂的依賴就是「沒有你我活不下去」。先撇開物件導向的實作原則等等裡論,我們來看看「依賴關係」究竟藏在哪裡,先看看以下程式碼範例 (請容許我用不是很物件的 PHP 來說明):

<?php

interface Car
{
    // interface..
}

interface Controllable
{
    // interface..
}

class TwoWheelCar
{
    // implements...
}

class BicycleDriver
{
    // implements...
}

class ManPower implements Power
{
    // implements...
}

class Bicycle extends TwoWheelCar implements Controllable
{
    private $_driver = null;
    private $_power = null;

    public function __construct ()
    {
        $this->_driver = new BicycleDriver();
        $this->_power = new ManPower();
    }

    // implements..
}

class Story
{
    public static function main ()
    {
        new Bicycle();
        // do something ...
    }
}

上述的的程式中我們先觀察一下 Bicycle Class 的程式碼,分析其依賴關係如下:

  1. Bicycle Class 繼承 TwoWhellCar Class,沒有 TwoWhellCar Class 活不了
  2. Bicycle Class 實作 Controllable Interface,沒有 Controllable Interface 活不了
  3. Bicycle Class 實體化與使用 BicycleDriver Class,沒有 BicycleDriver Class 活不了
  4. Bicycle Class 實體化與使用 ManPower Class,沒有 ManPower Class 活不了

上述可見 Bicycle Class 依賴了許多其他的類別與介面,然而在實際的編譯與執行過程中,依賴關係更是深遠,就像葡萄一樣抓起來一整串。在傳統的物件導向程式語言中,只要有「繼承、實作與實體化類別」這些地方就會產生依賴關係。在物件導向的設計理念中,類別除了繼承與實作產生了依賴關係,在程式碼中直接依賴其他類別是比較不被推薦的,然而靜態類別的使用更是容易不小心就產生了依賴關係。

利用設計模式改善類別依賴關係

回顧上述所產生的依賴關係,這結果將使得 Bicycle Class 的可攜性變得很低。但是有些地方所造成的類別依賴,是可以透過一些技巧來改善的,像是實體化類別的動作。我們修改一下 Bicycle Class 與 Story Class 如下:

<?php

interface Car
{
    // interface..
}

interface Controllable
{
    // interface..
}

interface Driver
{
    // interface..
}

interface Power
{
    // interface..
}

class TwoWheelCar implements Car
{
    // implements...
}

class BicycleDriver implements Driver
{
    // implements...
}

class ManPower implements Power
{
    // implements...
}

class Bicycle extends TwoWheelCar implements Controllable
{
    private $_driver = null;
    private $_power = null;

    public function setDrive (Driver &$driver)
    {
        $this->_driver = $driver;
    }

    public function setPower (Power &$power)
    {
        $this->_power = $power;
    }

    // implements..
}

class Story
{
    public static function main ()
    {
        $bicycle = new Bicycle();
        $bicycle->setDrive(new BicycleDriver());
        $bicycle->setPower(new ManPower());
    }
}

在上述的程式碼中,我們主要修改了兩個地方:

  1. 改用 Driver 與 Power Interface 取代原本 Bicycle Class 直接依賴 BicycleDriver 與 ManPower Class
  2. 將原本 Bicycle Class 實體化的工作轉交由第三方 Story 處理

第一點改用 Interface 拆解依賴關係是物件導向比較常見的技巧,這裡就不多做說明。第二個動作比較值得思考,我們將實體化類別的動作較給其他人處理,增加 Story 這個衰人的依賴關係,讓 Bicycle 自身變得更清澈,這樣轉移實體化動作到另一個衰人的技巧我們稱為依賴注射 (DI, Dependency Injection)。這樣一來 Bicycle Class 就剩下繼承類別與幾個介面存在依賴關係,可攜性變高了。可是想想,不對啊,如果大家都把這樣的依賴關係轉移到 Story Class,這樣一來 Story 可就衰了!但其實在實務上,我們會利用更高級的解依賴技巧,像是反射、動態實例化、混成、註譯設計甚至動態編譯等等手段來達成,今天先到這裡,關於依賴注射的實作我們下次在介紹囉。

Exit mobile version