Interface segregation principle (ISP) - tạm dịch: chả biết dịch sao cho sát nghĩa ??, cái từ segregation dịch ra là tách rời ra. Interface thì khỏi phải giải thích rồi nhỉ :) - anw, let’s move on …

Nguyên tắc này được diễn giải theo nguyên bản tiếng anh theo Uncle Bob “Client should not be forced to implement an interface that it doesn’t use” - tức là các lớp con (derived classes) không nên hiện thực những phương thức (method) mà nó không cần dùng đến.

Nghe lùng bùng, và khó hiểu, mình lấy lại cái ví dụ mà mình xem ở laracast để minh hoạ trong bài viết này.

Ta có 1 interface WorkerInterface, một class HumanWorker, AndroidWorker. Trong interface WorkerInterface ta có 2 phương thức (work and sleep). Các bạn có thể thấy ngay là AndroidWorker phải hiện thực phương thức sleep khi implements WorkerInterface - điều này thực tế không bao giờ xảy ra, vì android worker là cỗ máy thì ko ngủ ??

Xét diagram sau:

[caption id=“attachment_445” align=“aligncenter” width=“625”]class diagram 1 class diagram 1[/caption]

Có thể thấy rằng AndroidWorkerimplements **sleep **function thì không hợp lý lắm. Tuy nhiên, có thể by pass điều bất hợp lý này bằng cách return null khi implements phương thức này. Nhưng chỉ là giải pháp work-around

<?php
// demo of inversion principle in php
// rule: client should not be forced to implement an interface that it does not use
interface WorkerInterface
{
    public function work();

    public function sleep();
}

class HumanWorker implements WorkerInterface
{
    public function work()
    {
        print 'human worker is working' . PHP_EOL;
    }

    public function sleep ()
    {
        print 'human worker is sleeping' . PHP_EOL;
    }
}

class AndroidWorker implements WorkerInterface
{
    public function work()
    {
        print 'android worker is working' . PHP_EOL;
    }

    public function sleep()
    {
        // print 'android worker is sleeping ?????' . PHP_EOL;
        return null;
    }
}

class Captain
{
    public function manage(WorkerInterface $worker)
    {
        $worker->work();
        $worker->sleep();
    }
}

$aHumanWorker = new HumanWorker();
$anAndroidWorker = new AndroidWorker();
$captain = new Captain();

// manage human worker
$captain->manage($aHumanWorker);
// manage android worker
$captain->manage($anAndroidWorker);

Trong terminal bạn chạy: php inversion-principle.php sẽ có output như sau:

[caption id=“attachment_443” align=“aligncenter” width=“625”]php cli 1 php cli 1[/caption]

Giải pháp:

Để khắc phục ta thử tách riêng 2 functions (sleep, work) trong WorkerInterface thành 2 interfaces (segregation) - với mục đích là **AndroidWorker **không “bị” phải implements hàm sleep. Khi đó ta có diagram sau:

[caption id=“attachment_451” align=“aligncenter” width=“625”]class diagram 2 class diagram 2[/caption]

So far seem so good laH ^^ ??. Ta xét thử đoạn code sau:

class Captain
{
    public function manage(WorkableInterface $worker)
    {
        $worker->work();

        if ($worker instanceof AndroidWorker) return; // this violate open closed principle

        $worker->sleep();
    }
}

$aHumanWorker = new HumanWorker();
$captain = new Captain();
$captain->manage($aHumanWorker);

$anAndroidWorker = new AndroidWorker();
$captain->manage($anAndroidWorker);

–> như các bạn thấy đấy, chúng ta lại vi phạm OCP (open closed principle) khi thiết kế như trên. Vậy thì phải làm sao nhỉ, chúng ta thử thêm một hàm (phương thức, method) trung gian **beManaged **qua một interface trung gian **ManagableInterface **như diagram sau:

[caption id=“attachment_452” align=“aligncenter” width=“625”]class diagram 3 class diagram 3[/caption]

Mấu chốt của giải pháp này là: cả 2 class HumanWorkerAndroidWorker đều implements phương thức beManaged của ManagableInterface. Khi override hàm này trong các class con, chúng ta kiểm soát việc gọi hay không gọi các phương thức (override) từ các interfaces khác

class HumanWorker implements WorkableInterface, SleepableInterface, ManagableInterface
{
    public function work()
    {
        print 'human worker is working' . PHP_EOL;
    }

    public function sleep()
    {
        print 'human worker is sleeping' . PHP_EOL;
    }

    public function beManaged()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkableInterface, ManagableInterface
{
    public function work()
    {
        print 'android worker is working' . PHP_EOL;
    }

    public function beManaged()
    {
        $this->work();
    }
}

class Captain
{
    public function manage(ManagableInterface $worker)
    {
        $worker->beManaged();
    }
}

Phương án trên, còn gọi là **ISP - Interface Segregation Principle. **Hi vọng bài viết này sẽ giúp các bạn có thêm tham khảo về ISP trong SOLID principles

Source code demo: https://github.com/nguyentienlong/isp-in-solid/tree/master

Bài viết liên quan:

  1. http://longka.info/blog/2016/01/24/single-responsibility-trong-solid-principle/ (S)

  2. http://longka.info/blog/2016/04/29/contextual-binding-in-laravel-and-o-in-solid-principles/ (O)

  3. http://longka.info/blog/2015/10/26/a-note-about-solid-principles-demo-in-symfony2/ (SOLID)

References:

  1. Laracast tutorial

  2. https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

  3. Dependency relation in uml: https://vaughnvernon.co/?page_id=31