2

设计模式

设计模式基本原则

  • 设计原则 ① : 按接口而不是按实现来编程

按接口而不是按实现编程是指,要将变量设置为一个抽象类或接口数据类型的实例,
而不是一个具体实现的实例。这样可以将设计与实现解耦合。
有些语言变量声明包含数据类型,例如在一个强类型语言中可以有以下声明:

<?php
    Interface IAlpha    //接口名
    class AlphaA implements IAlpha        //AlphaA的数据类型为IAlpha
    Variable useAlpha    //声明类型为IAlpha,例如(IAlpha useAlpha)
    useAlpha    //实例化新的AlphaA()
?>

如果没有强类型机制,可以利用代码提示保证按接口编程,以上一节写过的一段代码为例

<?
//useProduct.php
class useProduct{
    public function __construct(){
        $apple = new FruitStore();
        $book = new BookStore();
        $this->doInterface($apple);
        $this->doInterface($book);
    }
    function doInterface(IProduct $product){
    //这里的IProduct $product就是一种类型提示,方法类型提示为接口IProduct
    //无论程序变得多复杂都没关系,只要保证接口
    //就可以做出任意的修改和增补,不会破坏程序的其它部分
        echo $product->apples();
        echo $product->books();
    }
}
$worker = new useProduct();
?>
  • 设计原则 ② : 应当优先选择对象组合而不是类继承

有的OOP程序猿认为对象重用就等同于继承,一个类可以有大量属性和方法,扩展这个类就可以重用所有那些对象元素,而不用重新编写代码。可以扩展类,再增加必要的新属性和方法。下面将举一个例子说明对象组合和类继承之间的区别。

首先是继承的代码

<?php
//Domath.php
class DoMath{
    private $sum;
    private $quotient;
    public function simpleAdd($first,$second){
        $this->sum = ( $first + $second );
        return $this->sum;
    }
    public function simpleDivide($divided, $divisor){
        $this->quotient = ( $dividend / $divisor);
        return $this->quotient;
    }
}
?>
<?php
//InheritMath.php
include_once('DoMath.php');
class InheritMath extends DoMath{
    private $textOut;
    private $fullFace;
    public function numToText($num){
        $this->textOut = (string)$num;
        return $this->textOut;
    }
    public function addFace($face,$msg){
        $this->fullFace = "<strong>" . $face . "</strong> : " .$msg;
        return $this->fullFace;
    }
}
?>
<?php
//ClientInherit
include_once('InheritMath.php');
class ClientInherit{
    private $added;
    private $divided;
    private $textNum;
    private $output;
    public function __construct(){
        $family = new InheritMath();
        $this->added = $family->simpleAdd(40,60);
        $this->divided = $family->simpleDivided($this->added,25);
        $this->textNum = $family->numToText($this->divided);
        $this->output = $family->addFace("Your results",$this->textNum);
        echo $this->output;
    }
}
$worker = new ClientInherit();
?>

下面是组合,Client类使用两个不同的类,分别包含两个方法,DoMath类等同于继承例子中的父类,所以首先是DoText类

<?php
//DoText.php
//与InheritMath类相似,事实也如此,不过他没有继承DoMath类
class DoText{
    private $textOut;
    private $fullFace;
    public function numToText($num){
        $this->textOut = (string)$num;
        return $this->textOut;
    }
    public function addFace($face,$msg){
         $this->fullFace = "<strong>" . $face . "</strong> : " .$msg;
        return $this->fullFace;
    }
}
?>
<?php
//ClientCompost.php
include_once('DoMath.php');
include_once('DoText.php');
class ClientCompose{
    private $added;
    private $divided;
    private $textNum;
    private $output;
    public function __construct{
        $useMath = new DoMath();
        $useText = new DoText();
        $this->added = $useMath->simpleAdd(40,60);
        $this->divided = $useMath->simpleDivide($this->added,25);
        $this->textNum = $useText->numToText($this->divided);
        $this->output = $useText->addFace("Your results",$this->textNum);
        echo $this->output;
    }
}
?>

