设计模式-入门

设计模式入门

简介

设计模式,是对前人遇到问题并顺利解决问题方案的一种总结,是一种经验复用。

UML关系图

1)泛化,可以简单地理解为继承关系;

2)实现,一般是接口和实现类之间的关系;

3)关联,一种拥有关系,比如老师类中有学生列表,那么老师类和学生类就是拥有关系;

4)聚合,整体与部分的关系,但是整体和部分是可以分离而独立存在的,如汽车类和轮胎类;

5)组合,整体与部分的关系,但是二者不可分离,分离了就没有意义了,例如,公司类和部门类,没有公司就没有部门;

6)依赖,一种使用关系,例如创建 A 类必须要有 B 类。

案例

下面我们从一个游戏说起

需求:做一个模拟鸭子的游戏,游戏中有各种鸭子,一边游泳,一边呱呱叫。

实现:设计一个鸭子超类,让各种鸭子继承这个超类。

实现说明:

1)由于所有的鸭子都会呱呱叫和游泳,所以这部分代码由超类来具体实现

2)由于每种鸭子的外观各不相同,所以超类中的display()方法是抽象的,每个鸭子子类负责自己的display()方法的具体实现

需求变更:需要会飞的鸭子

具体实现:在超类Duck中加入fly()方法,然后让所有的鸭子子类都会继承fly()方法

发现问题:并非所有的Duck子类都会飞,会使得某些不适合该行为的子类也具有该行为

这同时也说明一个问题,在涉及代码维护时,为了复用代码的目的而使用继承,有时结局并不完美。

分析

1)对代码的局部修改影响层面并不只是局部,在超类中加入fly()会导致所有的子类都具有fly()

2)如果在某些不需要fly()方法的子类中覆盖掉此方法,会怎么样

            V

   如果我加入一个橡皮鸭,只会吱吱叫,不会飞,需要覆盖超类quack()的方法,改为吱吱叫,覆盖超类fly()的方法,改为什么都不做

   如果我再加入一个木头鸭子,不会飞也不会叫,需要同时覆盖fly()和quack(),改为什么都不做

   每当加入一个新的鸭子时都要检查是否覆盖fly()和quack(),这简直是无穷无尽的噩梦

            V

   我们发现利用继承提供Duck的行为,会导致代码在多个子类中重复

      运行时的行为不容易改变,很难知道鸭子的全部行为

      改变会牵一发而动全身,造成其他鸭子不想要的改变

添加需求:每六个月更新一次产品

具体实现:把fly()从超类中提取出来放进一个Flyable接口中,只有实现此接口的鸭子类型才会飞,quack()亦如此

     需要让某些鸭子子类可以飞或可以叫,而不是全部

 

分析

   Flyable和Quackable能够解决一部分问题,但是却无法造成代码复用,

   在会飞的鸭子子类中,鸭子的飞行动作可能有多种变化,

   比如,50个子类实现Flyable接口后都需要稍微修改一下飞行的行为

                    V

   我们希望有一种方法,让我们用一种对既有代码影响最小的方式来修改软件

小结

继承并不能很好的解决问题,因为鸭子的行为在子类中不断的改变,并且让所有的子类都具有这些行为并不恰当,

Flyable和Quackable接口一开始似乎不错,解决了只有会飞的鸭子才继承Flyable接口,但是JAVA接口不具有实现具体代码的功能,

所有继承接口无法实现代码的复用,这意味着无论何时你需要修改某个行为,必须得在每一个定义此行为的类中去修改它。

设计原则一:将变化的部分与不变的部分分离

软件是需要成长和改变的,否则就会dead。

有一个设计原则可以帮助我们解决现在遇到的问题 ,把软件中可能需要变化的部分抽离并封装起来,

以便以后可以轻易的改动或扩充这部分,并不影响其他不需要改变的部分,系统因此变得具有弹性。

变化的部分与不变的部分

那么针对我们上面的Duck超类,需要改变的部分是哪些呢?

分析

    Duck类中的fly()和quack()会随着鸭子的不同而改变

              V

    为了把fly()和quack()这两个行为从Duck中分离出来

    我们将建立两组新类来各自代表fly()行为和quack()行为

    多种行为的实现分别放在这两组类中

变化的部分:fly()和quack()

不变的部分:Duck类

具体实现:建立两组类,一组fly()相关,一组quack()相关,每一组类实现各自的动作,

比如有一个类实现“呱呱叫”,有一个类实现“吱吱叫”,还有一个类实现“安静”

设计原则二:针对接口编程,而不是针对实现编程 

设计鸭子的行为

  鸭子的行为没有弹性

  我们希望指定行为到鸭子的实例中

      V

  设计一个新的绿头鸭

  指定特定类型的飞行行为给它

  顺便让鸭子的行为可以动态改变

      V

  利用接口代表每个行为,比如FlyBehavior, QuackBehavior

  行为的每个实现都将实现其中一个接口

      V

  Duck类不负责实现Flying和Quacking接口

  而是由我们制造一组其他行为类去专门实现FlyBehavior和 QuackBehavior

      V

  鸭子的行为被放在分开的类中,此类专门提供某行为接口的实现

  这样,鸭子就不需要知道行为的具体实现细节

以往做法

行为来自Duck超类的具体实现或是继承某个接口并由子类自己实现,这两种做法都依赖于实现,

使得我们被实现绑得死死的,只能写更多的代码去更改行为。

现在的做法

鸭子的子类将使用接口(FlyBehavior, QuackBehavior)表示行为,实际的实现不会被绑死在鸭子的子类中,

特定的具体行为编写在实现了接口(FlyBehavior, QuackBehavior)的类中。

实现说明

1)所有的飞行类都实现FlyBehavior接口,所有新的分行类都必须实现fly()方法

2)这样的设计可以让fly()和quack()的行为被其他对象复用,因为这些行为已与鸭子类无关

3)我们在新增行为时,不会影响到现有的行为类,也不会影响使用到飞行行为的鸭子类

针对接口编程的理解

针对接口编程的真正意思是针对类型编程。

针对接口编程,而不是针对实现编程。

针对接口变成的关键在于多态,利用多态,程序就可以针对超类编程,执行时会根据实际状况执行到

具体实现类的行为,而不是死死绑定在超类的行为上。

更明确的说,变量的声明应该是一个接口型或抽象类类型,如此只要是具体实现此接口或抽象类的类所产生的实例对象,

都可以指定给这个变量,这意味着声明类时不用理会以后执行时的真正对象类型。

这样做的好处是,子类实例化的动作不需要在代码中硬编码,而是在运行时根据需要指定具体的实现类对象。

我们不知道实际的子类型是什么,我们只关心它知道如何正确的进行调用方法。

设计原则三: 多用组合,少用继承

分析

  每一个鸭子都有一个FlyBehavior对象和一个QuackBehavior对象

  将飞行和呱呱叫的行为委托给它们处理

          V

  鸭子的行为不是继承来的,而是通过适当的行为对象组合而来

优点

使用组合建立的系统具有很大的弹性;

不仅可以将算法族封装成类,还可以在运行时动态的改变其行为

参考资料:经典设计模式实战

参考资料:《Head First设计模式》

原文地址:https://www.cnblogs.com/marton/p/11334111.html