软件工程2-11.构件级设计.ppt

上传人:本田雅阁 文档编号:3499345 上传时间:2019-09-04 格式:PPT 页数:78 大小:2.58MB
返回 下载 相关 举报
软件工程2-11.构件级设计.ppt_第1页
第1页 / 共78页
软件工程2-11.构件级设计.ppt_第2页
第2页 / 共78页
软件工程2-11.构件级设计.ppt_第3页
第3页 / 共78页
软件工程2-11.构件级设计.ppt_第4页
第4页 / 共78页
软件工程2-11.构件级设计.ppt_第5页
第5页 / 共78页
点击查看更多>>
资源描述

《软件工程2-11.构件级设计.ppt》由会员分享,可在线阅读,更多相关《软件工程2-11.构件级设计.ppt(78页珍藏版)》请在三一文库上搜索。

1、软件工程,第二部分 软件工程实践 第11章 构件级设计 Chapter 11 Component-Level Design,在体系结构设计阶段可以定义一套完整的软件构件。这个阶段的软件构件抽象层次比较高,没有接近代码抽象级。 构件级设计定义了数据结构、算法、接口特征和分配给每个软件构件的通信机制。,构件级设计,构件是计算机软件中的一个模块化的构造块。 OMG UML规范对构件的定义:系统中模块化的、可配置的和可替换的部件,该部件封装了实现并暴露了一组接口。 OMG Unified Modeling Language Specification OMG01 defines a component

2、 as “ a modular, deployable, and replaceable part of a system that encapsulates implementation and exposes a set of interfaces.”,11.1 什么是构件,在面向对象软件工程环境中,构件包括一组协作的类(有时,一个构件只包含一个单独的类)。 OO view: a component contains a set of collaborating classes。,11.1.1 面向对象的观点,11.1.1 面向对象的观点,构建分析模型,体系结构设计,构件级设计,可能与Pr

3、icingTable构件协作,可能与JobQueue构件协作,与面向对象的构件相似,传统的软件构件也来自分析模型。不同的是,传统的软件构件是以分析模型中的数据流要素作为导出构件的基础。,11.1.2 传统的观点,11.1.2 传统的观点,相当于PrintJob类定义的操作,相当于PrintJob类定义的操作,相当于PrintJob类定义的操作,相当于PrintJob类定义的操作,相当于PrintJob类定义的操作,相当于PrintJob类定义的操作,前面有两种构件级设计的观点:面向对象观点、传统观点。都假定从头开始设计构件。 另一种方法:使用已有构件来构造系统。,11.1.3 其他相关观点,1

4、1.1.3 其他相关观点,一、相关背景的比较 COM/DCOM/COM+:为了适应更加复杂应用的需要,Microsoft公司推出了构件对象模型COM,COM支持同一台计算机上不同进程间对象的调用;由于分布式处理系统的广泛应用和与CORBA竞争的需要,Microsoft公司于1997年推出了COM的分布式版本,即DCOM,支持对象间通过网络(包括局域网、广域网、因特网)进行通信。 CORBA:CORBA(Common Object Request Broker Architecture公共对象请求代理体系结构)是由OMG工业集团定义的分布对象计算模型和系统结构。OMG与1990年提出了一个对象管

5、理结构(OMA),这是CORBA的最原始的构想及基础。在OMA的基础上,1991年一些大公司联合提出了CORBA1.1版。目前有不少公司从事CORBA的实现工作,并推出了基于CORBA的产品。CORBA仅是一个分布对象规范,没有限定使用何种程序设计语言,其目的是不使CORBA束缚在某种特定程序设计语言上。用不同语言书写的对象,只要符合CORBA规范,就可以相互调用。但由于CORBA规范仅是一个书面的说明,各公司对其理解未必一致,规范中也有不少部分没有做统一规定,由厂家自行决定,因此各厂家基于CORBA的产品未必相互兼容。 Java Beans:Java Beans是于1996年提出的基于Jav

