设计模式概述
软件设计模式产生背景
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。
四位作者合称 GOF(四人帮,全拼 Gang of Four)。
软件设计模式概念
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
学习设计模式的必要性
设计模式在软件开发中的两个主要用途。
-
开发人员的共同平台
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。 -
最佳的实践
设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
设计模式分类
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。
- 创建者模式
用于描述“怎么样创建对象”,它的主要特点是创建和使用分离。四人组书中提供了单例,原型,工厂方法,抽象方法,建造者等5种创建型模式。 - 结构型模式
用于描述如何将类或者对象按某种布局组成更大的结构。四人组书中提供了代理,适配器,桥接,装饰,外观, 享元,组合等7种结构模型。 - 行为型模式
用于描述类或者对象之间怎么相互合作完成单独一个对象无法完成的任务, 以及怎么分配职责。提供了11种。
UML图
统一建模语言,是用来设计软件的可视化建模语言。它的特点是简单,统一,可视化,能表达软件设计中的动态和静态信息。
UML从目标系统的不同角度出发,定义了用例图,类图,对象图,状态图,活动图,时序图,协作图,构件图,部署图等9种图。
UML类图
类图是显示了模型的静态结构,特别是模型中存在的类,类的内部结构以及他们与其他类的关系等。类图是面向对象建模的主要组成部分。
作用
- 在软件工程中, 类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系。可以简化了人们对系统的理解。
- 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。(巧了,设计模式就是研究的设计阶段)
类图表示法
类的表示方式
类和类之间的表示方式
关联关系
单向关联
双向关联
自关联
聚合关系
也属于关联关系的一种
是强关联关系,是整体和部分之间的关系。
聚合关系也是通过成员对象来实现的。
例如,大学和老师
组合关系
更强烈的聚合
例如头和嘴
整体对象可以控制部分对象的生命周期, 一旦整体不存在,部分也就不存在,部分不能脱离整体而单独存在。
依赖关系
是一种使用关系
它是对象之间耦合度最弱的一种关系。
是一种临时性的关系
在代码中,某一个类的方法通过局部变量,方法的参数,静态方法的调用来访问另一个类中的某一些方法。
在UML类图中,使用带箭头的虚线表示。
箭头从使用类指向被依赖类
说白了, 从成员变量降低到了局部变量,耦合度就会大大的降低。
继承关系
实现关系
软件设计原则
在软件的开发中, 为了提高软件系统的可维护性和复用性, 增加软件的可扩展性和灵活性。要根据这六条原则来开发程序。
开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
用抽象构建框架,用实现扩展细节
优点:提高软件系统的可复用性和可维护性
看详细
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象
抽象不应该依赖细节
细节应该依赖抽象
更为精简的定义:面向接口编程(Object-Oriented Design, OOD)
深入理解:依赖倒置原则的本质就是通过抽象(抽象类或接口)使各个类或模块实现彼此独立,不互相影响,实现模块间的松耦合。在项目中使用这个规则需要以下原则;
一句话:依赖倒置原则的核心就是面向抽象(抽象类或者接口)编程
里氏代换原则
里氏代换原则的白话翻译是: 一个软件如果使用的是一个父类的话, 那么一定适用于其子类, 而察觉不出父类对象和子类对象的区别。 也即是说,在软件里面, 把父类替换成它的子类, 程序的行为不会有变化, 简单地说, 子类型必须能够替换掉它们的父类型。
里氏代换原则(LSP): 子类型必须能够替换掉它们的父类型。
也正因为这个原则, 使得继承复用成为了可能, 只有当子类可以替换掉父类, 软件单位的功能不受到影响时,父类才能真正被复用, 而子类也能够在父类的基础上增加新的行为。
正是由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展、不然还谈什么扩展开放, 修改关闭呢。 再回过头来看依赖倒置原则, 高层模块不应该依赖低层模块, 两个都应该依赖抽象,也就更容易理解了。
依赖倒置其实就是谁也不依靠谁, 除了约定的接口, 大家都可以灵活自如。
依赖倒置可以说是面对对象设计的标志, 用哪种语言来编写程序不重要, 如果编写时考虑的都是如何针对抽象编程而不是针对细节编程, 即程序中所有的依赖关系都是终止与抽象类或者接口, 那就是面向对象的设计, 反之那就是过程化的设计了。
迪米特法则
迪米特法则又叫作最少知识原则(Least Knowledge Principle,LKP)
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
合成复用原则
合成复用原则,又叫组合/聚合复用原则,它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。