PHP设计模式-适配器模式

适配器模式

组合优于继承

学习设计模式,最经常听到的一句话就是:组合优于继承,因为使用组合,可以使参与者之间的绑定更宽松,在重用、结构和修改等方面会有很多的有点。这个和继承不同,继承类或者所继承的类中包含已经实现的方法,这其实也是一种绑定,使用组合,就没有这种紧密绑定的缺点。

使用继承的适配器类图

使用继承的适配器类图

使用组合的适配器类图

使用组合的适配器类图

组合适配器的例子

有一家温湿度传感器公司,这个传感器可以测试空气的温度和湿度。分别调用getTemperature()方法和geThumidity()

class Sensor {

    public function getThumidity() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperature() {
        echo "the temperature is 45678... \n";
    }
}

这个时候呢,由于公司扩大业务,有一家智能家居的公司前来谈合作,想要使用遥控,来读取温度和湿度,分别调用方法 getThumidityByRemote() 和getTemperatureByRemote() ,没办法,只能扩充Sensor的类了。

class Sensor {

    public function getThumidity() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperature() {
        echo "the temperature is 45678... \n";
    }

    public function getThumidityByRemote() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperatureByRemote() {
        echo "the temperature is 45678... \n";
    }
}

紧接着,又来了一家智能音箱的公司,也要接入温湿度传感器,但是这家公司是根据用户的话的内容来区分是否查询温湿度getByAnswer($code), $code = 0的时候,查询温度,当$code = 1的时候,查询湿度。

class Sensor {

    public function getThumidity() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperature() {
        echo "the temperature is 45678... \n";
    }

    public function getThumidityByRemote() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperatureByRemote() {
        echo "the temperature is 45678... \n";
    }

    public function getByAsnwer($code) {
        if ($code == 0) {
            echo "the temperature is 45678... \n";
        } else if ($code == 1) {
            echo "the thumidity is 1234... \n";
        }
    }
}

可能你已经发现了问题,随着其他的公司不断的接入,Sensor的类会变得更加臃肿不看。或许我们应该好好区分一下整个过程中的角色问题。

  1. 目标(Target)角色:智能家居公司和智能音箱公司的接口或者方法,使我们需要实现的目标(target),因为外接需要调用这两个方法才行
  2. 源(Adaptee)角色:Sensor的温湿度查询,是我们需要适配方法,也就是,我们可以通过这两个方法,进行温湿度查询。
  3. 适配器(Adapter)角色:我们需要通过这个,来协调目标角色和源角色。

定义Sensor类

class Sensor {

    public function geThumidity() {
        echo "the thumidity is 1234... \n";
    }

    public function getTemperature() {
        echo "the temperature is 45678... \n";
    }

}

定义智能家居和智能音箱的接口

interface Household {
    public function geThumidityByRemote();
    public function getTemperatureByRemote();
}
interface Spearkers {
    public function getByAnswer($code);
}

定义适配器

class HouseholdTarget implements Household {

    private $_sensor = NULL;

    public function __construct(Sensor $sensor) {
        $this->_sensor = $sensor;
    }

    public function geThumidityByRemote() {
        $this->_sensor->geThumidity();
    }
    public function getTemperatureByRemote() {
        $this->_sensor->getTemperature();
    }

}
class SpearkersTargat implements Spearkers {

    private $_sensor = NULL;

    public function __construct(Sensor $sensor) {
        $this->_sensor = $sensor;
    }

    public function getByAnswer($code) {
        if ($code == 0) {
            $this->_sensor->getTemperature();
        } else if ($code == 1) {
            $this->_sensor->geThumidity();
        }
    }

}

客户端进行调用

function __autoload($class_name) {
    if (file_exists(__DIR__ . "/{$class_name}.php")) {
        require_once __DIR__ . "/{$class_name}.php";
    }
}

$sensor = new Sensor();
$HouseholdTarget = new HouseholdTarget($sensor);
$HouseholdTarget->geThumidityByRemote();
$HouseholdTarget->getTemperatureByRemote();

$SpearkersTargat = new SpearkersTargat($sensor);
$SpearkersTargat->getByAnswer(0);
$SpearkersTargat->getByAnswer(1);

输出结果
the thumidity is 1234…
the temperature is 45678…
the temperature is 45678…
the thumidity is 1234…

结论

使用适配器模式之后,能够更加方便对其进行拓展,如果有再多的公司进行接入,也不用担心影响Sensor类。

使用场景

1.系统需要使用现有的类,而此类的接口不符合系统的需要。

2.想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。