6、a语言的分布对象模型,其构件叫Bean。Bean就是以Java语言中的类和对象为基础定义的。当初,Java Beans主要为一些软件构造工具提供一些可视化构件。后经不断扩充,发展成为一种通用分布对象模型。,11.1.3 其他相关观点,二、基本概念的比较 COM/DCOM:COM/DCOM对象模型设计的指导思想是健壮、高效和切实可行。COM对象具有多个接口,通过每个接口可以访问一组成员函数。成员函数相当于方法。每个对象拥有自己的数据,表示对象的状态。数据只能通过接口访问,用户和应用程序不能越过接口访问数据。每个接口仅包含其所属成员变量函数的调用说明及引用它的指针。成员函数的实现不是对象的一部分,

7、一般可以有两种方法实现:一是用动态链接库DLL实现,二是作为一个可执行模块EXE实现。不管哪一种实现,成员函数都可以动态调用,直接执行,不需要编译连接。甚至调用者所用的程序设计语言与实现成员函数所用的程序设计语言也可以是不同的。用DLL实现时,在调用成员函数前必须将DLL加载到本地进程的地址空间,不能跨进程空间进行访问。而EXE模块不受这个限制,可以跨进程访问。 在COM对象的多个接口中,有一个接口是每个对象必备的该接口被命名为Iunknown,接口名前面加字母I,以便识别。 COM/DDCOM也有类的概念,类也看成是一种对象,称为类对象。由于COM/DCOM不支持继承的概念,一个对象的所有接

8、口及其成员函数都已在对象中定义,在引用对象时不必到其所属的类或其祖先类中查询有关的内容。只有在创建一个对象(即类的一个实例)时,才用到类中的内容。在COM中,除了类以外,还有类型(type)的概念。类型比类更抽象,它与实现无关。而类中可含有一些与实现有关的属性,诸如支持它的软件名称、所用的图表等。例如一个复合文件可以定义成一个类型,但这种类型可用不动的字处理软件、电子表格软件、多媒体软件来实现,形成不同的类,可用不同的图表表示。用户可以根据运行环境在同一类型中选择合适的类。,11.1.3 其他相关观点,二、基本概念的比较 CORBA CORBA的对象模型基本上按OMG所定义的公共对象模型COM

9、(不同于微软的COM),支持类、封装、继承和多态,是一个功能比较完备的对象模型。对象或类之间可按客户/服务器方式互相调用。每个对象或类即可以作为客户,也可以作为服务器,有时还可以兼作客户和服务器。 客户对象和服务器对象只通过消息交互作用。客户对象向服务器对象发出请求,服务器对象响应客户对象的请求完成一定的操作,并返回操作结果和必要的信息。它们只通过消息往来,不必了解与请求无关的功能。即使客户对象或服务对象重新实现,只要接口的语法和语义不变,不影响用户的使用。 客户和服务器的通信方式一般有两种:常用的是同步方式,即客户提交请求后,客户要等到服务器放操作执行完毕并返回操作结果或信息后,才继续运行;

10、另一种方式是异步方式,即客户提交请求后,可继续运行。,11.1.3 其他相关观点,二、基本概念的比较 Java Beans 与其它分布对象模型一样,Java Beans是以对象作为基本构件。类是对象的模板,而对象(即Bean)是有类生成的一个实例。类中可有多种构造对象的函数。如果生成对象时,不指明具体的构造函数,仅指明类名,则用下面的接口调用:new ()。这就表示用类中的缺省构造函数Constructor()生成对象。在Java Beans中,一组对象相互联系,相互作用,有公共的接口,服务于某一应用目的,则这组对象组成一个容器。以上的概念和方法与CORBA、DCOM相类似。,11.1.3 其

