易栈 · 一盏

塞外秋来,衡阳雁去

设计模式:面向对象软件设计常见问题

《设计模式:可复用面向对象软件基础》里面提到怎么用设计模式来解决面向对象软件设计中常见的一些问题,把书中对这些问题的分析整理了一下:

一、寻找合适的对象,决定对象的粒度

面向对象设计中最困难的部分是将系统分解成对象组合。可以参考的方法有:

  • 写出问题描述,挑出名词和动词,创建相应类和操作。
  • 关注系统的协作和职责关系。
  • 对现实世界建模,再将分析时发现的对象转化到设计中。

二、指定对象接口

对象支持的操作集合称为该对象的接口(interface)类型(type)是用来标识特定接口的一个名字。所以如果两个对象有相同的类型,那么它们支持相同的接口。相同的接口在不同的对象中可能有不同的实现。

运行时刻才去绑定接口请求和它对应的实现操作,称为动态绑定(dynamic binding)。动态绑定允许你在运行时刻彼此替换有相同接口的对象,这种可替换性称为多态(polymorphism)

多态是面向对象系统中的核心概念之一。多态允许客户对象要求其他对象支持特定接口,这样简化了客户的定义,使对象间彼此独立

三、描述对象的实现

对象的实现由它的类(class)决定。类指定了对象的内部数据和表示,也定义了对象所能完成的操作。

注意区分对象的类(class)和对象的类型(type)!!对象的类同时定义了对象的接口和实现,但是对象的类型只与对象的接口有关。

相应地,类继承接口继承也是有区别的。很多语言并不显式区分这两个概念,所以容易被混淆。在C++中,继承既指接口继承又指实现的继承。C++中接口继承的标准方法是公有继承一个含(纯)虚成员函数的类。C++中纯接口继承接近宇公有继承纯抽象类,纯实现继承接近于私有继承。

多态通常依赖于通过继承而来具有相同接口(类型)对象族。只针对这些相同的接口来编程,有两个好处:

  • 客户无须知道所使用的对象的特定类型,只须对象有客户所期望的接口。
  • 客户无须知道所使用的对象的具体,只须知道定义接口的抽象类。

综上得出可复用面向对象设计的第一个重要原则

“针对接口编程,而不是针对实现编程。”

这个对应到C++实际编程中,就是不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。

四、运用复用机制

常用的复用机制有继承、组合、委托、参数化类型等。

1、继承和组合

面向对象系统中功能复用的两种最常用技术是类继承对象组合

类继承复用称为“白箱复用”,因为在继承方式中,父类的内部细节对子类可见。对象组合复用称为“黑箱复用”,因为对象内部的细节是不可见的,对象通过定义好的接口被使用。

类继承的优点在于类继承是程序设计语言直接支持的,可以直按使用,而且类继承可以方便的改变被复用的实现。类继承的缺点如下:

  • 继承在编译时刻就定义了,无法在运行时刻改变从父类继承的实现。
  • 继承关系中,子类和父类有紧密的依赖关系。父类通常至少定义了部分子类的具体实现,父类实现中的任何变化必然会导致所有子类发生变化。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。 —个可用的解决方法是只继承抽象类,因为抽象类通常提供较少的实现。
  • 继承破坏了父类的封装,因为继承对子类揭示了其父类的实现细节。

相比之下在对象组合中:因为对象只能够通过接口访问,所以不会破坏封装性;只要类型一致,运行时刻可以用一个对象代替另一个对象;由于对象的实现是基于接口,所以依赖性较小。

综上得出可复用面向对象设计的第二个重要原则

“优先使用对象组合,而不是类继承。”

2、委托

委托是一种组合方法,它使组合具有与继承相同的复用能力。在委托的方式下,接受请求的对象将操作委托给它的代理者处理。委托的主要优点在于可以在运行时刻替换代理者,缺点在于代理者对被代理者数据的访问权限问题。

3、参数化类型

参数化类型也就是C++中的模板(templates),通过这种方式,可以使不同类型的元素复用同一套处理代码,例如list、map容器等。

五、设计应支持变化

设计时应考虑允许系统结构中的哪部分变化,却又不会引起重新设计。

“封装变化”是许多设计模式的主题。

本文链接:易栈 - 设计模式:面向对象软件设计常见问题