Java设计模式-软件设计原则-UML

在面向对象过程中,我们通常提到这样的关键词:"封装"、"继承"、"多态"。没错这是面向对象的核心思想,但是细想为何要这样做?首先封装是为了要达到数据保护,即将类的内部结构进行隐藏,对于使用者而言不需要清楚地知道在类的内部实现,仅提供可供访问的方法,对类进行操作。继承是通过对`IS-A`的具体过程,在面向对象过程中,同面相对象最大的区别在于,其需要尽量避免重复工作,以实现复用。继承则是达到复用的手段之一,通过继承子类可以继承父类的方法或某些属性,使得子类可以复用父类的方法或属性而不用重复实现,并额外地添加新的功能。多态基于继承之上,它能够允许通过使用父类的引用而在运行时动态地调用子类的方法,这样的好处在于对于父类而言其无需清楚具体的子类,当使用父类调用方法时,会动态地执行该执行的行为。

以上是面向对象的大致目标,而"设计模式"则是基于面向对象设计的更高一层的设计思路。但是需要注意的是,设计模式并不是一种语法或语言特性,而是一种基于经验的总结积累。以下引用Head First中对设计模式的定义:

A Pattern is a solution to a problem in a context.

定义给出,设计模式是在特定场景下解决某种问题的解决方案,该解决方案是通过积累而给出的建议,而不是强烈要求必须要这样做。在Head First原著中对定义的解释如下:

The context is the situation in which the pattern applies. This should be a recurring situation.

The problem refers to the goal you are trying to archieve in this context, but it refers to any constraints that occur in the context.

The solution is what you're after: a general design that anyone can apply which resolves the goal and set of contraints.

首先"场景"是指模式的所适用的情景,这种情景在软件设计中会重复多次出现。"问题"是指在上述情景下,你需要努力达到的设计目标,目标是指解决实现过程中出现的各种约束性条件。"解决方案"则正是当你面临需要解决一个特定目标和一系列约束时任何人都可以采用的一种通用的模式。因此设计模式是为在软件设计中的某一类特定问题而提出的特定的解决方案,该解决方案是一种通用的模式,类似于在建筑中所广泛采用的某种不成文的规约。

Refactoring time is is Patterns time.

Head First中提出在重构时正是设计模式使用的时候,事实也正如此,设计模式能够尽可能地在不修改或尽少的修改原有系统地情况下,对软件系统进行重构和扩展。但是在进行设计初期,便使用设计模式,对项目的扩展和维护也有巨大帮助。

软件设计原则(Design Principle)

在软件设计过程中是为了应对变化,并且需要识别变化,分离在软件设计中的变化,变化是可以扩展的部分,而使用设计模式适应该变化则是为了能够实现可扩展。

HAS-A can be better than IS-A

Head First中对该原则进行了这样的定义:

Favor composition over inheritance.

在很多中文书籍中,我们称该原则为"合成复用原则",根据Head First的描述,"有一个"的关系比"是一个"的关系更好,并且要善于利用组合去代替继承。合成复用原则对类的依赖关系做了一个要求,即要尽可能地使用弱依赖而不是强依赖,继承是一种静态的并且依赖性极强的关系,而组合较继承具有更低的依赖性,更具有扩展性和灵活性,使用组合模式构建系统,不仅让你可以在类中封装一系列你所期望的算法,而且能够在运行时动态地改变组合的行为,以下是原著的描述:

Creating systems using composition gives you a lot more flexibility. Not only does it let you encapsulate a family of algorithms into their own set of classes, but it also lets you change behavior at runtime as long as the object you're composing with implements the correct behavior interface.

开闭原则(Open-Closed Principle)

原著中对"开闭原则"的优先级是这样描述的:

Grasshopper is on to one of the most important design principles.

说明开闭原则的重要性。

开闭原则的定义如下:

Classes should be open for extension,  but closed for modification.

对于类而言需要对扩展开放对修改关闭。"开闭原则"能够保证在我们对系统进行设计时,尽可能少的修改已有的代码,保证原有系统不被破坏,在保证稳定的情况下,对功能进行扩展。