11、他相关观点,构件级设计基于分析模型、体系结构模型。面向对象方法中构件级设计主要关注分析类的细化(特定的问题域类)和基础类的定义和精化。,11.2 设计基于类的构件,四个基本设计原则: 开关原则 Liskov替换原则 依赖倒置原则 接口分离原则,11.2.1 基本设计原则,开关原则 模块应该对外延具有开放性,对修改具有封闭性。 The Open-Closed Principle (OCP). “A module component should be open for extension but closed for modification. 包含以下两层意思: 1. 所谓的open就是类,模

12、块,乃至函数对以后扩展而言是开放的,也就是说类,模块,函数要能方便以后的功能扩展。 2. 所谓的close就是类,模块,乃至函数对以后的修改而言是关闭的,也就是说类,模块,函数因需求变更带来的修改应该是最小的。(当然也有特殊的情况,较大的需求变更带来的修改也可能是很大的。),11.2.1 基本设计原则,/ 违背开关原则的一个例子 class GraphicEditor public void drawShape(Shape s) if (s.m_type=1) drawRectangle(s); else if (s.m_type=2) drawCircle(s); public void d

13、rawCircle(Circle r) public void drawRectangle(Rectangle r) class Shape int m_type; class Rectangle extends Shape Rectangle() super.m_type=1; class Circle extends Shape Circle() super.m_type=2; 在这个例子中,如果现在需求变更了,需要支持其他图形的绘制,如果让你在原有的基础上增加代码你会怎么做?毋庸置疑,你需要在drawShape中增加if分支来增加对新的图形的绘制。,但如果我们将原来的设计变更如下: /

14、符合开关原则的一个好例子 class GraphicEditor public void drawShape(Shape s) s.draw(); class Shape abstract void draw(); class Rectangle extends Shape public void draw() / draw the rectangle class Circle extends Shape public void draw() / draw the Circle 看看是不是简洁了很多,再来看看原来的drawShape函数,这里它只是调用了s的draw函数,原来的 drawCir

15、lcle,drawRectangle也没了。现在如果再增加新的图形就容易多了,我只要从Shape继承一个新的类,定义好其draw方法, GraphicEditor不用修改,编译的时候也只用编译新增的图形CPP文件,可使用新增图形类的文件,这里还减少了编译依赖。其实这里还可以看出另外一个原则就是依赖倒置原则,这里GraphicEditor只依赖于Shape接口,并不依赖具体的实现类,像Circle,Rectangle等等。,如何使用开闭原则: 抽象约束 第一,通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法; 第二,参数类型、引用对象尽量使用接口

16、或者抽象类,而不是实现类; 第三,抽象层尽量保持稳定,一旦确定即不允许修改。 元数据(metadata)控制模块行为 元数据就是用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。 Spring容器就是一个典型的元数据控制模块行为的例子,其中达到极致的就是控制反转(Inversion of Control),11.2.1 基本设计原则,Liskov替换原则 子类可以替换基类。将子类传递给构件来代替基类时,使用基类的构件应该仍然能够正确完成其功能。 The Liskov Substitution Principle (LSP). “Subclasses s

17、hould be substitutable for their base classes. LSP原则要求源自基类的任何子类必须遵守基类与使用该基类的构件之间的隐含约定。 这里约定既是前置条件构件使用基类前必须为真;又是后置条件构件使用基类后必须为真。 定义:所有引用基类的地方必须能透明地使用其子类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。,11.2.1 基本设计原则,里氏替换原则(Liskov Substitution Principel)

18、是解决继承带来的问题。 继承的优点: 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性; 提高代码的重用性; 子类可以形似父类,但又异于父类; 提高代码的可扩展性; 提高产品或项目的开放性。 继承的缺点: 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法; 降低代码的灵活性,子类必须拥有父类的属性和方法,让子类增加了约束; 增强了耦合性,当父类的常量、变量和方法被修改时,必须考虑子类的修改。,11.2.1 基本设计原则,含义: 子类必须完全实现父类的方法 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了里氏替换原则。 如果子类不能完整地

