您的位置:首页 > 其它

Chapter 0 设计模式的基础知识

2016-12-12 23:57 246 查看


1 概述

所谓设计模式(Design Pattern),是前人基于以往大量的实践总结出的一系列的解决问题的方法,这些方法应用得当可以极大的提高程序的复用性和扩展性。在实际的开发中,业务代码其实并不难写,但是要让写的代码易于扩展和复用往往不是一件易事,这就要求要对业务场景进行合理的抽象,应用合适的设计模式。有个形象的比喻,写代码就像做工艺品,设计模式就像用于做工艺品的模子,要做出漂亮的工艺品就要用一个合适的模子,类似的,要写出高质量的代码,要应用合适的设计模式。当然,不能不顾业务场景而去套用设计模式。

由Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著的"Design Patterns: Elements of Reusable Object-Oriented Software"是关于设计模式的最著名的书籍,他们常被人们称为:GoF(Gang of Four,四人组)。


2 设计模式的六个原则

就像数据库的事务需要遵循ACID原则一样,设计模式一般遵循以下六点原则:


2.1 单一职责原则(Single Responsibility Principle)

单一职责原则简称SRP,它约定某个类或模块(最简单比如函数)只有一个原因发生改变。

就一个类而言,如果其承担的职责过多,那么这些职责很可能耦合在一起。其中某个职责的变化有时候会波及到其他职责的正常运行。这样的设计会使得程序变得十分的脆弱,当出现某个很小的变化时都可能产生意想不到的问题。

为此,在设计程序时要遵守单一职责原则,将不同的职责封装到不同的类或模块之中,以期达到解耦和增强内聚性,也就是常说的“高内聚低耦合”的目的。


2.2 里氏替换原则(Liskov Substitution Principle)

里氏替换原则简称LSP, 是麻省理工学院的Barbara Liskov提出来的,它是定义子父类之间关系的原则,表示所有引用父类的地方都能透明的使用其子类对象。

继承作为OOP的三大特性之一,能使得子类通过继承父类获得父类的方法(和属性),从而使得子类的代码量变小。然而,它也存在着一些弊端,比如增加了子父类之间的耦合性,降低程序的可移植性。如果父类需要修改时,必须考虑其子类将要受到的影响,而子类如果对父类的非抽象方法的修改也可能导致整个继承体系受到破坏。

为此,LSP原则规定在设计类的继承关系时,子类可以扩展父类的功能,但尽量不要改变父类的原有功能。其具体有四个方面的含义:

(1)子类可以实现父类的抽象方法,但不要复写其非抽象方法

(2)子类可以增加自己特有的方法

(3)当子类重载父类的方法(尽量不要重载)时,方法的前置条件(即形参)要比父类的原有方法更加宽松

(4)当子类实现父类的抽象方法时,方法的后置条件(即返回值)要比父类更加严格


2.3 依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则简称DIP,其含义是:高层模块不应依赖于底层模块,二者都应依赖其抽象;抽象不依赖细节,细节应依赖于抽象。

在Java中,抽象指的是接口或者抽象类,而细节指的是具体的实现类。细节会因为业务的不同变得多变,而抽象的代码要稳定的多。DIP原则的核心是面向接口编程,即使用接口或抽象类制定好规范或契约,而将具体的实现细节留个其实现类去实现。


2.4 接口隔离原则(Interface Segregation Principle)

接口隔离原则简称ISP,它要求客户端不依赖于其不需要的接口,一个类对另一个类的依赖应建立在最小的接口上。

在使用接口时,使用多个专门的接口比使用单一的总接口要好,没有关系的接口耦合在一起会形成一个臃肿的体系。比如,某个系统,DBA有增、删、改、查数据的权限,RD有查询数据和修改数据的权限,而QA只有查询数据的权限,那么这里就应当有四个互相隔离的接口,即增、删、改、查各自有一个接口,否则可能出现QA误删除上线数据的问题。个人认为,ISP原则和SRP原则有异曲同工之妙。


2.5 迪米特法则(Law Of Demeter)

迪米特法则简称LOD,它的含义是一个对象应当对其它对象保持最少的了解。

