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
)的時候,應該從思考狗有哪些行為開始,像一般的狗都會有 eat
與 walk
的行為,而 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());
遵守介面隔離原則最大的好處是,在需要多型時,會比較容易為類別實作對應方法。