设计模式初探

前言

设计模式作为一个程序员,相信大家肯定不会陌生,它是一些成熟的且通用的程序设计解决方案,针对这些肯定会存在一些理论基础,来为这些这些模式提供理论依据,这里我们就要先搞明白这些理论到底是什么,这样我们对设计模式有事半功倍的效果。

设计模式的基本原则:
1. 单一职责原则
2. 开闭原则
3. 里式替换原则
4. 依赖倒转原则
5. 接口隔离原则
6. 合成复用原则
7. 迪米特法则

单一职责原则

定义:规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。
作用:它用于控制类的粒度大小
这个原则很好理解,我们的类在做某些事情的时候只专注与自己领域内的事儿就可以了,譬如我们的模型类就只针对特定模型进行操作,而不会去关心操作类里面的逻辑,这样单一的职责隔离,可以方便我们维护。
举个理想化例子:
现实生活中,我们的摄影师是什么都干的,布景、服装、灯光、拍照,可以说是累成狗。
但是在程序设计的世界里面,我们更加希望的是这样:

  • 我们的摄影师主要负责就是控制相机,指挥助手。
  • 指导助手布景,而具体的布景、服装和灯光布置可以交给我们助手。

我们用代码模拟下现实生活:

<?php
declare(strict_types=1);
namespace  Neilyoz\DesignPatternBase\SingleResponsibility;
class  RealPhotographer
{
    private  string  $name;
    public  function  __construct(string  $name)
    {
        $this->name  =  $name;
    }

    // 沟通
    public  function  communicate():  void
    {
        echo  $this->name  .  ' 在沟通'  .  PHP_EOL;
    }

    // 布置场景
    public  function  layout()
    {
        echo  $this->name  .  ' 在布置场景'  .  PHP_EOL;
    }

    // 搭配服装
    public  function  matchingClothing()
    {
        echo  $this->name  .  ' 搭配衣服'  .  PHP_EOL;
    }

    // 调整灯光
    public  function  adjustTheLights()
    {
        echo  $this->name  .  ' 调整灯光'  .  PHP_EOL;
    }

    // 控制相机
    public  function  controlCamera()
    {
        echo  $this->name  .  ' 控制相机'  .  PHP_EOL;
    }
}

我们可以看到一个摄影师负责了方方面面,俗话就是管的太宽了,我们要缩小粒度,让我们的摄影师只是专注拍照的本质,所以我们可以把 布置场景搭配服装调整灯光 交给助手去完成。我们来修改下这个方法,让特定的事儿给专业的人去完成。

我们修改下这个类:

<?php
declare(strict_types=1);
namespace  Neilyoz\DesignPatternBase\SingleResponsibility;

// 摄影师类
class  Photographer
{
    private  string  $name;
    public  function  __construct(string  $name)
    {
        $this->name  =  $name;
    }

    // 控制相机
    public  function  controlCamera()
    {
        echo  $this->name  .  " 操作控制相机拍照"  .  PHP_EOL;
    }

    // 指挥助手
    public  function  commandAssistant(Helper  $helper)
    {
        $helper->receivedCommand();
        $helper->matchingClothing();
        $helper->adjustTheLights();
        $helper->layout();
    }
}

// 助手类
class  Helper
{
    private  string  $name;
    public  function  __construct(string  $name)
    {
        $this->name  =  $name;
    }

    public  function  receivedCommand()
    {
        echo  $this->name  .  '收到指挥'  .  PHP_EOL;
    }

    // 布置场景
    public  function  layout()
    {
        echo  $this->name  .  ' 在布置场景'  .  PHP_EOL;
    }

    // 搭配服装
    public  function  matchingClothing()
    {
        echo  $this->name  .  ' 搭配衣服'  .  PHP_EOL;
    }

    // 调整灯光
    public  function  adjustTheLights()
    {
        echo  $this->name  .  ' 调整灯光'  .  PHP_EOL;
    }
}

这样我们就把职责更加明确的分配了,其实程序员有时候更像是一个管理者的觉得,我们需要管理具体的类去做具体的事儿。在管理这些类的时候,我们要合理的划分这些类的职责,否则职责到后面越来越混乱,反而影响我们的管理。所以单一职责让我们能更好的控制类的粒度。

开闭原则

定义:一个软件实体应当对扩展开放,对修改关闭。
我们的软件随着时间推移是会发生一些变化的,但是已有的代码已经是稳定运行的,我们不应该去修改这些成熟的代码扩展他们的功能,除非逼不得已。所以这就考验到我们的设计水平了。
我们还是看看下面这个场景:

  • 摄影师工作时不只是只用一个牌子的相机,不同的厂商的相机,有不同的效果

我们来看看我们通常专注于实现的代码是什么样子的:

<?php
declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\OpenAndClose;

class  Photographer
{
    private  string  $name;
    public  function  __construct(string  $name)
    {
        $this->name  =  $name;
    }