Client类必须包含多个类,看起来更胜一筹,不过在较大的程序中,组合可以避免维护多个继承层次上的各个子类,而且还可以避免可能导致的错误。例如父类的一个改变会逐级向下传递给子类实现,这可能会影响子类使用的某个算法。
要避免使用继承形成一长串子类、孙子类、曾孙子类,设计模式方法建议使用浅继承。

设计模式的组织

设计模式是按照作用和范围来组织的

设计模式按作用可以分为以下3类

  • 创建型

顾名思义,就是用来创建对象的模式,更确切地讲,这些模式是对实例化过程的抽象
如果程序越来越以来组合,就会减少对硬编码实例化的依赖,而更多地以来于一组灵
活的行为,这些行为可以组织到一个更为复杂的集合中,创建型模式提供了一些方法
来封装系统使用的具体类的有关知识,还可以隐藏实例创建和组合的相关信息

  • 结构型

这些模式所关心的是组合结构应当保证结构化。结构型类模式采用继承来组合接口或实现结构型对象模式则描述了组合对象来建立新功能的方法。了解结构型模式对于理解和使用相互关联的类(作为设计模式中的参与者)很有帮助

  • 行为型

到目前为止,绝大多数模式都是行为型对象,这些模式的核心是算法和对象之间职责的分配。这些设计模式描述的不只是对象或类的模式,它们还描述了类和对象之间的通信模式

设计模式按范围可以分为以下2类


  • 在两类范围中,第一类范围是类。这些类模式的重点在于类及其子类之间的关系,类模式中的关系是通过继承建成

  • 对象
    尽管大多数设计模式都属于对象范围,不过与类范围中的那些模式一样,很多模式也会使用继承。对象设计模式与类模式的区别在于,对象模式强调的是可以在运行时改变的对象,因此这些模式更具动态性。

设计模式作用、范围和变化

clipboard.png

设计模式与框架有什么不同

与框架相比,设计模式是体系结构中更小的元素,也更为抽象,另外设计模式没有框架那么
特定。因此,设计模式更可重用,也比框架灵活。
框架的有点与模板有些类似:它们更有指示性,可以更清楚地指示所解决问题的结构。为了
提供这种易用性,它们不得不放弃了体系结构的灵活性。如果使用框架,构建应用会快得多,
但是所构建的应用会受到框架本身的约束。框架可以包含面向对象结构,通常框架是分层的,
每一层处理更大设计中的一个方面。框架的一些特性在设计模式中也有体现,不过,设计模
式没有框架那么特定和具体,也没有那么庞大

UML


概念:UML(the Unified Modeling Language——统一建模语言),引入了一个强大的
图形化的语法来描述面向对象系统

类图

1.描述类

类是类图的主要部分,类用带有类名的方框来描述。

clipboard.png

上图中,图1最先显示的是类的名称,下面两部分是可选的,用于显示类名之外的信息。
可以发现图1已经足够描述一些类,并非总要在类图中显示每个属性和方法,甚至不需要
显示每个类

通常用斜体的类名(图2),或者增加{abstract}到类名下(图3)来表示该类是抽象
类。 第一种方法比第一种方法常用,但是当你做笔记时第二种方法更有用

接口定义的方式和类相同,但接口必须使用一个衍型(UML的一个扩展),如楼下所示

clipboard.png

2.属性

一般来说,属性用于描述一个类的属性,属性直接列在类名下面的格子中,如楼下所示
clipboard.png

属性前面的符号表示该属性可见性的级别或者是访问控制
clipboard.png

冒号用于分隔属性名和它的类型及默认值(默认值为可选项,可以不提供)
Again:只要提供必要的信息,不需要所有细节

3.操作

操作用于描述类方法,操作和属性使用了相似的语法

(1)可见性符号放在方法名之前
(2)参数列表包含在括号之中
(3)方法如果有返回类型的话,用冒号来描述
(4)参数用逗号来分隔,并且遵守属性语法
(5)参数名和它的数据类型间用冒号分隔

如下图所示

clipboard.png

4.描述继承和实现

继承关系用从子类到父类的一条线来表示,线的顶端有一个空心闭合箭头
clipboard.png

