深入PHP面向对象、模式与实践——对象与设计

设计基础

  • 代码设计的定义

对“代码设计”的理解涉及系统的定义:确定系统的需求、作用域及目标。从底层上看,设计是定义系统组成并组织各组件间关系的过程。本章从另一个角度考虑:类和对象的定义和配置。

面向对象的系统由一系列的类组成。决定系统中这些类的角色是非常重要的,而类由方法组成,所以在定义类时,必须决定哪些方法应该放在一起。另外,类与类之间常常通过继承关系联系起来以便遵循公用的接口。因此,这些接口或类型应该是设计系统时首先要考虑的。

  • 面向对象设计和过程式编程

面向对象和过程式编程一个核心的区别是如何分配职责。过程式编程表现为一系列命令和方法的连续调用。控制代码根据不同的条件执行不同的职责。这种自顶向下的控制方式导致了重复和相互依赖遍布于整个项目。面向对象编程则将职责从客户端代码移专门的对象中,尽量减少互相依赖。

  • 职责
    过程式代码忙于处理细节,而面向对象只需要一个接口即可工作,并且不用考虑实现细节。由于实现有对象负责,而不是由客户端代码负责,所以我们能很方便地增加对新格式的支持。
  • 内聚
    内聚(cohesion)是一个模块内部各成分之间相关联程度的度量。理想情况下,你应该使各组件职责清晰、分工明确。如果代码间的关联范围太广,维护就会很困难——因为你需要修改某部分代码的同时修改相关的代码。
  • 耦合
    当系统各部分代码紧密绑在一起时,就会产生紧密的耦合,这时在一个组件中的变化会迫使其他组件随之改变。
  • 正交
    正交(orthogonality)指将职责相关的组件紧紧组合在一起,而与外部系统环境隔开,保持独立。正交主张重用组件,期望不需要任何特殊配置就能把一个组件插入到新系统中。这样的组件有明确的与环境无关的输入与输出。正交代码使修改变得更加简单,因为修改一个实现只会影响到被改动的组件本身。最后,正交代码更加安全。bug的影响只局限于它的作用域中。内部高度互相依赖的代码发生错误时,很容易在系统中引起连锁反应。

我们如何才能在代码中达到一个平衡?通常,首先考虑哪些代码应该存在于系统中。

  • 选择类

我们应该如何定义类呢?最好的办法是让一个类只有一个主要的职责,并且任务要尽可能独立。你可以把类的职责用多个词来形容,最好不超过25个词,不要用到“且”或“或”。如果句子太长或者有复杂的子句,就应该考虑用你所描述的一部分任务来定义新类。

  • 多态

多态是指在一个公用接口后面维护多个实现。如果代码中存在大量的条件语句,就说明需要使用多态。要注意多态并没有消除条件语句,但多态可以把条件代码集中到一个地方。PHP强制接口由抽象类定义, 这非常有用,因为我们可以确定子类将会实现抽象父类中定义的所有方法,包括类类型提示和方法的访问控制。客户端代码因此可以使用一个公共父类的任意子类而不需要修改代码(只要客户端代码仅依赖与父类中定义的功能)。

这个规则唯一的缺憾是:无法强制规定类方法返回的数据类型,这意味着不同子类的方法可能返回不同类型的对象或基本数据类型,这会损害类型的互换性。你应该尝试使返回值保持一致。你可以在项目中靠人为的约定来使多个方法保持一致。你可以在源代码中使用注释来标明方法的返回值类型。

  • 封装

简单地说,封装就是对客户端代码隐藏数据和功能。

要实现封装,最简单的方法是将属性定义为private或protected。通过对客户端代码隐藏属性,我们创建了一个接口并防止在偶然情况下污染对象中的数据。

多态是另外一种封装。通过把不同的实现放在公共接口之后,我们对客户端代码隐藏了功能的实现。

  • 忘记细节