19、实现父类的方法,或者父类的某些方法在子类中发生了“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。,11.2.1 基本设计原则,目的: 采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。,11.2.1 基本设计原则,class Animal private string name; void Animal(string name) this.name = name; public void Description

20、() Console.WriteLine(“This is a(an) “ + name); /下面是它的子类猫类: class Cat extends Animal void Cat(string name) public void Mew() Console.WriteLine(“The cat is saying like mew“); /下面是它的子类狗类: class Dog extends Animal void Dog(string name) public void Bark() Console.WriteLine(“The dog is saying like bark“);

21、 ,/最后,我们来看看客户端的调用过程: public void DecriptionTheAnimal(Animal animal) if (typeof(animal) is Cat) Cat cat = (Cat)animal; Cat.Description(); Cat.Mew(); else if (typeof(animal) is Dog) Dog dog = (Dog)animal; Dog.Description(); Dog.Bark(); 通过上面的代码,我们可以看到这个设计的扩展性不好。 是什么原因呢?其实就是因为不满足替换原则,子类如Cat有Mew()方法父类根本没

22、有,Dog类有Bark()方法父类也没有,两个子类都不能替换父类。这样导致了系统的扩展性不好和没有实现运行期内绑定。,class Animal private string name; void Animal(string name) this.name = name; public void Description() Console.WriteLine(“This is a(an) “ + name); abstract void doAction(); /下面是它的子类猫类: class Cat extends Animal void Cat(string name) public vo

23、id doAction() Console.WriteLine(“The cat is saying like mew“); /下面是它的子类狗类: class Dog extends Animal void Dog(string name) public void doAction() Console.WriteLine(“The dog is saying like bark“); ,/最后,我们来看看客户端的调用过程: public void DecriptionTheAnimal(Animal animal) animal.Description(); animal.doAction(

24、); /或 public void DecriptionTheAnimal(Cat animal) animal.Description(); animal.doAction(); 通过上面的代码,我们可以看到客户端的调用满足替换原则。,依赖倒置原则 依赖于抽象,而非具体实现。 Dependency Inversion Principle (DIP). “Depend on abstractions. Do not depend on concretions.” 在传统的结构化编程中,最上层的模块通常都要依赖下面的子模块来实现,也称为高层依赖底层!DIP原则就是要逆转这种依赖关系,让高层模块不

25、要依赖底层模块。 依赖倒置原则的原始定义: 高层模块不应该依赖底层模块,两者都应该依赖其抽象; 抽象不应该依赖细节; 细节应该依赖抽象。 依赖倒置原则实际上就是要求“面向接口编程”。 采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。,11.2.1 基本设计原则,依赖倒置原则 本质: 依赖倒置原则的本质就是通过抽象(接口或者抽象类)使各个类或模型的实现彼此独立,不互相影响,实现模块间的松耦合。 规则: 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备; 变量的表面类型尽量是接口或者抽象类; 任何类都不应该从具体类派生; 尽量不要覆

26、写基类的方法; 结合里氏替换原则使用。 接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。,11.2.1 基本设计原则,依赖倒置原则 依赖倒置与依赖正置 依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,这也是正常人的思维方式,我要开奔驰车就依赖奔驰车,我要使用笔记本电脑就直接依赖某笔记本电脑,而编写程序需要的是对现实世界的事物进行抽象,抽象的结构就是有了抽象类和接口,然后我们根据系统设计的需要产生了抽象间的依赖,代替了人们传统思维中的事物间的依赖,“倒置”就是从这里产生

27、的。,11.2.1 基本设计原则,接口分离原则 多个用户专用接口比一个通用接口要好。 The Interface Segregation Principle (ISP). “Many client-specific interfaces are better than one general purpose interface. ISP原则建议: 设计者应该为每一个主要的客户类型都设计一个特定的接口。 只有那些与特定客户类型相关的操作,才应该出现在该客户的接口说明中。 如果多个客户要求相同的操作,则这些操作应该在每个特定的接口中都加以说明。,11.2.1 基本设计原则,接口分离原则 定义: 客

