• 设计模式-入门


    设计模式入门

    简介

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

    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设计模式》

  • 相关阅读:
    LVM 扩容硬盘笔记
    jupyter notebook 远程访问
    samba 配置文件详解
    linux 网络挂载 windows 共享文件夹
    cmder 与 win10 wsl ( 当前目录打开wsl)
    vscode for latex
    Python 使用代理
    Python Signal(信号) 异步系统事件
    centos7 install magento
    lua笔记
  • 原文地址:https://www.cnblogs.com/marton/p/11334111.html
Copyright © 2020-2023  润新知