好莱坞原则(The Hollywood Principle)

Don't call me, we'll call you.

不要调用我,我们会调用你。在原著中这句话较为隐晦,其表达的对象是基于父类和子类而言,具体的是:子类不要调用父类,父类会去调用子类。

在原著中有个概念叫"Dependency rot",依赖腐烂,其是这样描述的:

Denpency rot happens when you have high-level components depending on low-level components depending on high-level components depending on sideways components depending on low-level components, and so on.

其核心在于说明,依赖的不合理性,将造成复杂的系统,并且难以理解难以维护。因此好莱坞原则指的是,在软件设计过程中,要保证低层组件依赖于高层组件,而高层组件要于高层组件依赖。这是在告诉我们,在进行设计时,要实现抽象、多态的特性,"we'll call you"指的便是对于父类,其会选择性地去动态地调用子类。

依赖倒置原则(Dependency Inversion Principle)

Depend upon abstractions. Do not depend upon concrete classes.

依赖于抽象,而不是依赖于具体实现。依赖倒置原则,其核心概念便是:面向抽象编程。同好莱坞原则相似的是,其均对依赖于抽象这样一点提出了要求,而该原则基于好莱坞原则的基础上,提出了更加严格的要求。

迪米特法则(Law of Demeter)、最少知道原则(Least Knowledge Principle)

Principle of latest knowledge talk only to your immediate friends.

在最少知道原则下,类仅与其相关的类通信。换句话说便是,一个类应该尽可能少地了解其他类,这是希望能够简化在系统中类与类的之间的依赖,从而将各个类独立性更强,耦合性更低。在这种背景下,对一个类的改造,将不至于导致整个系统所有类的失效或重构。否则系统将难以维护。

UML类图

在面向对象设计中,类图能够清楚的表示类与类之间的关系,而设计模式也是借助类图来描述整个模式中,抽象、具体等的结构关系,因此读懂类图是学习模式前重要的一环。

在UML类图中,实体有以下几种`class`、`interface`、`abstract`,而实体中又有属性、方法等元素。类与类之间的关系有:继承、实现、依赖、关联、组合、聚合,下面我们以具体的类图进行说明。

类、抽象类、接口

如下图中为一个类图的基本结构,在第一层表示为类名称(Class Name),而第二层表示该类的各属性,其中`attr1`为**私有属性,其标识为红色框**,访问权限为`private`的属性将不能由子类继承访问。`attr2`属性为包内可见,或可称为在命名空间内可见,但是该权限使用较少。`attr3`表示为静态公有属性,`public`方法或属性均可被子类继承,并且可被外界直接访问,在类图中通常**以圆形表示公有属性或方法**而`static`属性或方法,则会在**方法名或属性名上添加下划线**以示区分。`attr4`访问权限为`protected`,保护属性可被子类继承并访问,但是不可被外界直接访问,在UML类图中通常使用**棱形表示**。在方法中,同样有以上相同的访问权限设置,其标识在形状上大体相同,可相互对照。通常在类图中,我们可能出现`attr:int`及`method(name:String):void`的写法,其表示的意义相同,均为了表示参数、返回值及其类型。

类图

下图为抽象类的表示方法,在抽象类中允许抽象方法的定义,因此在UML类图中,通常以**斜体**表示一个抽象方法,并且**抽象类类名也以斜体表示**,其他表示与类图一至。

抽象类

下图为接口的类图表示,接口仅可定义`public`方法,**除静态公有常量属性外不允许有其他属性**,在类图中**接口类名也以斜体表示**,在某些类图工具中,通常会加上`<<interface>>`的标识。

接口

### 继承、实现、依赖、关联、组合、聚合

继承与实现表示方法类似,区别在于其线条,**继承使用实线而实现使用虚线**,通常而言,在表示类关系时,**虚线的耦合比实线耦合更低**。

如图所示,类`Class`实现了借口`Interface`,以**虚线加三角**表示,而`SubClass`继承了`Class`,则以**实线加三角**表示,其中箭头指向父类。

实现和继承

