C++ 大规模程序设计 之 层次化

层次化的主要目的还是为了设计出高质量的系统

 + 易于理解
 + 易于维护
 + 易于测试
 + 易于重用
 = 少加班

导致物理循环依赖的原因

产生物理循环依赖的原因是多种多样的,书中列举了3种,但总结一下可能2种更合适一些。

  • 心理方面:在对原有系统开发新功能时,或修改问题时,信手拈来的习惯可能在不经意间造成循环依赖。
  • 技术方面:设计系统逻辑关系时,逻辑上的耦合导致了物理上的循环依赖。

处理物理循环依赖的方式

升级

组件间支配: 如果组件y处于组件x的上层,并且y在物理上依赖于x,则称组件y支配组件x。

支配的重要性在于它能够提供除了层次号以外的其他的信息。

  • 一个高层组件增加了一个对底层组件的依赖不会产生循环依赖;
  • 同一层的两个组件间增加一个依赖会影响层次关系,但也不会产生循环依赖。

升级:如果一个高层组件和一个底层组件间有循环依赖,可以考虑把依赖的这部分功能转移到高层组件中,从而打破依赖关系。这种方式称为升级。

升级的两种思路:

将部分功能转移,或者构造一个上层模块。


降级

与升级相对应的便是降级,有些功能模块是对于整个系统比较公共的,考虑把它们放入底层组件中可以提高共享,复用的程度,还是值得尝试的。

降级很好理解,但有时在没有发生循环依赖时也可以考虑使用降级方式优化结构,比如当发现有部分代码重复时,就可以把重复部分封装起来,降级处理,来提高复用程度。

升级和降级并是相对的,并不是对立或互斥的,有时结合使用可以达到更好的效果。

关于降级,书中还有一些小的建议,可以参考

  • 将一个具体类分解为包含更高和更低层功能的类可以促进层次化
  • 将一个抽象基类分解为两个类 —- 抽象接口和实现可以促进层次化
  • 把一个系统分解成更小的组件即可以使它更灵活,也会使它更复杂。

不透明指针

不透明的定义:如果一个指针指向的类型定义不包含在当前的编译单元中,则说它是不透明的。

程序解释最简单:

Class A在不知道B为何物时直接使用了B,这个是经常用到的。

A.h

#ifndef __A_H__
#define __A_H__

class B;

class A
{
public:
    func(B* pObjB);
}
#endif

这时候A对于B产生的只是名称上的依赖。并没有物理上的依赖,没有头文件引用等。


哑数据

哑数据是对不透明指针的一种概括,它主要指一个对象拥有不知道如何解释的信息,这些数据一般会用在另一个对象的上下文中。或者说它是不透明指针的一种泛化。

一个简单的运动会的例子(比赛场地,运动员和比赛项目)

运动员需要知道在哪块场地进行比赛,比赛场地需要知道有多少运动员参赛,但真的有必要让运动员知道场地的长宽高,有多少照明灯等具体信息吗?场地其实也没有必要知道运动员的身高体重等具体信息。

这里就没有必要在运动员和场地间互相定义不透明指针。在比赛场地类中用一个数组保存运动员ID就完全可以搞定。同样,运动员也保留一个份比赛场地ID的List也可以找到相应的信息了。

这里把运动员和比赛场地类型的不透明指针泛化成ID的形式。这些ID单纯对于运动员和比赛场地是没有任何意义的,但放在上下文中它们便是不可缺少的信息。

泛化提供了解除依赖(名称和物理)的一种方式,同样它也没有办法提供更具体的信息和安全的类型转换了。


冗余

冗余比较好理解,有复用的地方一定会带来一定的耦合关系,当耦合带来的问题远远大于复用带来的好处时此时就要考虑一下是不是真的需要复用。

也许这时直接复制一份代码在当前的模块中比直接复用组件更简单和更容易维护。


回调

回调函数本身是C/C++的一种语法,这里的回调主要是在设计层面上的。它是指由客户提供回调函数的接口,然后由子系统在上下文中进行调用。这样子系统便可以控制客户组件的某些行为。

可以考虑一个简单的消息驱动回调函数例子,在两个组件间只是一种消息传递的关系,而并没有其他的耦合。


管理类

在设计类时功能单一的原则可以使类方便的复用,而且很容易维护,但同样也会带来一些问题,比如一个子系统使用了很多功能单一的类的时候,这时会产生很多的依赖关系,并不利于管理。

例如一个画图组件,我们可以画圆,画方,画线,此时完全可以定义一个管理类叫画图,它对内包含三种画图的类,对外提供接口,此时这个画图的管理类变成对外依赖的接口。

这是使用管理类解除依赖关系一种方式。另一个好处是管理类本身还有协调的功能,因为它的层次更高,可以看到更多的状态变化。


分解

分解的主要思想是提取小块高内聚的功能,封装并转移到下层组件中以便提供功能复用。

分解与降级类似,区别是分解不是必然的会消除循环,他只是减小参与循环中的数量。


升级封装

封装本身是面向对象中的一个概念,屏蔽实现细节,使用户不能访问。

在系统设计时让每个组件都暴露接口给客户编程使用,在一定程度上是一种便利,但有时也会带来混乱。

比较子系统 A和B

A中所有的组件都对外开发,提供接口,B中暴露在外的只有w,x,z。

