设计模式介绍:
设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four(GoF)的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分别为创建型模式、结构型模式和行为型模式。
设计模式的类型:
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
序号 | 模式 & 描述 | 设计模式 |
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活 |
|
2 | 结构型模式 这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。 |
|
3 | 行为型模式 这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。 |
|
工具-UML图
介绍:
UML,全称为Unified Model Language,即统一建模语言,是由一整套图表组成的,为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言。UML 代表了一组最佳工程实践,这些实践已被证明在大型复杂系统的建模中是成功的。UML是开发面向对象软件和软件开发过程中非常重要的一部分。在嵌入式系统设计中,使用UML建模并书写文档,通常可以起到事半功倍的效果。
UML是在开发阶段,说明、可视化、构建和书写一个面向对象软件密集系统的制品的开放方法。最佳的应用是工程实践,对大规模,复杂系统进行建模方面,特别是在软件架构层次,已经被验证有效。统一建模语言(UML)是一种模型化语言。模型大多以图表的方式表现出来。一份典型的建模图表通常包含几个块或框,连接线和作为模型附加信息之用的文本。这些虽简单却非常重要,在UML规则中相互联系和扩展。
UML中的各种图
UML具有许多不同类型的图表,包括:
- 静态图:用例图、类图、包图
- 动态图:活动图、状态图、时序图、协作图
这些不同的图,可以提供从不同的角度来描述系统,因为大型的软件开发流程中除了程序员外,还有产品、设计、测试等人员,这些人都对系统的不同方面有不同关注,因此在建模时需要考虑不同的细节层次。
类之间的关系
设计思路
"既要也要",
设计一个类之间的关系既要高内聚,
设计多个类之间的关系要低耦合
常用的类之间关系
图形表示
依赖关系
- 依赖指得是类之间的调用关系,一个类调用了另一个类的方法。
- 如果类A在它的方法中使用到了另一个类B的方法或者属性,但是这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A,这个时候类A依赖类B。
泛化关系
- 泛化就是从子类抽象出一个父类 ,包含了继承关系(由父类具体化一个子类)。
- 继承关系指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。
- 继承与泛化可以看作一个逆过程
实现关系
- 实现指的是一个类实现一个interface接口(可以是多个)的功能,在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。
聚合关系
- 聚合(Aggregation)关系表示整体与部分的关系。在聚合关系中,成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用带空心菱形的直线表示,如汽车(Car)与引擎(Engine)、轮胎(Wheel)、车灯(Light),Java表示为:
组合关系
- 组合关系,是关联关系的一种,是比聚合关系更强的关联关系。它要求聚合关系中代表整体的对象负责代表部分对象的生命周期。也就是说,在组合关系中,部分和整体的生命周期是一样的。
- 它是一种整体与部分(part-of)关系。
- 一般使用成员(实例)变量来体现。
- 比如:人和胳膊的关系,是整体和部分的关系,胳膊是属于人体的一部分,并且胳膊和人体拥有同样的生命周期,人活着胳膊才有可能活着。
依赖关系举例:
public class Student{private Course coursepublic Student(Course course) {this.course = course;}public void study (){Course.learn();}
}
已上代码依赖于 Course类
泛化关系举例:
常规用法在泛型
MutiOverClass<T1,T2>
//MutiOverClass:泛型类名称
public class OverClass<T> {private T over;public T getOver() {return over;}public void setOver(T over) {this.over = over;}public static void main(String args[]) {OverClass<Boolean> over1 = new OverClass<Boolean>();OverClass<Float> over2 = new OverClass<Float>();over1.setOver(true);over2.setOver(3.14f);Boolean b = over1.getOver();Float f = over2.getOver();System.out.println(b);System.out.println(f);}
}
/*输出结果如下:
true
3.14
*/
聚合关系举例:
public class Engine
{}
public class Wheel
{}
public class Light
{}
public class Car {private Engine engine;private Light light;private Wheel wheel;public void setEngine(Engine engine) {this.engine = engine;}public void setLight(Light light) {this.light = light;}public void setWheel(Wheel wheel) {this.wheel = wheel;}public void drive(){}
}
组合关系举例:
public class Mouth
{}
public class Nose
{}
public class Head
{private Mouth mouth;private Nose nose;public Head(){mouth = new Mouth();nose = new Nose();}public void shake(){}
}
设计模式七大原则:
单一职责
一个类被改变的原因不能超过一个,也就是说,一个类只有一个职责,如果职责过多,代码就会臃肿,可读性更差,也更难以维护。其实上单一职责原则和接口隔离原则有一定的关系,接口隔离以后,职责就单一了,实现这个接口的类的职责自然也就单一了。但是接口隔离关注的是抽象层,单一职责关注的是两者兼而有之,偏重于实现。
接口隔离原则
客户端不应该被迫依赖于它不使用的方法(Clients should not be forced to depend on methods they do not use)。该原则还有另外一个定义:一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。
依赖倒转原则
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。High level modules should not depend upon low level modulesboth should depend upon abstractions. Abstractions should not depend upon detailsdetails
should depend upon abstractions.面向接口编程从而解耦
注意:
1)底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
2)变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓存层,利于程序扩展和优化
3)继承时遵循里氏替换原则
里氏替换原则
里氏替换原则(Liskov Substitution Principle,LSP)由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在 1987 年的“面向对象技术的高峰会议”(OOPSLA)上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出来的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
开闭原则
开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则。
一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
迪米特法则
如果一个系统满足迪米特法则,那么当其中一个软件实体发生变化时,就会尽量少的影响其他软件实体,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可以降低系统的耦合度,使类与类之间保持松耦合状态。
迪米特法则还有几种定义形式,包括:不要和"陌生人"说话、只与你的直接朋友通信等,在迪米特法则中,对于一个对象,其朋友包括以下几类:
- 当前对象本身(this)
- 以参数形式传入到当前对象方法中的对象
- 当前对象的成员对象
- 如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友。
- 当前对象创建的对象
任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。在应用迪米特法则时,一个对象只能与直接朋友发生交互,不能与“陌生人”发生直接交互,这样子可以降低系统的耦合度,一个对象的改变不会给太多其他对象带来影响。
迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象不必直接通信,那么这两个对象就不应该发生任何直接的相互作用,如果其中一个对象需要调用另外一个对象的某个方法时,可以通过第三者转发这个调用。就是通过引入一个合理的第三者来降低先有对象之间的耦合度。
合成复合原则
合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。
处理前:
处理后:
总结
继承复用的优点
- 可以很容易地修改或扩展父类的实现
继承复用的缺点
- 继承破坏封装,因为父类的实现细节完全暴露给子类(白盒复用)
- 父类的实现发生改变,则子类必受牵连
- 继承是静态的,不能在运行时发生改变,不灵活
组合复用的优点
- 不破坏封装,这种复用是黑盒复用,因为成员对象的内部细节对新对象保密
- 所需依赖少(只依赖接口)
- 是动态的,可以把成员对象动态替换为另一个类型相同的对象
组合复用的缺点
- 对象数量会增加
- 使用委托(delegation)会使得系统复杂
当需要应对变化的时候,应该首先使用组合的方式,而不是继承——因为组合更加灵活