iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
4
Software Development

看到 code 寫成這樣我也是醉了,不如試試重構?系列 第 10

SOLID 之 介面隔離原則(Interface segregation principle)

Clients should not be forced to depend on methods that they do not use.

直譯:「客戶不應該被強迫依賴他們不使用的方法」

我們來看個例子:有隻瑪爾濟斯(Maltese),愛吃也愛散步;主人訓練它握手,客人來都會表演一下,它的實作如下:

<?php
interface Dog
{
    public function eat();
    public function walk();
    public function handshake();
}

class Maltese implements Dog
{
    public function eat()
    {
        print '吃飯皇帝大!';
    }
    
    public function walk()
    {
        print '出來放風囉!';
    }
    
    public function handshake()
    {
        print '我會握手哦!';
    }
}

$myMaltese = new Maltese();
$myMaltese->eat();
$myMaltese->walk();
$myMaltese->handshake();

思考一下,如果這時來了新的家庭成員--雪納瑞(Schnauzer),雖然它長的很像瑪爾濟斯,但因為剛來到主人家,它還不會握手,這時 handshake 的實作就會很奇怪:

<?php

class Schnauzer implements Dog
{
    public function eat()
    {
        print '吃飯皇帝大!';
    }
    
    public function walk()
    {
        print '出來放風囉!';
    }
    
    public function handshake()
    {
        throw new Exception('主人哩咧共蝦會');
    }
}

$mySchnauzer = new Schnauzer();
$mySchnauzer->eat();
$mySchnauzer->walk();
$mySchnauzer->handshake();   // 失控的雪納瑞

為什麼會這樣?因為我們在定義狗(Dog)的時候,應該從思考狗有哪些行為開始,像一般的狗都會有 eatwalk 的行為,而 handshake 是主人教完才會的行為。

但雪納瑞不會握手,硬要把它實作出來也怪怪的,好吧!那只好丟例外。但這樣就違反里氏替換原則了,因為這很有可能在子類替換父類時,發生非預型的行為,程式也會因此變得非常不穩定。

針對這個問題,必須小小重構一下,才能順利替換。

換子類前一定要先拆介面:

<?php
interface Dog
{
    public function eat();
    public function walk();
}

interface Show
{
    public function handshake();   
}

改成兩個介面之後,瑪爾濟斯和雪納瑞在實作 handshake 時,就不會起爭議了。接著來調整實作:

<?php

class Maltese implements Dog, Show
{
    public function eat()
    {
        print '吃飯皇帝大!';
    }
    
    public function walk()
    {
        print '出來放風囉!';
    }
    
    public function handshake()
    {
        print '我會握手哦!';
    }
}

class Schnauzer implements Dog
{
    public function eat()
    {
        print '吃飯皇帝大!';
    }
    
    public function walk()
    {
        print '出來放風囉!';
    }
}

這次雪納瑞的實作就比較公道了,場景範例程式如下:

<?php

class Context
{
    public function feed(Dog $dog)
    {
        $dog->eat();
    }
    
    public function play(Show $player)
    {
        $player->handshake();
    }
}

$context = new Context();

// 只要是狗都能餵食
$context->feed(new Maltese());
$context->feed(new Schnauzer());

// 只要會表演的都會握手
$context->play(new Maltese());
// 這裡就會有「雪納瑞不會握手」的提示了
// $context->play(new Schnauzer());

優點

遵守介面隔離原則最大的好處是,在需要多型時,會比較容易為類別實作對應方法。

參考資料


上一篇
SOLID 之 里氏替換原則(Liskov substitution principle)
下一篇
SOLID 之 依賴反轉原則(Dependency inversion principle)
系列文
看到 code 寫成這樣我也是醉了,不如試試重構?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言