28、户端不应该依赖它不需要的接口 类间的依赖关系应该建立在最小的接口上 我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。 提供给每个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。 接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。,11.2.1 基本设计原则,接口分离原则 假设FloorPlan类用在SafeHome的安全和监督功能中。 对于安全功能,FloorPlan只有在配置活动中有用,并且使用pla

29、ceDevice()、showDevice () 、groupDevice () 、removeDevice ()等操作。 对于监督功能,除了使用placeDevice()、showDevice () 、groupDevice () 、removeDevice ()等操作,还需要特殊的操作showFOV()、showDeviceID()来管理摄像头。 ISP建议定义两个特殊的接口。安全接口包括placeDevice()、showDevice () 、groupDevice () 、removeDevice ()等4操作;监视接口包括placeDevice()、showDevice () 、gr

30、oupDevice () 、removeDevice ()、 showFOV()、showDeviceID()等6操作,11.2.1 基本设计原则,迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP) 一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。,11.2.1 基本设计原则,迪米特法则(Law of Demeter,LoD)也称为最少知识

31、原则(Least Knowledge Principle,LKP) 含义: 只和朋友交流 朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。,11.2.1 基本设计原则,11.2.1 基本设计原则,11.2.1 基本设计原则,迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP) 注意:一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况 。类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量

32、不引入一个类中不存在的对象,当然,JDK API提供的类除外。 注意:迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。 最后,迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。,11.2.1 基本设计原则,单一职责原则(SRP) 定义: 应该有且仅有一个原因引起类的变更。 There should never be more than one reason for a class to change 优点: 类的复杂性降低

33、,实现什么职责都有清晰明确的定义; 可读性提高,复杂性减低,可读性当然提高; 可维护性提高,可读性提高,可维护性当然提高; 变更引起的风险减低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的类有影响,对其他接口无影响,这对系统的扩展性、维护性都有非常大的帮助。,11.2.1 基本设计原则,单一职责原则(SRP) 注意: 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。 建议: 接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。,11.2.1 基本设计原则

34、,设计模式六大原则 单一职责原则(SRP) 开闭原则(OCP) 里氏替换原则(LSP) 依赖倒置原则(DIP) 接口隔离原则(ISP) 迪米特法则(LoD,LKP),11.2.1 基本设计原则,内聚意味着 构件或者类只封装那些相互关联密切,以及与构件或类自身有密切关系的属性和操作。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 1.功能内聚 如果一个模块内部的各组成部分的处理动作全都为执行同一个功能或实现单一的目标而存在,并且只执行一个功能,则称为功能聚合。 判断一个模块是不是功能聚合,只要看这个模块是“做什么”,是完成一个具体的任务,还是完成多任务。 功能内聚的模块易于复用、易

35、于维护。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 2.分层内聚 由包、构件和类来表现。 高层能够访问低层的服务,但低层不能访问高层。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 3.通信内聚 访问相同数据的所有操作被定义在一个类中。一般来说,这些类只着眼于数据的查询、访问和存储。 体现出功能、分层、通信等内聚的类或构件易于实现、测试和维护。尽可能实现这些内聚。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 4.顺序内聚 将构件或者操作按照前者为后者提供输入的方式组合,目的在于实现一个操作的序列。,11.2.3 内聚性,内聚的七种形式:按照内聚度

36、从高到低: 5.过程内聚 构件或者操作的组合方式是,允许在调用前面的构件或操作之后,马上调用后面的构件或操作,及时两者之间没有数据进行传递。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 6.暂时内聚 操作的执行是为了反映某一指定的行为或状态,如在启动是要执行的某一操作或者在错误检测时所要执行的全部操作。,11.2.3 内聚性,内聚的七种形式:按照内聚度从高到低: 7.实用内聚 在一类中,在其他方面不相关的构件、类或操作被分成一组。如,如果Statistics类包括计算6个简单统计度量所需的所有操作和属性,那么这个类体现出实用性内聚。 尽量不要出现上述4个内聚(4-7)。,11.

