(二) 公共耦合
两个以上的模块共同引用一个全局数据项就称为公共耦合。大量的公共耦合结构中,会让你很难确定是哪个模块给全局变量赋了一个特定的值
(四) 控制耦合
控制耦合 。一个模块通过接口向另一个模块传递一个控制信 ,接受信 的模块根据信 值而进行适当的动作,这种耦合被称为控制耦合,也就是说,模块之间传递的不是数据,而是一些标志,开关量等等
(六) 数据耦合
模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形 式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另 一些模块的输入数据
三 UML 类图及类图之间的关系
在一个相对完善的软件系统中,每个类都有其责任,类与类之间,类与接口之间同时也存在着各种关系,UML(统一建模语言)从不同的角度定义了多种图,在软件建模时非常常用,下面我们说一下在设计模式中涉及相对较多的类图,因为在后面单个设计模式的讲解中,我们会涉及到,也算是一个基础铺垫。
(一) 类
类是一组相关的属性和行为的集合,是一个抽象的概念,在UML中,一般用一个分为三层的矩形框来代表类
-
第一层:类名称,是一个字符串,例如 Student
-
第二层:类属性(字段、成员变量)格式如下:
- 例如:-name:String
-
第三层:类操作(方法、行为),格式如下:
-
例如:+ display():void
(三) 关系
(1) 依赖关系
定义:如果一个元素 A 的变化影响到另一个元素 B,但是反之却不成立,那么这两个元素 B 和 A 就可以称为 B 依赖 A
- 例如:开门的人 想要执行开门这个动作,就必须借助于钥匙,这里也就可以说,这个开门的人,依赖于钥匙,如果钥匙发生了什么变化就会影响到开门的人,但是开门的人变化却不会影响到钥匙开门
- 例如:动物生活需要氧气、水分、食物,这就是一个很字面的依赖关系
依赖关系作为对象之间耦合度最低的一种临时性关联方式
在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
(3) 聚合关系
聚合关系也称为聚集关系,它是一种特殊的较强关联关系。表示类(准确的说是实例化后的对象)之间整体与部分的关系,是一种 has-a 的关系
- 例如:汽车(Car)有轮胎(Wheel),Car has a Wheel,这就是一个聚合关系,但是轮胎(Wheel)独立于汽车也可以单独存在,轮胎还是轮胎
聚合关系可以用带空心菱形的实线箭头来表示,菱形指向整体
(5) 泛化关系
泛化描述一般与特殊(类图中“一般”称为超类或父类,“特殊”称为子类)的关系,是父类和子类之间的关系,是一种继承关系,描述了一种 is a kind of 的关系,特别要说明的是,泛化关系式对象之间耦合度最大的一种关系
Java 中 extend 关键字就代表着这种关系,通常抽象类作为父类,具体类作为子类
- 例如:交通工具为抽象父类,汽车,飞机等就位具体的子类
泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类
四 设计模式七大原则
(一) 开闭原则
定义:软件实体应当对扩展开放,对修改关闭
我们在开发任何产品的时候,别指望需求是一定不变的,当你不得不更改的你的代码的时候,一个高质量的程序就体现出其价值了,它只需要在原来的基础上增加一些扩展,而不至于去修改原先的代码,因为这样的做法常常会牵一发而动全身。
也就是说,开闭原则要求我们在开发一个软件(模块)的时候,要保证可以在不修改原有代码的模块的基础上,然后能扩展其功能
我们下面来具体谈谈
(1) 对修改关闭
对修改关闭,即不允许在原来的模块或者代码上进行修改。
A:抽象层次
例如定义一个接口,不同的定义处理思路,会有怎样的差别呢
定义一
定义二
两种方式看似都是差不多的,也都能实现要求,但是如果我们想要在其基础上增加一个新的参数
- 如果以定义一的做法,一旦接口被修改,所有调用 connectServer 方法的位置都会出现问题
- 如果以定义二的做法,我们只需要修改 FTP 这个实体类,添加一个属性即可
- 这种情况下没有用到这个新参数的调用处就不会出现问题,即使需要调用这个参数,我们也可以在 FTP 类的构造函数中,对其进行一个默认的赋值处理
B:具体层次
对原有的具体层次的代码进行修改,也是不太好的,虽然带来的变化可能不如抽象层次的大,或者碰巧也没问题,但是这种问题有时候是不可预料的,或许一些不经意的修改会带了和预期完全不一致的结果
(2) 对扩展开放
对扩展开放,也就是我们不需要在原代码上进行修改,因为我们定义的抽象层已经足够的合理,足够的包容,我们只需要根据需求重新派生一个实现类来扩展就可以了
(3) 开发时如何处理
无论模块是多么“封闭”,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对他设计的模块应该对那种变化封闭做出选择,他必须先猜测出最有可能发现的变化种类,然后构造抽象来隔离那些变化 ——《大话设计模式》
预先猜测程序的变化,实际上是有很大难度,或许不完善,亦或者完全是错误的,所以为了规避这一点,我们可以选择在刚开始写代码的时候,假设不会有任何变化出现,但当变化发生的时候,我们就要立即采取行动,通过 “抽象约束,封装变化” 的方式,创建抽象来隔离发生的同类变化
举例:
例如写一个加法程序,很容易就可以写的出来,这个时候变化还没有发生
小结:
-
我们希望开发刚开始就知道可能发生的变化,因为等待发现变化的时间越长,要抽象代码的代价就越大
-
不要刻意的去抽象,拒绝不成熟的抽象和抽象本身一样重要
(二) 里氏替换原则
(1) 详细说明
定义:继承必须确保超类所拥有的性质在子类中仍然成立
里氏替换原则,主要说明了关于继承的内容,明确了何时使用继承,亦或使用继承的一些规定,是对于开闭原则中抽象化的一种补充
这里我们主要谈一下,继承带来的问题:
- 继承是侵入性的,子类继承了父类,就必须拥有父类的所有属性和方法,降低了代码灵活度
- 耦合度变高,一旦父类的属性和方法被修改,就需要考虑子类的修改,或许会造成大量代码重构
里氏替换原则说简单一点就是:它认为,只有当子类可以替换父类,同时程序功能不受到影响,这个父类才算真正被复用
其核心主要有这么四点内容:
- ① 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- ② 子类中可以增加自己特有的方法
- ③ 当子类的方法重载父类的方法时,子类方法的前置条件(即方法的输入参数)要比父类的方法更宽松
- ④ 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
对照简单的代码来看一下,就一目了然了
① 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
前半句很好理解,如果不实现父类的抽象方法,会编译 错
后半句是这里的重点,父类中但凡实现好的方法,其实就是在设定整个继承体系中的一系列规范和默认的契约,例如 鸟类 Bird 中,getFlyingSpeed(double speed) 用来获取鸟的飞行速度,但几维鸟作为一种特殊的鸟类,其实是不能飞行的,所以需要重写继承的子类方法 getFlyingSpeed(double speed) 将速度置为 0 ,但是会对整个继承体系造成破坏
虽然我们平常经常会通过重写父类方法来完成一些功能,同样这样也很简单,但是一种潜在的继承复用体系就被打乱了,如果在不适当的地方调用重写后的方法,或多次运用多态,还可能会造成 错
我们看下面的例子:
父类 Father
子类 Son
子类 Daughter
测试类 Test
运行结果:
父类: speaking方法被调用
子类: speaking方法被调用
父类: speaking方法被调用
② 子类中可以增加自己特有的方法
这句话理解起来很简单,直接看代码
父类 Father
子类 Son
测试类 Test
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!