当系统规模较大时,A中每个组件暴露的接口未必都能被高效的使用,甚至可能找都找不到。 B中的封装的方式也许能够提供一个相对清晰的结构。


总结:

关于书中提出的各个原则和设计方法,没有灵丹妙药。这里更多的思考是对于不同系统不同问题的折中的解决方法,趋利避害,使系统在当前的运行环境中更加的合理。脱离实际环境,实际问题很难讲哪种方法就是最好的,没有任何副作用的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-13 08:53:43

C++ 大规模程序设计 之 层次化的相关文章

C++ 大规模程序设计 之 物理层次结构

在系统中物理组件间必然会存在着各种各样的联系(DependOn 关系).良好的设计应该是接口短小,易于理解,易于使用和易于测试的.理清物理层次结构对于设计良好的系统是很有帮助的. 易测试性设计 质量设计的一个重要部分是易测试性设计,如果一个组件设计良好,那么用它构造测试用例也是比较容易的,同样它的易用性对于用户来说也是很好的,很多时候测试用例和用户使用的场景是一致的.这个和极限编程中测试驱动的想法是一致的.由使用倒逼设计. 规则 1:对整个系统的层次结构进行分布测试比只在上层测试要有效的多. 如

前端模块化

前端模块化 前端模块化 在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可:如今CPU.浏览器性能得到了极大的提升,很多页面逻辑迁移到了客 户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀 这时候JavaScript作为嵌入式的脚本语言的定位动摇了,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,JavaScript极其简单的代码组织规范不足以驾

nodejs(一)

nodejs第一章节 使用node来实现第一个http服务器 var http = require('http'); http.createServer(function (request, response) { // 发送 HTTP 头部 // HTTP 状态值: 200 : OK // 内容类型: text/plain response.writeHead(200, {'Content-Type': 'text/plain'}); console.log("我是yyk"); //

在Windows下用Eclipse+CDT+MinGW搭建C++开发平台

本文提供了在Windows下用Eclipse+CDT+MinGW搭建C / C++开发平台的方法, 测试平台为Windows XP Sp2 CHS. 以下软件均为Windows平台下的版本. 1.安装JDK,目的是为了Eclipse的运行.目前版本是jdk-1_5_0_06-windows-i586-p.exe ,下载地址http://java.sun.com/javase/downloads/index.jsp.仅安装JDK即可.假设安装路径为D:\java\JDK.配置系统环境变量(右键点击

【C++探索之旅】开宗明义+第一部分第一课:什么是C++?

内容简介 1.课程大纲 2.第一部分第一课:什么是C++? 3.第一部分第二课预告:C++编程的必要软件 开宗明义 亲爱的读者,您是否对C++感兴趣,但是C++看起来很难,或者别人对你说C++挺难的,你不知道如何入门? 别担心,这个系列教程就是为C++初学者准备的. 不可否认,C++是一门非常著名的语言.几乎学计算机的同学都难免要碰一下C++,中国的大学课堂一般也是先学C语言入门,然后就是Java或者C++. C++ 这门语言的使用很广泛,特别是在游戏开发领域,C++的性能和无限可能性使得其几乎

js模块化开发——前端模块化

在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可:如今CPU.浏览器性能得到了极大的提升,很多页面逻辑迁移到了客 户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀 这时候JavaScript作为嵌入式的脚本语言的定位动摇了,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码 模

《程序员修炼之道:从小工到专家》读后感

本书相对比较基础,不是那种大规模程序设计.高并发设计等等,主要是针对程序员的一些基本素质和一些基本常规编程设计做一些梳理和规范,对于初入职的程序员,养成这些良好素质是非常有必要的:对于已经入职多年的程序员,回顾一下本书,然后结合自身情况看看是否能够基本达到本书中的一些素养也是有必要的. 整体而言,有一定的经验性总结,相对来说比较基础,对开发者也有一定的作用:对个人而言,里面很多的素养.设计规范之类都有一定了解,不过可能平时做的不够彻底,因此看完之后,还是有一定的收获,至少有了这样的文档性的总结,

大型项目开发: 隔离 (《大规模C++程序设计》书摘)

书中第六章 隔离. 主要在撰述什么须要定义在头文件?什么应当移到编译单元中? 核心仍然是先区分接口定义与实现细节.实现细节的改变会导致客户代码的又一次编译,从逻辑上也表示与客户代码间可能存在着强耦合. 实现细节与隔离 主要考察下面实现细节.它们会在接口中引入实现细节.也是须要考虑进行隔离的内容: 继承 分层 简单的说就是类的成员中有还有一个类的实例时,如Foo mFoo. 这个类就会依赖于Foo的定义.而转为持有地址时,即将关系从HasA改为HoldA时,就不存在这个问题.也就是定义为Foo*

[连载]Java程序设计(04)---任务驱动方式:工资结算系统

任务:还是在上一家公司,该公司将职员分为三类:部门经理.技术员和销售员.在发工资的时候,部门经理拿固定月薪8000元,技术人员按每小时100元领取月薪,销售人员按照500元底薪加当月销售额的4%进行提成,设计并实现一个工资结算系统. 分析:不管是部门经理.技术员还是销售员都具有员工的共同特征,可以先设计一个员工类(Employee),并将结算工资的方法设计为抽象方法,因为不同的员工有不同的结算工资的方式,需要进行多态实现.所谓的抽象方法就是没有方法体并被abstract修饰符修饰的方法.如果一个