在设计阶段,让大脑空白一段时间会给你带来意想不到的好处。请清空脑海中这些和细节相关的念头。你可以只考虑系统中的关键参与者:项目需要的对象类型和这些对象的接口。除此之外,请忽略具体的实现细节,而要让代码中的结构和关系来引导你,你会发现在一个定义良好的接口之后加入你的实现代码是很容易的。接着你可以灵活地选中、改进或货站一个可能需要的实现,而不会影响到外界的系统。

为了强调接口,我们按抽象基类而不是具体的子类来思考。用这种方法,我们一开始就在系统中使用了多态和封装。这个结构也具有类切换的能力。

“为接口而不是实现而编程”。

  • 4个方向标

本节挑选出4个说明代码需要检查的“路标”。

  • 代码重复
    认真查看系统中代码重复的地方,很可能他们不应该放在一起。重复通常意味着紧密耦合。
  • 类知道的太多
    使用全局变量可以让所有的方法都能获得数据,但使用时一定要谨慎考虑。使用全局变量或者允许一个类知道它之外的领域的内容,你就会把这个类绑定到外部环境中,让它很难重用,并无法保持独立。
  • 万能的类
    如果类尝试一次性完成过多工作,那么把一些功能提取出来,称为一个基类。如果类的职责过多,那么在创建子类的时候就会产生问题,可能产生太多的子类或者过度依赖于条件语句。
  • 条件语句
    如果您发现在一个类中频繁地进行特定条件的判断,特别是当你发现这些条件判断在多个方法中出现时,就说明这个类需要拆分成两个或更多。拆分出来的几个类应该有一个共享的抽象基类,这时你需要知道如何传递正确的子类给客户端代码。

  • UML

UML是Unified Modeling Language(统一建模语言)的缩写,注意说这个词的时候要使用定冠词the。

  • 类图
    类图(class diagram)这是UML的一部分,但它们可能是最常用的。类常用带类名的方框来描述。类被分为三部分,最先显示的是类名,下面两部分可选,用于显示类名之外的信息。通常用斜体类名或增加{abstract}来表示抽象类。
  • 属性
    属性直接列在类名下面的格子中,属性前面的符号表示属性的可见性的级别或者访问控制。冒号用于分隔属性名和它的类型及默认值(默认值为可选项)。
  • 操作
    操作和属性使用了相似的语法,列表参数包含在括号之中。方法如果有返回类型的话,用冒号来描述。参数用逗号来分隔,并遵守属性语法,参数名和它的数据类型间用冒号分隔。
  • 描述继承和实现
    UML一般用“泛化”来描述继承关系。这个关系用从子类到父类的一条线来标识,线的顶端有一个空心闭合箭头。UML用“实现”来描述接口和实现接口的类之间的关系,用虚线标识。
  • 关联
    当一个类的属性保存了对另一个类的一个实例(或多个实例)的引用时,就产生了关联。
  • 聚合和组合
    聚合和组合都描述了一个类长期持有其他类的一个或多个实例的情况。在聚合情况下,被包含对象是容器的一个核心部分,但是它们也可以同时被其他对象所包含。聚合关系用一条以空心菱形开头的线来说明。
    组合则是一个更强的关系。在组合中,被包含对象只能被它的容器所引用。当容器被删除时,它也应该被删除。组合关系和聚合关系的描述类似,但是菱形必须是实心的。
  • 描述使用
    一个对象使用另一个对象的关系在UML中被描述为一个依赖关系。一个被使用的类可以作为类方法的参数传递或者作为方法调用的结果得到,使用这种关系由一条连接两个虚线和开放箭头表示。
  • 使用注解
    类图可以捕捉到系统的结构,但类图并不能解释类处理任务的过程。注解由一个折角方框组成,它通常包含伪代码片段。
    这里写图片描述
  • 时序图
    时序图是基于对象而不是基于类的,它用于为系统中过程化的行为建模。
    这里写图片描述
    垂直的虚线是生命线,展示了系统中对象的生命周期。箭头表示消息从一个对象传递到另一个对象,UML在这里提供了一些语法,例如方括号说明一个条件,星号用于表示一个重复的操作。你还可以在方括号中进一步说明。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值