37、2.3 内聚性,消息和协作是面向对象系统的基本要素。 耦合是类之间彼此联系程度的一种定性度量。 耦合的九种形式: 1.内容耦合 当一个构件“暗中修改其他构件的内部数据”时,就会发生这种类型的耦合。 这违反了基本设计概念当中的消息隐蔽原则。一定不要出现内容耦合。,11.2.4 耦合性,11.2.4 耦合性,class A public int ma; / class B private A a = new A(); public void methodA() a.ma += 1; / 这里出现内容耦合 ,11.2.4 耦合性,/ 改正: class A private int ma; / pub

38、lic private / /加 getter/setter public int getMa() return this.ma; public void setMa(int ma) this.ma = ma; class B private A a = new A(); public void methodA() this.a.setMa(this.a.getMa() + 1); ,内容耦合示例: 例:在宠物商店的例子中,假设有一个的产品类Product,该类有一个用来记录宠物单价的实例变量unitPrice,如果该变量是Public的,那么其它类(如订单类)就可以轻易的修改该变量,甚至将单

39、价改为一个负数。代码如下所示 public class Product public float unitPrice; public class Order private Product myProduct=new Product(); public void setItem() myProduct.unitPrice = -100; /内容耦合 ,Public类和Order类之间构成了内容耦合。为了避免这种耦合,Java的做法是将其变为私有变量,并提供get和set方法,.Net则将该变量变为属性,在属性内部提供get和set方法。这两种方法大同小异,使用get方法可以访问变量,而恰当地使

40、用set方法能够保证“合法地”修改变量。本例中,将unitPrice变量改为属性,并提供get和set方法。,上面的Product类的代码可以修改为: public class Product private float _unitPrice; public float unitPrice get return _unitPrice; set if (value0) /code here to throw an exception else _unitPrice=value; ,耦合的九种形式: 2.公用耦合 当大量的构件都要使用同一个全局变量/数据时发生此种耦合。 尽管有时这样做是必要的(如

41、,设立一个在整个应用系统中都可以使用的缺省值),但是这种耦合进行变更时,能够导致不可控制的错误蔓延和不可预见的副作用。 共同的数据环境可能是共享的通信区、内存、存储介质等,如果二个构件一个只是写、另一个只是读,则这种公用耦合称为松散公用耦合,否则是紧密公用耦合。,11.2.4 耦合性,耦合的九种形式: 2.公用耦合 公用耦合是一种不良的耦合关系,它给构件的维护和修改带来困难。如公用数据要作修改,很难判定有多少构件应用了该公用数据。只有在构件间需要传输的数据量很大,不宜通过参数传递时,才采用公用耦合。 二个构件对同一个数据库进行读写是最常见的公用耦合。 可以通过封装来对公用耦合降耦。例如将所有全

42、局变量封装到一个包含公有方法的类中,通过调用这些方法来获取和设置 “合法的”数据。但这只能在一定程度上减小全局变量的危害。仍然不能避免对使用全局变量的模块之间的耦合所引起的副作用。,11.2.4 耦合性,11.2.4 耦合性,公用耦合例子,耦合的九种形式: 3.控制耦合 如果一个构件通过传送开关、标志、名字等控制信息,明显地控制选择另一构件的功能,就是控制耦合。 这种形式耦合的主要问题在于B中的一个不相干变更,往往能够导致A所传递控制标记的意义必须发生变更。,11.2.4 耦合性,/* 控制耦合。* 根据年龄判断是否大于18岁,然后根据是否满18岁判断是否到达法定饮酒年龄*/ #include

