您的位置:首页 > 其它

读《大话设计模式》有感之单一、开闭与依赖倒转原则

2013-12-04 11:31 183 查看
  单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。

  其实单一职责就理解而言,并没有什么难度,可以想想看,自己在学习和工作的时候,是同时做一件事情的效率高呢,还是同时做几件事情的效率高呢?毫无疑问,当然是在同一时间只做一件事情的效率更高一点了。因为同时做几件事情,这种并发其实很容易发生错误。

  打个比方,今天自己的任务有:1、和老板汇报工作情况;2、与客户沟通,增进产品销售进度;3、整理上周的材料等等。如果在整理材料的时候突然想到要和顾客沟通,就急急忙忙打电话给顾客了,然后聊完之后,你会还记得材料整理到什么进度了么?很难想起来吧,因此,对于类而言也是一样的,把一个类看做一个自己,如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。因此,在做设计的时候有时让类遵循单一职责也是很有必要的。

  软件设计真正要做的许多内容,就是发现职责并把那些职责互相分离。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责,这时候就可以思考是不是可以把类进行职责分离。

  好了,看完单一职责原理,接下来,来说说开闭原则。

  开放-封闭原则:是说软件实体(类、模块、函数等等)应该可以扩展,但是不可以修改。也就是说对于扩展代码是开放的,然而对于修改代码是封闭的。

  这段话,其实看起来非常好理解,无论是类、模块、函数这些,都可以用面向对象的继承和多态来扩展软件的实体,但是对于软件的实体,就设计层面上而言,是不建议可以修改的。原因很明确,就是你根本不知道是不是在对这些修改之后,其他的代码功能有没有发生改变。所以,如果可以,在后期拓展的时候,增加新的,可以;对以前代码修改,免谈。因为我们谁也不能保证本来测试好的代码,会不会因为这点小小的修改就突然猛增超多的BUG。虽然程序员的一生都在不断和BUG进行斗争的反斗争,但能够减少工作量,对于我们而言也是喜闻乐见的事情。

  我们总在思考,到底肿么样的设计才能面对需求的改变却可以保持相对稳定,从而使得系统可以站在第一个版本以后不断推出新的版本呢?其实开闭原则就是答案。

  因此面对需求,对程序的改动是通过新增新代码进行的,而不是更改现有的代码。

  可是现实到底和想象中的美好世界差的很远的,总是理想很肉感,现实很骨感。无论模块式多么的“封闭”,都会存在一些无法对之封闭的变化,既然不可能完全封闭,那么设计人员就必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。然后等到发生变化时就要立即采取行动。

  简单的说,就是在我们最初写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。我们希望的是在开发工作展开不久就知道可能发生的变化,查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。

  开闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中所呈现出频繁变化的那些部分作出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

  最后,来聊一聊依赖倒转原则。

  依赖倒转原则:A.高层模块不应该依赖低层模块。两个都应该依赖抽象;B.抽象不应该依赖细节,细节应该依赖抽象。

  对于依赖,在学习编程的那段时间,总是对这个很模糊,什么是细节依赖抽象,什么是高层模块不应该依赖低层模块。完全不知道是什么意思,但是在写了一两个小项目后,却慢慢对它有点感悟了,谈不到什么高层次的问题,但是能讲讲简单肤浅的理解。

  之前做了一个考试系统,把批改试卷的功能都放在一个service中,然后让service层直接调用dao层,结果后来换了需求,不再是用oracle的数据库,而是使用mysql的数据库,可是苦逼了,oracle的jbdc和mysql的jbdc有些语法是不一样的,为了防止苦逼需求有发生改变,说有又要用oracle了,所以只能再写一个用于mysql的dao层,然后一个一个地去service层该调用。这个过程不是太难,但是却是很容易出错的。

  针对以上过程,如果当时自己用上了依赖,那么其实就不用很苦逼的该service层了。

  肿么做呢?

  其实就是在dao层加一个dao和daoImp,前者是dao层的接口,后者是dao层接口的实现,只要在service层用接口调用方法,然后再spring中对dao接口的实现进行配置,就ok了。以后要该数据库的实现,就只要在spring中改dao的实现,就能使用改过后的实现了。

  这个就是:抽象不应该依赖细节,细节应该依赖抽象。换句话说就是说要针对接口编程,不要对现实编程。

  很多人都会走进一个思想的误区,比如说现在有两个类:

  鸟:属性:有羽毛,卵生;方法:会飞;鸡:属性:有羽毛,卵生;方法:会吃;

  那么,我们能说鸡是鸟么?从生物学上来说,是的鸡是鸟的一种,即使鸡不会飞,但鸡也是鸟;但是在程序中,我们能说鸡是鸟么?Of course not.鸡不会飞,那么鸡就不是鸟的子类,因为作为一个子类,那么它就一定要会父类会的所有非private方法,有所有父类有的非private属性。

  一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单得说,子类型必须能够替换掉它们的父类型,这就是里氏代换原则。

  里氏代换原则:子类型必须能够替换掉它们的父类型。

  只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。

  依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之,那就是过程化的设计了。

  部分语句摘自《大话设计模式》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: