导读

在实际应用(如文件目录树)中,我们遇到的除了树形结构之外,还会遇到森林。直接对森林的操作会更加困难,因此通常会将森林转换为二叉树进行结点的操作。这样,我们通过树与二叉树的转换,森林与二叉树的转换,最后就能实现树与森林的转换。

森林转换为二叉树

森林是由若干棵树组成的,如果给这些树添加一个根结点,就变为一棵树。因此完全可以理解为,森林中的每一个棵树都是兄弟,所以可以按照兄弟的处理办法来操作森林。步骤如下:
* 步骤1 将森林中的每棵树变为二叉树;
* 步骤2 因为转换所得的二叉树的根结点的右子树均为空,故可将各二叉树的根结点视为兄弟从左至右连在一起,就形成了一棵二叉树。

如下图所示:

二叉树转换为森林

判断二叉树转换为树还是森林,标准很简单,那就是看这颗二叉树的根结点有没有右孩子,有就是森林,没有就是树。那么如果转换为森林,步骤如下:
* 步骤1 从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除……,直到所有这些根节点与右孩子的连线都删除为止;
* 步骤2 将每棵分离后的二叉树转换为树。

如下图所示:

享元模式简述

      在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
      在享元模式中,由于要产生各种各样的对象,所以在享元模式中常出现工厂模式。享元的内部状态是用来共享的,享元工厂负责维护一个对象存储池来存放内部状态的对象。



运行结果如图:

优缺点及适用情况
优点:
节省一个复杂的系统中大量的内存空间

适用情况:
如果一个应用程序使用了大量的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式

装饰模式简述

      我们可以通过继承和组合的方式来给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:一、对象之间的关系复杂的话,系统变得复杂不利于维护。二、容易产生“类爆炸”现象。三、是静态的。在这里我们可以通过使用装饰者模式来解决这个问题。
      装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。虽然装饰者模式能够动态将责任附加到对象上,但是他会产生许多的细小对象,增加了系统的复杂度。





运行结果如图:

优缺点及适用情况
优点:
* 使对象功能的扩充更加灵活

适用情况:
* 在程序运行期间,需要某个对象的行为发生一些细小的变化,并且这些变化可以进行组合

代理模式简述

      代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。
下面来看一下示例:

      新建一个people人类,具有买车的行为,所以实现接口BuyCar


      people类不能拥有车,必须经过proxy代理类的认证,符合条件之后才可以拥有车辆,新建一个代理,这个代理类来考察当前的people是否有资格进行买车:

      运行结果如图:

优缺点及适用情况

优点:
      可以隔离目标对象的状态迁移
缺点:
      运用代理模式会使得代理对象与被代理对象造成紧耦合
适用情况:
      当某些事情无法直接完成时

组合模式简述

     组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。它定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。



运行结果如图:

优缺点及适用情况
优点:
* 使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码
* 更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利

缺点:
* 组合模式不容易限制组合中的构件
* 适用情况:
* 当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了

访问者模式简述

      在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误。对于这种问题,访问者模式提供了比较好的解决方案。访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
      访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。我们先看看下面的小例子:

      看一下类B的showA方法,showA方法使用类A作为参数,然后调用类A的method1方法,可以看到,method2方法绕来绕去,无非就是调用了一下自己的method1方法而已。

      下面我们来演示一下访问者模式的过程:

      运行结果如图:

优缺点及适用情况

优点:
      符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展
      扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展
缺点:
      增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改
适用情况:
      假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去
      假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去

模板方法模式简述

      有些时候我们做某几件事情的步骤都差不多,仅有那么一小点的不同,在软件开发的世界里同样如此,如果我们都将这些步骤都一一做的话,费时费力不讨好。所以我们可以将这些步骤分解、封装起来,然后利用继承的方式来继承即可,当然不同的可以自己重写实现嘛!这就是模板方法模式提供的解决方案。
      所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

      运行结果如图:

优缺点及适用情况

优点:
      模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码
      子类实现算法的某些细节,有助于算法的扩展
      通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”
缺点:
      每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象
适用情况:
      在某些类的算法中,用了相同的方法,造成代码的重复
      控制子类扩展,子类必须遵守算法规则

桥接模式简述

如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是将这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。
桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。
我们举个例子,汽车有不同的发动机,有烧油的、烧天然气的、耗电的。
我们先不同桥接模式,看看:

因为不同的种类的车,用的发动机也不同,所以将具体的车建为抽象类,将其中实现Car的方法也抽象化。


运行结果如图:

可以看到,一种车每增加一种发动机就要增加一个子类,到了后期,子类会越来越多,越来越庞大。这种方法不推荐使用。
如果我们把所有的发动机都定义到Car接口中,每种车实现一次就好了


运行结果如图:

      这种方式就没有那么多子类了,每种车只有一个实现类,但是这种方法不符合我们的开放封闭原则,每增加一种发动机,就要修改Car接口,并修改所有实现类。这种方法肯定也是不推荐的。
      接下来我们就要说说桥接模式了。
      桥接模式的概念:桥接模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。




运行结果如图:

可以看到,这样子的话,新建车的种类,丝毫不影响发动机,新建一种发动机,也不影响车。
优缺点及适用情况
优点:
* 面向接口编程,抽象与具体实现分离
* 扩展性好,任意纬度扩展都不需要修改原有系统,符合开闭原则
缺点:
* 较难识别出系统中独立变化的多个纬度
适用情况:
* 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系
* 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合
* 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
* 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者
* 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

策略模式简述

      我们知道一件事可能会有很多种方式来实现它,但是其中总有一种最高效的方式,在软件开发的世界里面同样如此,我们也有很多中方法来实现一个功能,但是我们需要一种简单、高效的方式来实现它,使得系统能够非常灵活,这就是策略模式。
      所以策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,使得算法可以独立于使用它的客户。
      我们看看如下例子:
      刘备要到江东娶老婆了,走之前诸葛亮给赵云三个锦囊妙计,说是按天机拆开能解决棘手问题。场景中出现三个要素:三个妙计(具体策略类)、一个锦囊(环境类)、赵云(调用者)。

      三招下来,搞得的周郎是“赔了夫人又折兵”。

优缺点及适用情况

优点:
      可以动态的改变对象的行为
缺点:
      客户端必须知道所有的策略类,并自行决定使用哪一个策略类
      策略模式将造成产生很多策略类
适用情况:
      当一个应用程序需要实现一种特定的服务或者功能,而且该程序有多种实现方式时使用

状态模式简述

      在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。

      先来看这样一个示例。


      下面使用策略模式重写上面的例子


优缺点及适用情况

优点:
      封装了转换规则
      枚举可能的状态,在枚举状态之前需要确定状态种类
      将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
      允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块
      可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点:
      状态模式的使用必然会增加系统类和对象的个数
      状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
      状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码
适用情况:
      在行为受状态约束的时,且状态不超过5个