当类与类之间的关系越紧密,其耦合度越大,其其中一个类发生改变时,另一个类也会受到影响。那么怎样降低耦合度,减少类之间的影响呢?迪米特法则要求被依赖的类无论逻辑多么复杂,都应当尽量将逻辑控制在其内部,除了被依赖的方法被public修饰以外,需要用private关键字控制信息的泄露。


2.6 开闭原则(Open Close Principle)

开闭原则简称OCP,它的含义是程序中的模块、类或者函数都应对扩展保持开放,而对修改保持关闭。

在某个软件系统的开发和维护过程中,不可避免的需要对原有的代码进行修改,期间有可能对之前的旧代码产生影响,更严重的有可能需要对某些功能整体进行重构,使得原有的代码需要重新进行测试。而开闭原则的核心是要求在程序的变化过程中,尽量通过扩展的方法来实现变化的逻辑,而不是强行修改原有的代码来进行修改。

其实,开闭原则更像是前面五个原则的概括,当前面的原则都被很好的遵循了,设计出来的程序自然是符合开闭原则的。


3 设计模式的分类

在GoF的著作中,一共有23中设计模式被提出来了,这些设计模式大概可以分为以下三类:

创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。


4 UML类图

UML是Unified Modeling Language的简称,在设计模式中,UML类图可以用来描述类之间关联的属性及行为,从而成为开发过程中的一种高效的沟通工具。


4.1 类和接口的表示

类用一个矩形框表示,其中分三个部分,如下图所示:



其中,最上面表示类名,中间的表示类的属性,+表示公共可见性,-表示私有可见性,#表示受保护的可见性,没有任何符号表示package级别的可见性,冒号后面的表示属性的类型。最下面的表示类的方法,权限控制和返回值类型与属性类似,并且可以列出形参的类型。并且,如果属性或方法的下面有下划线,表示为静态的。

接口的UML类图和类的相似,只不过第一栏加上了<<interface>>标识接口。




4.2 类与类的关系

类与类之间的关系一般有六种,即:继承(Generalization)、实现(Implements)、组合(Composition)、聚合(Aggressgation)、依赖(Dependency)、关联(Association),它们的UML类图分别有如下的表示


4.2.1 继承

继承关系的表示很简单,由子类通过实现和空心的三角形指向父类,例如下图所示:




4.2.2 实现

实现与继承相似,由实现类指向接口,只不过实线改成了虚线:




4.2.3 组合

组合表示的是一种“contains-a”的强拥有关系,即整体与部分的关系,整体与部分的生命周期相同。用UML类图表示时,可用一个带箭头的直线表示,直线的末尾有实心的菱形,并且可以用数字表示两者之间的数量关系,例如下图所示:



其中,0..*表示有大于等于0的数目


4.2.4 聚合

聚合表示的是一种“has-a”的弱拥有关系,其整体与部分的生命周期可以不同,这是聚合关系和组合关系的主要不同点。体现在UML图上即为,直线的末尾处的菱形需要用空心的表示。例如下图所示:



上图表示,一个班级有若干老师,但是当老师退休了,班级依然可以存在,这就是一种典型的聚合关系


4.2.5 依赖

两个类之间的依赖关系体现为一个类需要操作另一个类的对象,在UML类图表示中,由依赖的类出发的带箭头的虚线指向被依赖的类,例如下图所示:



在上图中,表示老师的类依赖于课程类,体现在代码上是老师类有一个teach方法,该方法的操作对象为课程类对象


4.2.6 关联

关联体现的是两个类之间语义级别的一种强依赖关系,比如学生和老师。这种关系比依赖更强、不存在依赖关系的偶然性,关系一般是长期且平等的,关联可以是单向、双向的。表现在代码层面,为被关联类B以类属性的形式出现在关联类A中。 体现在UML类图中,单向和双向关联分别为带箭头的实线和不带箭头的实现连接两个类,例如下图所示:



老师类和学生类是一个双向关联关系,因为一个老师对应多个学生,而一个学生也对应多个老师,而学生类和课程类之间是单向关联关系,一个学生可能要学习多门课程。


5 总结

以上总结了设计模式的一些基础知识,这些知识目前只是做个简单的归纳,显得还是比较抽象。后续在学习23种设计模式的过程中,慢慢体会这些知识的具体含义,相信理解会更加深刻。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息