四、坏耦合的原因与解耦(三)

坏耦合的原因

  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);
}

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

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

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

 

posted @ 2020-02-20 15:04  天凉好个秋秋  阅读(306)  评论(0编辑  收藏  举报