zoukankan      html  css  js  c++  java
  • 四、坏耦合的原因与解耦(三)

    坏耦合的原因

      1. 依赖他人

      例1.

    void SaveMoney(float money);
    void WithdrawMoney(float money);

      如果有假币出现,那么存钱函数SaveMoney就提前处理了,并不会存进去,WithdrawMoney函数从来没有遇到过假钱,而它并没有处理假钱的能力,也一直没被发现!!!如果有一天WithdrawMoney因为意外把假币给了用户,将不知道怎么处理。根本原因就是,WithdrawMoney一直依赖SaveMoney函数处理假币。

      2. 双向依赖,你中有我,我中有你

      3. 侵占公共资源

      要尽量做到公共资源是不可变的,或者操作它的途径非常有限。

      4. 需求变化

      需求的变化,可能使得原本是好的耦合也会变成坏耦合;

      重构的时候,要尽可能解决可预见的问题;

      一般项目在迭代开发的时候,要做到两分精力放在用户看不到的内部优化中。

    解耦的原则

      (一)  让模块逻辑独立而完整

      解耦的根本目的就是拆除元素之间不必要的联系,一个核心原则就是让每个模块的逻辑独立二完整。即:

    •   所依赖的外部资源尽可能是不变量;
    •   对外体现的特性是“不变量”,让别人可以放心的依赖我。

      函数要把自己需要的数据都明确标识在参数列表里,把自己能提供的全集中在返回值里。

      案例1

    void updatePerson(Array persons){
        DB.open();
        foreach(Person person in persons) {
            update(person);
        }
        DB.close();
    }
    //update函数

     void update(Person person){

      string sql = person.GenerateInsertSQL();
      openedDB.ExecuteSQL(sql);
      }

       update里也有数据库的操作,却无须DB的open和close语句。这里是没有问题的。

      但是update是一个public函数,那么就会有问题。

      如何解决或优化这个问题呢?可以将数据库资源参数化。

    void update(Person person,DBConnection openedDB){
        string sql = person.GenerateInsertSQL();
        openedDB.ExecuteSQL(sql);
    }
    
    
    //之后对update的调用
    foreach(Person person  in persons){
        update(person,sharedDB);
    }

       当程序员对一个类或者一个方法的使用需要额外的记忆时,这不是好代码。

      案例2

      一个人要看书

     (1)原始版本

    Person person = new Person();
    person.ReadBook(book);
    
    void ReadBook(Book book){
    //看书之前要先戴眼镜
        WearGlasses(this.MyGlasses);
        Read(book);
    }

       如果这个人没有眼镜,即 this.MyGlass  变量为null,直接调用person.ReadBook()发现异常,怎么办呢?

      (2)优化版本一:通过属性注入 

     person.MyGlasses = new Glasses();  //虽然简单,但是专业叫法,叫属性注入
     person.ReadBook(book);

           但是这样写也有问题,因为 ReadBook 函数,使用上不应该有隐式的限定条件。

       (3) 优化版本二:通过构造函数的注入

    public Person(Glasses glasses){
        this.MyGlasses  = glasses;
    }

      但是这样仍然存在一个问题:喜欢读书的人毕竟是少数,Person中的很多函数行为,如跑步、吃饭等,并不需要眼镜。

      我们应该让各自的需求各自解决。

       (4)优化版本三:通过普通成员函数的注入 

      对于二,恢复为最初的无参构造函数,并单独为ReadBook函数添加一个glasses参数。      

    void ReadBook(Book book, Glasses glasses){
        WearGlasses(glasses);
        Read(book);
    }

       对函数的调用: 

    person.ReadBook(bookl, new Glasses());

      这样只有需要读书的人,才会被配一副眼镜了,实现资源的精确分配。

      可是呢,现在每次读书都需要配一副新眼镜,太浪费了。

      (4)优化版本四:封装注入 

    person.ReadBook(book, person.MyGlasses);

      每次取自己之前的眼镜最符合现实需求。但是又出现了之前的问题:person.MyGlasses 参数可能为空,怎么办?

      让person.MyGlasses封装的get 函数自己去解决这个逻辑:

    public Glasses MyGlasses{
        get{
            if(this.myGlasses == null){
                this.myGlasses  = new Glassess();
                return this.myGlasses;
            }
        }
    }        

       然后

    void ReadBook(Book book){
        WearGlasses(this.glasses);
        Read(book);
    }

     总结:从优化一到优化三分别是三种依赖注入的手段:属性注入、构造函数注入,普通成员函数注入;让每一个模块独立而完整,是说各自的需求各自解决,每个模块规规矩矩,这个模块被替换掉,也不会有问题。

    (二)让连接桥梁坚固而兼容

      模块好比孤岛,孤岛之间需要桥梁去连接。我们需要这些桥梁坚固(具有不变性),还可以兼容各种岛屿(具有兼容性)。
      需求发生变化,我们要让变化尽量落在岛屿上,而不是桥梁上,因为更换桥梁的成本更高,风险更大!!!

  • 相关阅读:
    Xtreme ToolkitPro 初使用
    VC 播放WAV文件
    Socket Select IO模型
    SpringBoot AOP 记录操作日志、异常日志
    基于 SpringBoot + Vue3.2 + Element Plus 的后台管理系统
    一个无限级树结构配合FlyTreeView的例子
    类似百度搜索的输入框自动完成功能
    PyQuery的安装
    CreateRemoteThread的调试问题
    OD中的自定义函数
  • 原文地址:https://www.cnblogs.com/juanzhi/p/12335746.html
Copyright © 2011-2022 走看看