43、 static bool Signal; void AdultOrNot(int age) if (age 18) Signal = 1; else Signal = 0; void WineOrNot() if (Signal = 1) printf(“%sn“, “您已到达法定饮酒年龄!“); else printf(“%sn“,“您未到达法定饮酒年龄!“); int main() int Age = 0; printf(“%s“,“请输入您的年龄:“); scanf(“%d“, ,耦合的九种形式: 3.控制耦合 这种形式耦合的主要问题在于B中的一个不相干变更,往往能够导致A所传递控制标记

44、的意义必须发生变更。 改善方法就是把B构件调用的函数直接写入A构件中,然后删除B构件。,11.2.4 耦合性,耦合的九种形式: 4.印记耦合 当类B被声明为类A某一操作中的一个参数类型时就会发生这种耦合。 由于类B作为类A定义的一部分,所以修改系统就会变得更为复杂。,11.2.4 耦合性,印记耦合示例: 例:在宠物商店的例子中,订单类(Order)有一个方法(getTotalMoney)是计算用户订单的总金额。该方法要根据用户的级别 (如1-钻石级、2-白金级和3-黄金级等)和消费总积分而采用不同的折扣策略。为了获得用户级别和消费总积分,在下面的程序中 User类的一个实例作为一个参数传入了O

45、rder类的calcTotalMoney方法中。这样,Order类和User类构成了印记耦合。 public class Order public float calcTotalMoney(User user) int userLevel = user.getLevel(); int userConsumeScore = user.getConsumeScore(); /计算订单总金额 ,上面主要问题在于: 1) calcTotalMoney方法只需要使用user的getLevel方法和getConsumeScore方法,然而user作为一个User类的实例传入calcTotalMoney中,

46、它的所有公共方法和变量都暴露给了calcTotalMoney方法,显然授予了calcTotalMoney更大的、不必要的访问权限。这容易产生副作用。 2) 当开发人员修改User类时,必须得检查是否也要修改Order类,因为后者用到了前者的方法。 3) Order类可复用性较低,因为它依赖于User类。必须将二者“捆绑”在一起才能被复用到其它环境。,订单类(Order)的calcTotalMoney方法可以用两个简单变量类型的参数代替User类的实例,代码如下: public class Order public float getTotalMoney(int userLevel, int c

47、onsumeScore) /. return 0; 显然,这种方法限制了Order类的访问权限,降低了Order类与User类的耦合。,耦合的九种形式: 5.数据耦合 当操作需要传递较长的数据参数时就会发生这种耦合。,11.2.4 耦合性,/* * 数据耦合 * 主函数main()和Multiply(int x, int y)之间为数据耦合关系 */ #include int Multiply(int x, int y) return(x * y); void main() int x = 0; int y = 0; scanf(“%d%d“, ,耦合的九种形式: 6.例程调用耦合 当一个操作

48、调用另一个操作时就会发生这种耦合。 这种级别的耦合很常见,并且常常是必要的。,11.2.4 耦合性,耦合的九种形式: 7.类型使用耦合 当构件A使用了在构件B中定义的一个数据类型时会发生这种耦合。,11.2.4 耦合性,耦合的九种形式: 8.包含或导入耦合 当构件A引入或者包含一个构件B的包或内容时会发生这种耦合。,11.2.4 耦合性,耦合的九种形式: 9.外部耦合 当构件和基础设施构件(如操作系统功能、数据库功能、无线通信功能)进行通信和协作时会发生这种耦合。 尽管这种耦合是必要的,但一个系统中应该尽量将此种耦合限制在少量的构件或者类范围内。,11.2.4 耦合性,耦合的九种形式: 9.外部耦合 如:在宠物商店的例子中使用的是SqlServer数据库,设计人员考虑要使这个系统也能支持其他数据库,例如Oracle。如果系统中的多个模块都直接读写数据库,则外部耦合严重。为了支持Oracle数据库,需要修改大量代码。如果再要支持DB2,系统还有再度修改代

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1