PHP常用的设计模式--工厂、单例模式

设计模式介绍

在软件工程中,设计模式(Design Pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。构建一个 web 应用或工程有无数种方式,同样,架构的方式也有无数种。但是我们建议遵循一些常见的模式,因为这会让你的代码更容易管理,可读性更高。

工厂模式

最常用的设计模式就是工厂模式。主要作用是用来实例化对象,而不需要客户了解这个对象属于哪个具体的子类

简单工厂模式【静态工厂方法模式】

简单工厂实例化的类具有相同的接口或者基类,在子类比较固定并不需要扩展时,可以使用简单工厂。简单工厂模式就是由一个工厂类根据传入的参数决定创建哪一种的产品类。
- 主要角色:
1. 抽象产品(Product)角色:具体产品对象共有的父类或接口

UML类图

<?php

/**
 *简单工厂模式是由一个工厂(注意是一个!)对象决定创建出哪一种产品类的实例(比如苹果。葡萄等等)
 *实现方式的实质:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例
 * 农场里有三种水果 苹果、葡萄
 * 我们设想:1、水果有多种属性,每个属性都有不同,但是,他们有共同的地方 |  生长、种植、收货、吃
 *          2、将来有可能会增加新的水果、我们需要定义一个接口来规范他们必须实现的方法
 *          3、我们需要获取某个水果的类,要从农场主那里去获取某个水果的实例,来知道如何生长、种植、收货、吃
 */


/**
 * 农场接口,供每个水果继承
 */
interface fruit{

    /**
     * 生长
     */
    public function grow();

    /**
     * 种植
     */
    public function plant();

    /**
     * 收获
     */
    public function harvest();

    /**
     * 吃
     */
    public function eat();

}

/**
 * 定义具体产品类 苹果
 * 首先,我们要实现所继承的接口所定义的方法
 * 然后定义苹果所特有的属性,以及方法
 */
class apple implements fruit{

    //苹果树有年龄
    private $treeAge;

    //苹果有颜色
    private $color;

    public function grow(){
        echo "grape grow";
    }

    public function plant(){
        echo "grape plant";
    }

    public function harvest(){
        echo "grape harvest";
    }

    public function eat(){
        echo "grape eat";
    }

    //取苹果树的年龄
    public function getTreeAge(){
        return $this->treeAge;
    }

    //设置苹果树的年龄
    public function setTreeAge($age){
        $this->treeAge = $age;
        return trie;
    }

}
/**
 * 定义具体产品类 葡萄
 * 首先,我们要实现所继承的接口所定义的方法
 * 然后定义葡萄所特有的属性,以及方法
 */
class grape implements fruit{


    //葡萄是否有籽
    private $seedLess;

    public function grow(){
        echo "apple grow";
    }

    public function plant(){
        echo "apple plant";
    }

    public function harvest(){
        echo "apple harvest";
    }

    public function eat(){
        echo "apple eat";
    }

    //有无籽取值
    public function getSeedLess(){
        return $this->seedLess;
    }

    //设置有籽无籽
    public function setSeedLess($seed){
        $this->seedLess = $seed;
        return true;
    }
}
/**
 *农场工厂类,用来实例化具体的水果
 */
class farmer{
    //定义个静态工厂方法
    public static function factory($fruitName){
        if(class_exists($fruitName)){
            $fruit = new $fruitName();
            if($fruit instanceof fruit)
                return $fruit;
        }
        throw new InvalidArgumentException("Error no the fruit");
    }
}
/**
 * 获取水果实例化的方法
 */
try{
    $appleInstance = farmer::factory('apple');
    var_dump($appleInstance);
}catch(InvalidArgumentException $err){
    echo $err->getCode() . "_______" . $err->getMessage();
}

上面的代码用来一个工厂来创建apple对象。用这种方式创建对象有两个好处: 首先,如果你后续需要更改,重命名或替换apple 类,你只需要更改工厂类中的代码,而不是在每一个用到 apple 类的地方修改; 其次,如果创建对象的过程很复杂,你也只需要在工厂类中写,而不是在每个创建实例的地方重复地写。

工厂模式主要角色
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的farmer类。
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。如例中的fruit接口。
- 具体产品角色:工厂类所创建的对象就是此角色的实例。如例子中的grape,apple 类。

抽象工厂模式

抽象工厂模式为一组相关或相互依赖的对象创建提供接口,而无需指定其具体实现类。抽象工厂的客户端不关心如何创建这些对象,只关心如何将它们组合到一起。

  • UML类图:抽象工厂为每个产品(具体实现)定义了工厂方法,每个工厂方法封装了new操作符和具体类(指定平台的产品类),每个“平台”都是抽象工厂的派生类。
    image

  • 主要角色

    抽象工厂(Abstract Factory)角色:它声明一个创建抽象产品对象的接口。通常以接口或抽象类实现,所有的具体工厂类必须实现这个接口或继承这个类。

具体工厂(Concrete Factory)角色:实现创建产品对象的操作。客户端直接调用这个角色创建产品的实例。这个角色包含有选择合适的产品对象的逻辑。通常使用具体类实现。

抽象产品(Abstract Product)角色:声明一类产品的接口。它是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。

具体产品(Concrete Product)角色:实现抽象产品角色所定义的接口,定义一个将被相应的具体工厂创建的产品对象。其内部包含了应用程序的业务逻辑。

  • 代码实现
<?php
/**
 * 抽象工厂类
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2016/6/17
 * Time: 15:01
 */

namespace AbstractFactory;

/**
 * 抽象工厂类,抽象工厂类中可以定义要生产的产品
 * 生产猫,生产狗,每一个产品都包含各种规格
 */

interface AnimalFactory {
    public function createCat();
    public function createDog();
}

/**
 *然后我们可以从抽象工厂为每一种规格派生出具体工厂类
 * Class BlackAnimalFactory
 * @package AbstractFactory
 */
class BlackAnimalFactory implements AnimalFactory {
    function createCat(){
        return new BlackCat();
    }
    function createDog(){
        return new BlackDog();
    }
}

class WhiteAnimalFactory implements AnimalFactory {

    function createCat(){
        return new WhiteCat();
    }

    function createDog(){
        return new WhiteDog();
    }
}

//抽象产品
interface Cat {
    function Voice();
}

interface Dog {
    function Voice();
}

//具体产品
class BlackCat implements Cat {

    function Voice(){
        echo '黑猫喵喵……';
    }
}

class WhiteCat implements Cat {

    function Voice(){
        echo '白猫喵喵……';
    }
}

class BlackDog implements Dog {

    function Voice(){
        echo '黑狗汪汪……';
    }
}

class WhiteDog implements Dog {

    function Voice(){
        echo '白狗汪汪……';
    }
}

//客户端
class Client {

    public static function main() {
        self::run(new BlackAnimalFactory());
        self::run(new WhiteAnimalFactory());
    }

    public static function run(AnimalFactory $AnimalFactory){
        $cat = $AnimalFactory->createCat();
        $cat->Voice();

        $dog = $AnimalFactory->createDog();
        $dog->Voice();
    }
}
Client::main();
?>

抽象模式的优点
- 分离了具体的类
- 使增加或替换产品族变得容易
- 有利于产品的一致性

抽象模式的缺点
- 难以支持新种类的产品。这是因为AbstractFactory接口确定了可以被创建的产品集合。支持新各类的产品就需要扩展访工厂接口,从而导致AbstractFactory类及其所有子类的改变。
- 抽象工厂就是以一种倾斜的方式支持增加新的产品中,它为新产品族的增加提供了方便,而不能为新的产品等级结构的增加提供这样的方便。

单例模式

简单说来,单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个,同时这个类还必须提供一个访问该类的全局访问点(实质就是多个对象共享一块内存区域)。
image

<?php
/**
 * 单例模式相关类
 * Singleton类只能有一个实例(不能多)
 * Singleton类必须能够自行创建这个实例
 * 必须自行向整个系统提供这个实例,换句话说:多个对象共享一块内存区域,比如,对象A设置了某些属性值,则对象B,C也可以访问这些属性值(结尾的例子很好的说明了这个问题)
 */

class Singleton
{
    /**
     * @var Singleton reference to singleton instance
     */
    private static $instance;

    /**
     * 构造函数私有,不允许在外部实例化
     *
     */
    private function __construct()
    {
    }

    /**
     * 防止对象实例被克隆
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * 防止被反序列化
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}
$result=new Singleton();//Fatal error: Call to private Singleton::__construct() from invalid context in E:\src\trunk\jerrydemo\Singleton\Singleton.php on line 42

以上的Singleton类是无法从自身的类外部创建实例的,因为我们将构造函数设为了private,所以通过new Singleton()是无法从类外部使用私有的构造函数的,如果强制使用,将会报如下错误:
Fatal error: Call to private Singleton::__construct() from invalid context in E:\src\trunk\jerrydemo\Singleton\Singleton.php on line 42

按照已往的思维逻辑,实例化一个类都是直接在类外部使用new操作符的,但是既然这里讲构造函数设为private了,我们知道,私有的成员属性或函数只能在类的内部被访问,所以我们可以通过在类Singleton内部再创建一个静态方法(比如:getInstance()),而且必须是public的,getInstance()函数中主要进行的是实例化Singleton类

<?php

<?php

/**
 * 单例模式相关类
 * Singleton类只能有一个实例(不能多)
 * Singleton类必须能够自行创建这个实例
 * 必须自行向整个系统提供这个实例,换句话说:多个对象共享一块内存区域,比如,对象A设置了某些属性值,则对象B,C也可以访问这些属性值(结尾的例子很好的说明了这个问题)
 */

class Singleton
{
    /**
     * @var Singleton reference to singleton instance
     */
    private static $instance;

    /**
     * 通过延迟加载(用到时才加载)获取实例
     *
     * @return self
     */
    public static function getInstance()
    {
        if (null === static::$instance) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * 构造函数私有,不允许在外部实例化
     *
     */
    private function __construct()
    {
    }

    /**
     * 防止对象实例被克隆
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * 防止被反序列化
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}
$obj = Singleton::getInstance();
var_dump($obj === Singleton::getInstance());             // bool(true)


  • 构造函数 __construct() 被声明为 protected 是为了防止用 new 操作符在这个类之外创建新的实例。
  • 魔术方法 __clone() 被声明为 private 是为了防止用 clone 操作符克隆出新的实例.
  • 魔术方法 __wakeup() 被声明为 private 是为了防止通过全局函数 unserialize() 反序列化这个类的实例。

应该非常小心地使用单例模式,因为它非常自然地引入了全局状态到你的应用中,降低了可测试性。 在大多数情况下,依赖注入可以(并且应该)代替单例类。 使用依赖注入意味着我们不会在设计应用时引入不必要的耦合,因为对象使用共享的或全局的资源,不再需要耦合具体的类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值