PHP,DDD,CQRS,Event Sourcing,Kubernetes,Docker,Golang

0%

设计模式笔记:第一个设计模式原则——按接口编程

第一个设计模式原则:按接口而不是按实现来编程。
以最简单的方式来讲就是,要将变量设置为一个抽象类或接口数据类型的实例,而不是一个具体实现的实例。

比如说,声明一个变量$bianliang,那么其值类型应该是某个抽象类或接口。这在强类型语言中比较容易做到,假设有个接口ITest,那么我声明变量则应该是这样声明的ITest bianliang,此时变量bianliang的数据类型是ITest。但对于PHP来说,这可能有些困难,因为PHP变量的数据类型是在给该变量赋值时而确定的,但抽象类或接口不能被直接实例化(因为需要被继承实现啊),所以也就无法得到一个接口数据类型的PHP变量。最多得到一个该接口具体实现类的数据类型的变量。看下列代码及注释:

//现有一个接口ITest
Interface ITest
{
    ...
}
//现有一个类Ceshi,实现了ITest
class Ceshi implenmts ITest
{
    ...
}
//那么,最多我声明的变量$bianliang的数据类型是Ceshi
$bianliang = new Ceshi;

但其实没关系,变量$bianliang的数据类型不仅是CeshiITest也可以作为该变量的数据类型。记住这一句话:一个对象实例的数据类型不仅是它实例化的对象类型,该对象的父类也将作为它的数据类型

在这里要注意一点是,按接口编程中的接口这个概念,并不是只用关键字Interface创建的才叫接口,我们这里的接口可以指Interface创建出来的也可以指某个抽象类,我觉得其实所有类都可以成为接口,不过这里我们就只要认为是Interface和抽象类吧,免得搞混。

来一个按接口编程的例子:

abstract class IAbstract
{
    //对所有实现都可用的属性
    protected $valueNow;
    /*所有实现都必须包含以下两个方法*/
    //必须返回十进制值
    abstract protected function giveCost();
    //必须返回字符串值
    abstract protected function giveCity();
    //这个具体函数对所有类实现都可以用,而不覆盖内容
    public function displayShow()
    {
        $stringCost =  $this->giveCost();
        $stringCost = (string)$stringCost;
        $allTogether = ("Cost: $": . $stringCost . " for " . $this->giveCity());
        return $allTogether;
    }
}
//接下来是这个抽象类的两个不同扩展,它们分别提供抽象方法的不同实现。
class NorthRegion extends IAbstract
{
    //必须返回十进制值
    protected function giveCost()
    {
        return 210.54;
    }
    //必须返回字符串值
    protected function giveCity()
    {
        return "Moose Breath";
    }
}
class WestRegion extends IAbstract
{
    //必须返回十进制值
    protected function giveCost()
    {
        $solarSavings = 2;
        $this->valueNow=210.54/$solarSavings;
        return $this->valueNow;
    }
    //必须返回字符串值
    protected function giveCity()
    {
        return "Rattlesnake Gulch";
    }
}

利用一个抽象类的两个不同实现,可以看到,这里所说的“按接口编程”实际上是指类的接口,而不是使用关键字interface定义的接口结构。最后,Client类建立了一个包含代码提示的方法,指定这个抽象类作为接口:

class Client
{
    public function __construct()
    {
        $north = new NorthRegion;
        $west = new WestRegion;
        $this->showInterface($north);
        $this->showInterface($west);
    }
    private function showInterface(IAbstract $region)
    {
        echo $region->displayShow().'<br>';
    }
}
$worker = new Client();

最终输出结果:

Cost: $210.54 for Moose Breath
Cost: $105.27 for Rattlesnake Gulch

对于不同的地区,结果值是不同的,因为两个具体类NorthRegionWestRegion分别采用了不同方式来实现这个抽象方法。如果使用了一个不正确的数据类型,则会报错。

所以,就其自身而言,类型提示可以帮助你尽可能遵守第一个设计模式原则,即按接口而不是按实现来编程。

如果想了解这种编程风格的好处,可以增加IAbstract抽象类的实现,并增加到Client类中,可以看到,只要保持接口一致,完成增补和修改很容易。