UML用"实现"来描述接口和实现接口的类之间的关系,如果ShopProduct类实现了Chargeable接口,就可以加入类图中,如图所示
clipboard.png

5.关联

一个类的属性保存了对另一个类的一个实例或多个实例的引用时,就产生了关联
下图中为辆各类建立模型,并创建类之间的关联,图中指出Teacher对象拥有一个或多个对Pupil对象的引用,或者Pupil对象拥有一个或多个对Teacher对象的引用
clipboard.png

我们可以用剪头来描述关联的方向,如果Teacher类拥有Pupil类的一个实例,但是Pupil类并没有Teacher类的实例,那我们可以让关联剪头从Teacher类指向Pupil类,称为"单向关联"
clipboard.png

如果两个类剪互相拥有对方的引用,可以用一个双向剪头来描述这种"双向关联"关系
clipboard.png

也可以在关联中指定一个类的实例被其它类引用的次数,可以通过把次数或者范围放在每个类旁边来说明。用星号(*)表示任意次数,如下图所示,Teacher对象拥有零个或多个Pupil对象
clipboard.png

6.聚合和组合

都描述了一个类长期持有其它类的一个或多个实例的情况,通过聚合和组合,被
引用的对象实例成为引用对象的一部分。
聚合关系用一条以空心菱形开头的线来说明,如下图所示,定义了两个类,
SchoolClass和Pupil, SchoolClass类聚合了Pupil
(意思就是学生组成了班级,如果我们要删除一个学校类,不需要同时删除)

clipboard.png

组合是一个更强的关系,在组合中,被包含对象只能被它的容器所引用,当容器删除
时,她应该也被删除,组合关系用类似聚合关系的方式描述,但它的菱形是实心的
下图中,Person类持有对SocialSecurityData对象的引用,而SocialSecurityData
对象实例属于包含它的Person对象

clipboard.png

7.描述使用

使用:即依赖关系,并非类之间的长久关系
下图中,Report类使用了ShopProductWriter对象,这种使用关系由一条连接两个类
的虚线和开放剪头表示。Report类没有把ShopProductWriter保存为类中的属性,
ShopProductWriter对象则将一组ShopProduct对象作为属性

clipboard.png

8.使用注解

注解:解释类处理任务的过程(类图只能捕捉系统结构)
如下图,Report对象使用了ShopProductWriter,但是不知道具体如何实现,
我们使用了注解来补充说明

clipboard.png

如图所示,注解由一个这叫的方框组成,通常包含伪代码片段

时序图

时序图是基于对象而不是基于类的,用于为系统中过程化的行为建模。

如下,为Report对象输出产品数据的过程建模,从左到右展现了系统的参与者
我们值使用类名来标记对象,如果图中有同一个类的多个对象实例在独立工作,
可以使用label:class(例如product1 : ShopProduct)格式来包含对象名

clipboard.png

下图我们从上到下展示了该过程每个对象的生命周期

clipboard.png

垂直的虚线是生命线,展示了系统中对象的生命周期,生命线上的矩形说明了过程中的
焦点,即某个对象的激活期。
显示对象间传递的消息,这个图才更容易被看懂,如下

clipboard.png

箭头表示消息从一个对象传递到另一个对象,返回值一般不写。
每个消息都用相关的方法调用来标记,例如方括号说明一个条件,像
{okToPrint}
write()
只有在一定条件下write才会被执行
星号用于表示一个重复的操作,可以在方括号中进一步说明
*{for each ShopProduct}
write()

该图含义:Report对象获得一个来自ProductStore对象的ShopProduct对象列表。
Report对象传递这个ShopProduct列表给一个ShopProcuctWriter对象,而
ShopProductWriter存放了对ShopProduct对象的引用(虽然我们只能从图中推断
出折点)ShopProductWriter对象为它引用的每个ShopProduct对象调用ShopProduct::
getSummaryLine(),并添加执行结果到最终的输出结果中



本文笔记参考书籍:
《深入PHP:面向对象、模式与实践》第4章
《Learning-PHP设计模式》第4章

下节:Chap3:创建型设计模式————工厂方法设计模式


R_Jeff
405 声望21 粉丝

坚持拍黄片的大四网页狗要成为PHPer