3.(对组合适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。

PHP设计模式-原型模式

原型模式

原型设计模式是通过使用一种克隆技术复制实例化的对象。新对象是通过复制原型而进行创建的。原型设计模式的目的是通过克隆以减少实例化对象的开销。

何时使用原型模式

原型模式要求你创建某个原型对象的多个实例,这个时候就可以使用原型模式。比如,在关于进化的研究,通常使用果蝇作为研究对象。果蝇很快繁殖,基本上一个小时就能进行产卵,和其他的生物相比,找到和记录变异的几率更大。如果换做大象的话(比如大象的孕育期长达22个月),那么整个研究过程将会消耗很长的时间。因此,只需要完成两个实例化(一个雄性和一个雌性),然后就可以跟进克隆多个变异,而不需要由具体类另外创建实例。

克隆函数

使用原型模式,首先就要了解如何使用PHP的__clone()。

abstract class CloneMe {
    public $name;
    abstract function __clone();
}

class Person extends CloneMe {

    public function __construct() {
        $this->name = "Original";
        echo 'hello';
    }

    public function display() {
        echo "\n$this->name\n";
    }

    public function __clone() {}

}
$worker = new Person();
$worker->display();

$slacker = clone $worker;
$slacker->display();

定义了一个抽象类CloneMe,然后定义一个Person类进行实现。定义一个变量$worker进行实例化,然后使用关键字clone进行对象的克隆。

输出结果:
hello
Original

Original

不过需要注意的是,__clone()不能直接调用,会出现报错Fatal error: Cannot call __clone() method on objects - use 'clone $obj' instead

克隆不会启动构造函数

上面的输出结果可能已经见到了,clone是不会启动构造函数的。其实这个也是比较理解的。举个例子,现在有一个人A,那么这个人肯定会有手脚,那么手脚就算是默认构造函数进行的初始化,如果要根据A克隆一个人B,那么B不用再造手脚,而是克隆之后,就自带了A的手脚,当然,你也可以把B的手脚砍掉(比如$slacker->name=”Tyler Teng”)。

abstract class CloneMe {
    public $name;
    abstract function __clone();
}

class Person extends CloneMe {

    public function __construct() {
        $this->name = "Original";
        echo 'hello';
    }

    public function display() {
        echo "\n$this->name\n";
    }

    public function __clone() {
    }

}
$worker = new Person();
$worker->display();

$slacker = clone $worker;
$slacker->name = "Tyler Teng";
$slacker->display();

输出结果
hello
Original

Tyler Teng

研究果蝇

抽象类和具体实现

简单定义三个属性:眼睛的颜色,翅膀震动次数,眼睛个数

abstract class IPrototype {
    public $eyeColor;
    public $wingBeat;
    public $uniyEyes;
    abstract function __clone();
}

除了一些基本的属性,还需要区别雄性和雌性

include_once 'IPrototype.php';
class FemaleProto extends IProtoType {

    const gender = "FEMALE";
    public $fecundity;

    public function __construct() {
        $this->eyeColor = "red";
        $this->wingBeat = "220";
        $this->unitEyes = "760";
    }

    public function __clone() {}

}
include_once "IPrototype.php";
class MaleProto extends IPrototype {

    const gender = "MALE";

    public $mated;

    public function __construct() {
        $this->eyeColor = "red";
        $this->wingBeat = "220";
        $this->unitEyes = "760";
    }

    public function __clone() {}

}
客户端

我们定义一个Client类,首先从具体类中实例化$fly1和$fly2,$c1Fly、$c2Fly和$updateCloneFly则分别是这两个类实例的克隆

function __autoload($class_name) {
    include_once realpath(__DIR__) . '/' .  $class_name . '.php';
}
class Client {
    private $fly1;
    private $fly2;

    private $c1Fly;
    private $c2Fly;
    private $upDatadCloneFly;

    public function __construct() {
        $this->fly1 = new MaleProto();
        $this->fly2 = new FemaleProto();

        $this->c1Fly = clone $this->fly1;
        $this->c2Fly = clone $this->fly2;

        $this->upDatadCloneFly = clone $this->fly2;
        $this->c1Fly->mated = "true";
        $this->c2Fly->fecundity = "186";
        $this->upDatadCloneFly->eyeColor = "purple";
        $this->upDatadCloneFly->wingBeat = "220";
        $this->upDatadCloneFly->unitEyes = "750";
        $this->upDatadCloneFly->fecundity = "92";

        $this->showFly($this->c1Fly);
        $this->showFly($this->c2Fly);
        $this->showFly($this->upDatadCloneFly);
    }

    public function showFly(IProtoType $fly) {
        echo "Eye color : " . $fly->eyeColor . "\n";
        echo "Wing Beats/second : " . $fly->wingBeat . "\n";
        echo "Eye units : " . $fly->unitEyes . "\n";
        $genderNow = $fly::gender;
        echo "Gender : " . $genderNow . "\n";
        if ($genderNow == "FEMALE") {
            echo "Numbers of eges : " . $fly->fecundity .  "\n";
        } else {
            echo "Mate : " . $fly->mated . "\n";
        }
    }
}

$woker = new Client();

输出
Eye color : red
Wing Beats/second : 220
Eye units : 760
Gender : MALE
Mate : true
Eye color : red
Wing Beats/second : 220
Eye units : 760
Gender : FEMALE
Numbers of eges : 186
Eye color : purple
Wing Beats/second : 220
Eye units : 750
Gender : FEMALE
Numbers of eges : 92

总结

作为被克隆的类,默认构造函数不应该做太多的初始化,否则结果往往不灵活,而且是过度的耦合设计。构造函数不应完成具体的工作,一种做法是忽略模式类中的构造函数,除非你有充分的理由包含这些构造函数;另外一种做法是,允许在需要的时间调用,让客户端负责实例化和克隆的有关事务。

参考文献

  • Learning PHP设计模式

附件