思考:
- 假设有个需求,模拟鸭子游戏:在游戏中会出现各种各样的鸭子,一边游泳戏水,一边呱呱叫。
开始我们的设计吧:
class Duck
{
public function quack()
{
//鸭子呱呱叫
}
public function fly()
{
//鸭子会飞
}
public function display()
{
//鸭子外观
}
//...鸭子的其他方法
}
class GreenDuck extends Duck
{
public function display()
{
//外观是绿色的
}
}
class RedDuck extends Duck
{
public function display()
{
//外观是红色的
}
}
这应该是大家最容易想到的设计吧,没学习设计模式之前,我也只能想到这样的设计。这样设计有什么坏处了:
1.所有继承了Duck超类的子类都拥有了Duck超类中的方法,实际上并不是所有子类都需要父类的该方法。除非子类覆盖超类的方法。
public function quack() {
//覆盖,然后什么都不做
}
2.如果要让所有鸭子的叫声各不相同,那么必须在子类中重写父类的方法,假设有5个子类,其中2个子类都重写了父类fly()方法,且代码一样:
在GreenDuck类中:
public function fly()
{
echo '我可以垂直起飞';
}
在RedDuck类中:
public function fly()
{
echo '我可以垂直起飞';
}
此时两个类中的fly()方法代码完全一样,造成了代码重复,且后期一旦修改的话就必须依次找到这几个类,然后一起修改代码。如果需求变化很大的话,那维护起来真的不敢想象。
现在引出一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
我们发现鸭子的fly()、quack()方法会根据鸭子类的不同而不断变化,那么我们根据该原则,我们应当把飞行的行为和叫的行为从Duck类中取出独立起来。
此时,引出另外一个设计原则:
针对接口编程,而不是针对实现编程。
官方描述感觉太抽象,还是讲人话吧:
子类的鸭子继承了超类Duck类,其行为是子类自行重写方法实现的或继承自父类的方法而来的。这两种做法都是依赖于"实现"。我们被实现绑的死死的,除非写更多的代码,否则没办法更改行为。
而针对接口编程,意思是针对超类型编程,变量的声明应该是超类,超类一般是一个抽象方法或接口。
请看例子:
针对实现编程
$duck = new GreenDuck();
$duck->fly();
子类直接调用父类的方法。
--------------------------------------------
针对接口编程:
飞行的行为方法来自另外一个类(飞行类),构造函数中引入这个类并赋值给一个属性。
注意,这时实现飞行行为则是飞行类调用自己的fly()的方法,而不是调用父类的方法。
abstract class Duck
{
protected $flyBehavior;
/**
* Duck constructor.
* @param $flyBehavior
*/
public function __construct(FlyBehavior $flyBehavior)
{
$this->flyBehavior = $flyBehavior;
$this->quackBehavior = $quackBehavior;
}
/**
* @param mixed $flyBehavior
*/
public function setFlyBehavior($flyBehavior)
{
$this->flyBehavior = $flyBehavior;
}
public function performFly()
{
$this->flyBehavior->fly();
}
/**
* 展示
*/
abstract public function display();
}
好了,现在上全部代码:
Duck.php
<?php
namespace App\StrategyPattern;
abstract class Duck
{
protected $flyBehavior;
protected $quackBehavior;
/**
* Duck constructor.
* @param $flyBehavior
* @param $quackBehavior
*/
public function __construct(FlyBehavior $flyBehavior, QuackBehavior $quackBehavior)
{
$this->flyBehavior = $flyBehavior;
$this->quackBehavior = $quackBehavior;
}
/**
* @param mixed $flyBehavior
*/
public function setFlyBehavior($flyBehavior)
{
$this->flyBehavior = $flyBehavior;
}
/**
* @param mixed $quackBehavior
*/
public function setQuackBehavior($quackBehavior)
{
$this->quackBehavior = $quackBehavior;
}
public function performFly()
{
$this->flyBehavior->fly();
}
public function performQuack()
{
$this->quackBehavior->quack();
}
public function swim()
{
echo '我是一只鸭子,所以我肯定是会游泳的,没有例外哦!';
}
/**
* 展示
*/
abstract public function display();
}
FlyBehavior.php
<?php
namespace App\StrategyPattern;
interface FlyBehavior
{
public function fly();
}
FlyNoWay.php
<?php
namespace App\StrategyPattern;
class FlyNoWay implements FlyBehavior
{
public function fly()
{
echo '我是不会飞的鸭子哦';
}
}
FlyWithWings.php
<?php
namespace App\StrategyPattern;
class FlyWithWings implements FlyBehavior
{
public function fly()
{
echo '我是会飞的鸭子哦';
}
}
QuackBehavior.php
<?php
namespace App\StrategyPattern;
interface QuackBehavior
{
public function quack();
}
Quack.php
<?php
namespace App\StrategyPattern;
class Quack implements QuackBehavior
{
public function quack()
{
echo '呱呱叫';
}
}
Squeak.php
<?php
namespace App\StrategyPattern;
class Squeak implements QuackBehavior
{
public function quack()
{
echo '吱吱叫';
}
}
RedDuck.php
<?php
namespace App\StrategyPattern;
class RedDuck extends Duck
{
public function display()
{
echo '我是一只红头呀,哇哈哈';
}
}
具体调用示例如下:
<?php
use App\StrategyPattern\RedDuck;
use App\StrategyPattern\FlyWithWings;
use App\StrategyPattern\Squeak;
$duck = new RedDuck(new FlyWithWings(),new Squeak());
$duck->performQuack();
$duck->performFly();
$duck->display();
终端调用输出:
☁ learn_design_pattern php strategy.php
吱吱叫我是会飞的鸭子哦我是一只红头呀,哇哈哈
现在给出策略模式的官方定义:
策略模式
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
算法族,即是刚才各自定义的行为接口(FlyBehavior.php和QuackBehavior.php)。使用算法的客户即是Duck类。
请大家再次理解 针对接口编程 的真正意义,是在客户中定义了算法族行为属性,然后通过属性调用算法族的方法。
声明:
设计模式学习我选用的教材是《Head First 设计模式》,此篇讲解代码也是出自该书。写这篇文章的目的也是为了自己做个笔记,同时可以和大家一起交流。在写作的过程中,我发现设计模式要讲透彻真的非常困难,里面包含了太多的设计思想。所以后续我尽量做到自己理解的非常非常透彻以后才会发到简书上和大家一起交流。
以上如有错误的地方请及时指出,谢谢。