依赖于关联属于相似的关系,其区别也在于线条,**依赖关系使用虚线**,而**关联关系使用实线**,因此依赖的耦合度比关联更低。从图中可以看出,左侧实线表示了关联关系,而右侧虚线表示了依赖关系。

在具体实现中,关联更多表示为在一个类中使用了另一个类的对象作为属性,如在`ClassA`中有属性`attr`类型为`ClassB`,因此为关联关系。而依赖关系其耦合更低,通常表示在一个类中,**另一个类作为其方法参数、返回值或局部变量**,如类图中所示的`method()`方法。

或在如下代码中体现:

```java

public class ClassA {

  public ClassB method1() {

    return new ClassB();

  }

  public void method2(ClassB c) {

    ...

  }

  public void method3() {

    ClassB b = new ClassB();

    b.xxx;

    ...

  }

}

```

上述三个方法均是依赖关系的体现,表现为三种形式:**返回值、参数、局部对象**。在关联、依赖关系中,箭头方向指向被关联或被依赖的类,如`ClassA`关联`ClassB`则方向为`A->B`,并且在`A`中存储了`B`的对象,依赖等同。并且关联关系中,通常有`1..n`,`0..n`,`1`,`n`等的表示方法,其表示的为数字端的该类对象在关联类中的数量,如下图中,`1`表示在`ClassA`中维持了一个数量为`1`的`ClassB`对象。

*注:关联还能双向关联,多维关联等,该两种可以不加方向箭头。*


依赖关联

而组合及聚合则是关联的更高一层次的体现,其比关联拥有更高的耦合度,在代码层面上,组合及聚合同关联一样,均会导致类属性的增加。在UML类图中,**聚合关系使用空心棱形加箭头形式**表示,而**组合关系使用实心棱形**,从图形上看,实心比组合具有更高的耦合度。

组合聚合

在图中可以看出,在左侧`HAS-A`关系使用了聚合,而`CONTAINS-A`使用了组合。我们可以这样理解这两者的区别:对于聚合关系,其表示了更低的耦合性,其表示的关系是**整体与个体之间的关系**,如部门与职员的关系;而组合关系,其耦合性更强,其**表示的关系是个体与部分的关系**,如人是个体,手是一个个体中的一个必不可少的组成部分。在UML图中,箭头及箭头方向与关联表示的一致。

从代码层面上分析,在`Java`语言中上述两种关系区分并不明确,只能从意义上分析。从生命周期分析,组合关系中"部分"与"个体"生命周期一致,当"个体"消失时,"部分"也无法单独完成服务。而聚合关系,"个体"与"整体"生命周期相互独立,"个体"即使脱离了"整体"仍可单独地完成服务和行为。在`C++`语言中,两种关系可以使用指针、对象引用区分。

**组合、聚合同关联关系的区别在于类层次不同**,关联关系是无法使用组合、聚合表示时的一种类关系,关联关系的两个类只表明两个类有关系,但是**属于同一个类层次**,如头和手,头可以指挥手进行工作,相当于头与手与关联,但是他们对于人而言,都是属于器官层次,层次相同。而**组合聚合,则类之间属于不同层次**,如手和人,部门与职员。

**综上,上述类关系的类关系强弱为:依赖<关联<聚合<组合<实现<继承。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 设计模式的基本原则 设计模式的基本原则非常重要,只要真正深入地理解了设计原则,很多设计模式其实就是原则的应用而已,...
    泥孩儿0107阅读 223评论 0 0
  • 2018-08-15 今日内容 应用YOLO作者官网:https://pjreddie.com/darknet/y...
    MWhite阅读 201评论 0 0
  • 我看过所有离别时的眼神和空气里的日暮 听过所有人的悲泣和愤怒 闻过所有新生的泥土和云朵 我知道 每一条河流都连通你...
    茧鹿阅读 163评论 0 0
  • 78_闲云阅读 359评论 0 1
  • n\长愁天下,短念人间(想黄爷了) 长愁天下,短念人间。 曾经有这么一个人,眼中风情万种。 曾经有这么一个人,手里...
    陈霓裳阅读 9,113评论 0 30