文章目录
1、基础概念
1.1 设计模式
1.1.1 定义
设计模式是软件开发人员在软件开发过程中面临的不断重复的问题的解决方案,是解决特定问题的一系列套路,具有一定的普遍性。设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。使用设计模式是为了复用代码、让代码更容易被他人理解、保证代码可靠性。
1.1.2 基本要素
- 模式名称:根据功能、效果或者解决方案为对应方法设计的名称。
- 应用场景:对应设计模式的使用环境,需要满足的先决条件,何时使用该模式。
- 解决方法:对应模式的设计思路。
- 实现效果:模式解决的问题,模式的优缺信息。
1.1.3 分类
1. 根据目的划分
可分为创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)3 种。
创建型模式:用于描述如何创建对象,在创建对象的同时隐藏了创建逻辑。避免使用 new 运算符直接实例化对象。
主要包括单例模式、原型模式、工厂模式、抽象工厂模式、建造者模式5种设计模式。
结构型模式:用于描述如何将类或对象按某种方式进行组合形成大的结构。主要包括代理、适配器、桥接、装饰器、外观、享元、组合、过滤器等 8 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作完成单个对象无法单独完成的任务。主要包括了模板模式、策略、命令、责任链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器,空对象模式等 12 种行为型模式。
2. 根据作用范围来分
根据模式用于类还是对象上来分,可分为类模式和对象模式两种。
- 类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
- 对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂模式 | (类)适配器 | 模板模式、解释器 |
对象模式 | 单例 ,原型,抽象工厂,建造者 | 代理,(对象)适配器,桥接,装饰,外观,享元,组合 | 策略,命令,职责链,状态,观察者,中介者 ,迭代器,访问者,备忘录 |
3. J2EE设计模式
设计模式除了GOF提出的23种外还包括其他场景种的模式如Sun Java Center提出的J2EE模式,这些模式主要关注表示层。
包括:
- MVC 模式(MVC Pattern)
- 业务代表模式(Business Delegate Pattern)
- 组合实体模式(Composite Entity Pattern)
- 数据访问对象模式(Data Access Object Pattern)
- 前端控制器模式(Front Controller Pattern)
- 拦截过滤器模式(Intercepting Filter Pattern)
- 服务定位器模式(Service Locator Pattern)
- 传输对象模式(Transfer Object Pattern)
1.2 类间关系
1.2.1 关联关系(Association)
关联关系是类之间最常见的关系,表示不同类别对象之间具有某种含义的联系,一般是对象之间的引用关系,在代码中以成员变量形式出现,定义了对象之间静态的结构关系,是一种“强关联”的关系。关联关系一般用带箭头的实线表示,箭头从使用类指向被引用类,箭头两端标注角色名,实线上标注关系名称。关联可以是单向的,也可以是双向的,双向的双箭头表示。
1.2.2 聚合关系(Aggregation)
聚合关系是通过成员变量来实现的,所以也是关联关系的一种。成员对象是整体对象的一部分,所以是整体和部分的关系(has-a)。但是,成员对象可以脱离整体而存在。比如,公司与员工,公司没了,员工可以保留。聚合关系在类图中可以用带空心菱形的实线来表示,菱形指向整体。
1.2.3 组合关系(Composition)
组合关系同样通过引用方式表示整体和局部的关系,但是是不可分的,即整体对象消失,局部对象也不复存在。比如,公司和部门的关系,公司没了,部门也就没了。组合关系用带实心菱形的实线来表示,菱形指向整体。
1.2.4 依赖关系(Dependency)
依赖关系是一种临时的关联关系,是对象之间耦合度最弱的一种关联方式,可能随着运行时的变化依赖关系也发生变化。依赖关系建模的是使用关系,在代码中表现为一个类通过方法参数或者局部变量访问另一个类的对象的方法或者字段。表示一个类在运行中使用到了另一个类中的对象。依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。
1.2.5 泛化关系(Generalization)
泛化关系表示类之间的继承,即is-a关系,是对象之间耦合度最大的一种关系。在代码中表现为继承一个非抽象类。泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。
1.2.6 实现关系(Realization)
实现关系是接口与实现类之间的关系。代码中表现为继承抽象类或者接口。实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。
2、设计原则
设计模式的设计遵从着七种设计原则,是设计模式的基础。软件工程一直推崇着低耦合高内聚的思想,让每个类只负责一件事,若有功能相似的类则考虑继承。对于多方法调用的情况要善用接口。降低对象之间的耦合增加程序的复用性、扩展性。
2.1 开闭原则
不改源码而增加功能,关键在于如何抽象。开闭原则是保证程序扩展性的原则,主要思想是当需求发生变更时,不能修改原有代码的前提下改变模块的功能即对扩展开放,对修改关闭。开闭原则的实现主要是通过接口或者抽象类将稳定不变的需求抽象出来,变化的部分由派生类来实现。当需求发生变化时只需要重新派生一个实现类扩展即可,即抽象约束,封装变化。关键是如何抽象。
2.2 里氏替换原则
父类可以出现的地方,子类一定可以出现即要确保父类所拥有的性质在子类中仍然成立即用子类可以实现父类的原功能。里氏替换是描述继承复用的原则,当子类替换父类时,程序不会出错。即子类可以扩展父类的功能,但不能改变父类原有的功能,尽量不要重写父类的方法。里氏替换原则是开闭原则的一种实现规范,它要求父类尽可能抽象,确保用不同的子类替换父类时,以前程序中基于父类的方法调用不会出错。关键在继承原则,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
2.3 依赖倒置原则
面向接口编程,依赖抽象而不是依赖具体实现。软件设计中具体实现往往随需求而变动,但抽象层相对稳定,具体实现依赖于抽象层可以降低实现与设计之间的耦合。在实际编程中一般在要求在变量声明时类型尽量是接口或者抽象类;任何类都不应从具体实现类中派生。关键在子类不可改父类功能。
2.4 单一职责原则
单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。关键在最小类,最小功能单元。
2.5 接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。接口隔离原则建议尽量将复杂的接口分解为更小更具体的接口,去掉冗余部分。为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用,降低了类之间的耦合度。与单一职责原则不同,接口隔离重点在于接口依赖的隔离,单一职责注重的是功能,是职责;并且接口隔离主要是从抽象层,整体的角度约束接口,而单一职责则是从具体实现的角度约束类这个维度。关键在最小接口,接口级别。
2.6 迪米特法则
又称最少知道原则,是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。尽量只与直接相关的实体交互。也就是说,如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。在实现上,迪米特法则建议在类划分上降低类之间的耦合,不暴露类的属性成员,而提供相应的访问器。
2.7 合成复用原则
尽量使用合成/聚合的方式,其次才是使用继承。合成复用是通过组合或聚合的方式,将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能。这种方式新旧类之间的耦合度低,依赖较少,新对象存取旧对象的唯一方法是通过旧对象的接口。在实现中,已有对象是作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用的效果。
继承复用会将父类的实现细节暴露给子类,父类对子类是透明的;子类与父类的耦合度高,父类的实现的任何改变都会导致子类的实现发生变化;从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
总结:设计原则的目的就是降低耦合,提高内聚性,增加程序的复用性和可扩展性。
设计原则 | 归纳 | 目的 |
---|---|---|
开闭原则 | 对扩展开放,对修改关闭 | 降低维护带来的新风险 |
依赖倒置原则 | 高层不应该依赖低层,要面向接口编程 | 更利于代码结构的升级扩展 |
单一职责原则 | 一个类只干一件事,实现类要单一 | 便于理解,提高代码的可读性 |
接口隔离原则 | 一个接口只干一件事,接口要精简单一 | 功能解耦,高聚合、低耦合 |
迪米特法则 | 不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度 | 只和朋友交流,不和陌生人说话,减少代码臃肿 |
里氏替换原则 | 不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 |
合成复用原则 | 尽量使用组合或者聚合关系实现代码复用,少使用继承 | 降低代码耦合 |