    public  function  photograph(string  $camera)
    {
        switch ($camera) {
            case  '佳能':
                echo  $this->name  .  ' 使用佳能拍'  .  PHP_EOL;
                break;
            case  '尼康':
                echo  $this->name  .  ' 使用尼康拍'  .  PHP_EOL;
                break;
            case  '索尼':
                echo  $this->name  .  ' 使用索尼拍'  .  PHP_EOL;
                break;
            default:
                echo  $this->name  .  ' 使用手机拍'  .  PHP_EOL;
                break;
        }
    }
}

这里代码看着是实现了我们的需求,但是如果客户要求用 哈苏宾得富士拍照呢?你是不是要去修改 photograph 这个方法。这样就违背了我们的开闭原则。那我们要怎么才能不修改代码的情况下,去完成我们的进击的需求呢?我们可以这样改:

<?php
declare(strict_types=1);
namespace  Neilyoz\DesignPatternBase\OpenAndClose;
interface  Camera
{
    function  photograph():  string;
}

class  CannonCamera  implements  Camera
{
    public  function  photograph():  string
    {
        return  ' 使用佳能拍'  .  PHP_EOL;
    }
}

class  NikonCamera  implements  Camera
{
    public  function  photograph():  string
    {
        return  ' 使用尼康拍'  .  PHP_EOL;
    }
}

class  SonyCamera  implements  Camera
{
    public  function  photograph():  string
    {
        return  ' 使用索尼拍'  .  PHP_EOL;
    }
}

class  FujiCamera  implements  Camera
{
    public  function  photograph():  string
    {
        return  ' 使用富士拍'  .  PHP_EOL;
    }
}

class  Photographer
{
    private  string  $name;

    public  function  __construct(string  $name)
    {
        $this->name  =  $name;
    }

    public  function  photograph(Camera  $camera):  void
    {
        echo  $this->name  .  $camera->photograph();
    }
}

$photographer  =  new  Photographer("鱼不浪");
$photographer->photograph(new  CannonCamera());
$photographer->photograph(new  NikonCamera());
$photographer->photograph(new  SonyCamera());
$photographer->photograph(new  FujiCamera());

我们可以使用接口把相机的拍照功能抽象出来,这样即使是有新的相机进来,我们无非就是实现这个接口就能达到扩展的目的,而不需要去修改我们的现有代码。

里式替换原则

定义:所有引用基类的地方必须透明地使用其子类的对象。
使用里式替换原则时需要注意如下:

  • 子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
  • 我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

其实还是一个抽象的概念,我们还是拿摄影师来说:

  • 摄影师拿相机,至于什么牌子的相机我们不管,我们只是抽象相机这个概念
<?php
declare(strict_types=1);
namespace  Neilyoz\DesignPatternBase\Liskv;

abstract  class  Camera
{
    public  function  open()
    {
        echo  "相机开机"  .  PHP_EOL;
    }

    public  abstract  function  screen():  void;
}

class  CannonCamera  extends  Camera
{
    public  function  screen():  void
    {
        echo  "佳能拍照"  .  PHP_EOL;
    }
}

class  NikonCamera  extends  Camera
{
    public  function  screen():  void
    {
        echo  "尼康拍照"  .  PHP_EOL;
    }
}

class  Photographer
{
    /**
    * 这里参数是父类,我们可以传入子类
    * @param  Camera $camera
    */
    public  function  screen(Camera  $camera)
    {
        $camera->open();
        $camera->screen();
    }
}

$photographer  =  new  Photographer();
$photographer->screen(new  CannonCamera());
$photographer->screen(new  NikonCamera());

依赖倒转原则

定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

这个我们在开闭原则中已经给出了实例,我们就是针对上层抽象进行的编程。

我们来看看常用的三种注入方式:

  • 构造注入
  • 设值注入
  • 接口传递注入

我们挨个看,为了省事儿我就只用一个类来演示:

<?php
declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\Dependency;

interface  Camera
{
    public  function  open():  void;
    public  function  screen():  void;
}

// 实现相机接口

class  CannonCamera  implements  Camera
{
    public  function  open():  void
    {
        echo  "打开佳能相机"  .  PHP_EOL;
    }

    public  function  screen():  void
    {
        echo  "佳能相机拍照"  .  PHP_EOL;
    }
}

// 实现相机接口
class  NikonCamera  implements  Camera
{
    public  function  open():  void
    {
        echo  "打开尼康相机"  .  PHP_EOL;
    }

    public  function  screen():  void
    {
        echo  "尼康相机拍照"  .  PHP_EOL;
    }
}

class  Photographer
{
    private  Camera  $camera;

    // 使用构造注入
    public  function  __construct(Camera  $camera)
    {
        $this->camera  =  $camera;
    }

    /**
    * 使用设值注入
    * @param  Camera $camera
    */
    public  function  setCamera(Camera  $camera):  void
    {
        $this->camera  =  $camera;
    }

    /**
    * 使用接口传递注入
    * @param  Camera $camera
    */
    public  function  open(Camera  $camera):  void
    {
        $camera->open();
    }

    public  function  screen()
    {
        $this->open($this->camera);
        $this->camera->screen();
    }
}

// 我们摄影师本来有自己佳能相机
$photographer  =  new  Photographer(new  CannonCamera());

// 并用它进行拍照
$photographer->screen();

// 朋友带着尼康相机来了,他拿朋友的尼康相机来玩儿
$photographer->setCamera(new  NikonCamera());
$photographer->screen();

接口隔离原则

定义:

  • 客户端不应该依赖它不需要的接口。
  • 类间的依赖关系应该建立在最小的接口上。

我们通过上面的例子,发现接口真的是一个好东西,可以让我们解耦很多我们的程序。

还是摄影师的例子,不同摄影师有不同的行为,我们不排除有的摄影师啥都 OK,有的摄影师也缺乏一些能力。

  • 摄影师可以拍摄表达作品想法
  • 摄影师也要和客户沟通,但有的摄影师只是工具人,不需要沟通
  • 有的摄影师自己重洗胶片,有的直接用数码

对于这些接口我们的摄影师不用都实现,实现自己需要的接口就好了。


<?php
declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\InterfaceSegregation;

interface  Screen
{
    public  function  screen();
}

interface  Communicate
{
    public  function  communicate();
}

interface  RinseTheFilm
{
    public  function  rinseTheFilm();
}

class  PhotographerOne  implements  Screen, Communicate
{
    public  function  screen()
    {
        echo  "PhotographerOne 拍照"  .  PHP_EOL;
    }

    public  function  communicate()
    {
        echo  "PhotographerOne 沟通"  .  PHP_EOL;
    }
}

class  PhotographerTwo  implements  Screen, Communicate, RinseTheFilm
{
    public  function  screen()
    {
        echo  "PhotographerTwo 拍照"  .  PHP_EOL;
    }

    public  function  communicate()
    {
        echo  "PhotographerTwo 沟通"  .  PHP_EOL;
    }

    public  function  rinseTheFilm()
    {
        echo  "PhotographerTwo 冲洗照片了"  .  PHP_EOL;
    }
}

合成复用原则

定义:尽量使用合成、聚合的方式,而不是使用继承

这里有很多种情况,还是用代码来举例说明,我们在有时候在开发中,有时候类可能会用到别的类的方法,我们可以有继承,依赖,聚合,组合的关系。继承会导致我们的程序耦合度过高,所以我们会选择另外的三种方式:

  • 依赖
<?php

declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\Composite;

class  CompositeParent
{
    public  function  method1()
    {
        echo  "方法1"  .  PHP_EOL;
    }

    public  function  method2()
    {
        echo  "方法2"  .  PHP_EOL;
    }

    public  function  method3()
    {
        echo  "方法3"  .  PHP_EOL;
    }
}

class  CompositeChild
{
    public  function  method1(CompositeParent  $compositeParent)
    {
        $compositeParent->method1();
    }
}
  • 聚合

<?php
declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\Composite;

class  CompositeParent
{
    public  function  method1()
    {
        echo  "方法1"  .  PHP_EOL;
    }

    public  function  method2()
    {
        echo  "方法2"  .  PHP_EOL;
    }

    public  function  method3()
    {
        echo  "方法3"  .  PHP_EOL;
    }
}

class  CompositeChild
{
    private  CompositeParent  $compositeParent;
    public  function  setCompositeParent(CompositeParent  $compositeParent):  void
    {
        $this->compositeParent  =  $compositeParent;
    }
}
  • 组合
<?php

declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\Composite;

class  CompositeParent
{
    public  function  method1()
    {
        echo  "方法1"  .  PHP_EOL;
    }

    public  function  method2()
    {
        echo  "方法2"  .  PHP_EOL;
    }

    public  function  method3()
    {
        echo  "方法3"  .  PHP_EOL;
    }
}

class  CompositeChild
{
    private  CompositeParent  $compositeParent;
    public  function  __construct()
    {
        $this->compositeParent  =  new  CompositeParent();
    }
}

迪米特法则

定义:

  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大
  • 一个类对自己依赖的类知道的越少越好,对于被依赖的类不管多复杂,都尽量将逻辑封装在内部。对外部除了提供 public 方法,不要透露任何信息
  • 只与直接的朋友通信
  • 直接朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、聚合等。其中我们称出现成员变量,方法参数,方法返回值中的类为直接朋友,而局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
<?php
declare(strict_types=1);

namespace  Neilyoz\DesignPatternBase\LawOfDemeter;

class  Student
{
}

class  Police
{
}

class  LawOfDemeter
{
    // 直接朋友
    private  Student  $student;
    public  function  getStudent():  Student
    {
        return  $this->student;
    }

    public  function  setStudent(Student  $student):  void
    {
        $this->student  =  $student;
    }

    public  function  doSomething()
    {
        // 非直接朋友
        $police  =  new  Police();
    }
}

总结

根据这些基本的原则去理解设计模式,感觉不会太困难,当然会 UML 类图就最好了。

原文链接

本作品采用《CC 协议》,转载必须注明作者和本文链接
Neilyozの鱼不浪
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!