OO设计原则总结

什么是设计原则? 

设计原则是基本的工具,应用这些规则可以使你的代码更加灵活、更容易维护,更容易扩展。

基本原则

oop

 

  • 封装变化Encapsulate what varies.
  • 面向接口编程而非实现 Code to an interface rather than to an implementation.
  • 优先使用组合而非继承 Favor Composition Over Inheritance

SRP: The single responsibility principle 单一职责

系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成。

Every object in your system should have a single responsibility ,and all the object s services should  be focused on carrying out that single responsibility .

 

  1. 每一个职责都是一个设计的变因,需求变化的时候,需求变化反映为类职责的变化。当你系统里面的对象都只有一个变化的原因的时候,你就已经很好的遵循了SRP原则。
  2. 如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化就可能削弱或者抑制这个类其它职责的能力。这种设计会导致脆弱的设计。当变化发生的时候,设计会遭到意想不到的破坏。
  3. SRP 让这个系统更容易管理维护,因为不是所有的问题都搅在一起。
  4. 内聚Cohesion 其实是SRP原则的另外一个名字.你写了高内聚的软件其实就是说你很好的应用了SRP原则。
  5. 怎么判断一个职责是不是一个对象的呢?你试着让这个对象自己来完成这个职责,比如:“书自己阅读内容”,阅读的职责显然不是书自己的。
  6. 仅当变化发生时,变化的轴线才具有实际的意义,如果没有征兆,那么应用SRP或者任何其它的原则都是不明智的。

DRY : Don't repeat yourself Principle

通过抽取公共部分放置在一个地方避免代码重复.

Avoid duplicate code by abstracting out things that are common and placing those thing in a single location .

 

  1. DRY 很简单,但却是确保我们代码容易维护和复用的关键。
  2. 你尽力避免重复代码候实际上在做一件什么事情呢?是在确保每一个需求和功能在你的系统中只实现一次,否则就存在浪费!系统用例不存在交集,所以我们的代码更不应该重复,从这个角度看DRY可就不只是在说代码了。
  3. DRY 关注的是系统内的信息和行为都放在一个单一的,明显的位置。就像你可以猜到正则表达式在.net中的位置一样,因为合理所以可以猜到。
  4. DRY 原则:如何对系统职能进行良好的分割!职责清晰的界限一定程度上保证了代码的单一性。

OCP : Open-Close Principle开闭原则

类应该对修改关闭,对扩展打开;

Classes should be open for extension ,and closed  for modification .

 

  1. OCP 关注的是灵活性,改动是通过增加代码进行的,而不是改动现有的代码;
  2. OCP的应用限定在可能会发生的变化上,通过创建抽象来隔离以后发生的同类变化
  3. OCP原则传递出来这样一个思想:一旦你写出来了可以工作的代码,就要努力保证这段代码一直可以工作。这可以说是一个底线。稍微提高一点要求,一旦我们的代码质量到了一个水平,我们要尽最大努力保证代码质量不回退。这样的要求使我们面对一个问题的时候不会使用凑活的方法来解决,或者说是放任自流的方式来解决一个问题;比如代码添加了无数对特定数据的处理,特化的代码越来越多,代码意图开始含混不清,开始退化。
  4. OCP 背后的机制:封装和抽象;封闭是建立在抽象基础上的,使用抽象获得显示的封闭;继承是OCP最简单的例子。除了子类化和方法重载我们还有一些更优雅的方法来实现比如组合;

怎样在不改变源代码(关闭修改)的情况下更改它的行为呢?答案就是抽象,OCP背后的机制就是抽象和多态

  1. 没有一个可以适应所有情况的贴切的模型!一定会有变化,不可能完全封闭.对程序中的每一个部分都肆意的抽象不是一个好主意,正确的做法是开发人员仅仅对频繁变化的部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。
  2. OCP是OOD很多说法的核心,如果这个原则有效应用,我们就可以获更强的可维护性 可重用 灵活性 健壮性 LSP是OCP成为可能的主要原则之一

LSP: The Liskov substitution principle

子类必须能够替换基类。

Subtypes must be substitutable  for their base types.

 

  1. LSP关注的是怎样良好的使用继承.
  2. 必须要清楚是使用一个Method还是要扩展它,但是绝对不是改变它。
  3. LSP清晰的指出,OOD的IS-A关系是就行为方式而言,行为方式是可以进行合理假设的,是客户程序所依赖的。
  4. LSP让我们得出一个重要的结论:一个模型如果孤立的看,并不具有真正意义的有效性。模型的有效性只能通过它的客户程序来表现。必须根据设计的使用者做出的合理假设来审视它。而假设是难以预测的,直到设计臭味出现的时候才处理它们。
  5. 对于LSP的违反也潜在的违反了OCP

DIP:依赖倒置原则

高层模块不应该依赖于底层模块 二者都应该依赖于抽象

抽象不应该依赖于细节 细节应该依赖于抽象

  1. 什么是高层模块?高层模块包含了应用程序中重要的策略选择和业务模型。这些高层模块使其所在的应用程序区别于其它。
  2. 如果高层模块依赖于底层模块,那么在不同的上下文中重用高层模块就会变得十分困难。然而,如果高层模块独立于底层模块,那么高层模块就可以非常容易的被重用。该原则就是框架设计的核心原则。
  3. 这里的倒置不仅仅是依赖关系的倒置也是接口所有权的倒置。应用了DIP我们会发现往往是客户拥有抽象的接口,而服务者从这些抽象接口派生。
  4. 这就是著名的Hollywood原则:"Don't call us we'll call you."底层模块实现了在高层模块声明并被高层模块调用的接口。
  5. 通过倒置我们创建了更灵活 更持久更容易改变的结构
  6. DIP的简单的启发规则:依赖于抽象;这是一个简单的陈述,该规则建议不应该依赖于具体的类,也就是说程序汇总所有的依赖都应该种植于抽象类或者接口。
  7. 如果一个类很稳定,那么依赖于它不会造成伤害。然而我们自己的具体类大多是不稳定的,通过把他们隐藏在抽象接口后面可以隔离不稳定性。
  8. 依赖倒置可以应用于任何存在一个类向另一个类发送消息的地方
  9. 依赖倒置原则是实现许多面向对象技术多宣称的好处的基本底层机制,是面向对象的标志所在。

ISP:接口隔离原则

不应该强迫客户程序依赖它们不需要的使用的方法。

 

  1. 接口不是高内聚的,一个接口可以分成N组方法,那么这个接口就需要使用ISP处理一下。
  2. 接口的划分是由使用它的客户程序决定的,客户程序是分离的接口也应该是分离的。
  3. 一个接口中包含太多行为时候,导致它们的客户程序之间产生不正常的依赖关系,我们要做的就是分离接口,实现解耦。
  4. 应用了ISP之后,客户程序看到的是多个内聚的接口。
#########################

视角的力量--再说OO设计原则

本文内容包括:

1.为什么我们过早的纠缠于细节?问题的本质是什么?

2.救命稻草--Martin Fowler的三层视角理论

3.三层视角--回头再说OO设计原则

 

为什么我们过早的纠缠于细节?问题的本质是什么?
         做设计时过早的关注细节几乎是多数程序员的泥沼,也是我自己的顽疾。就像我刚开始工作不久要做一个自动更新的系统,设计会议上开发组老大定了使用FTP协议完成,你知道我脑袋里面想的是什么?--“Indy组件好像不支持中文”…...

      过早的关注细节,大体上可能有两种原因:1.经验丰富,举一反三,纲举目张,各种技术玄妙如数家珍 2.没有什么经验,只知道点技术细节,难以跳出思维桎梏;我知道我是后者,以前是现在也是。

       人是有惰性的,人们习惯性的做自己熟练精通的事情。所以做设计的时候,当对大框架缺少把握能力的时候,潜意识里我更愿意去思考那些细节。这是在偷懒,所有的技术细节、问题都是没有疑问的(我们不是做科研),或者是有疑问你可以很容易获得解答无论是开发文档还是在社区。细节的解决方案总是显而易见,但并非肯定是最好的入手点。

       有时候我真的要做设计了,我想要避免陷入“细节泥沼”可是我还是无意中把细节扯进来,这是为什么?剖析自己,我知道这是因为我的思考是平铺的,是没有层次的,所有的问题搅在一起,做设计的时候难免拖泥带水,泥沙俱下。

       思考没有层次这就是问题本质所在,我要做好设计,而思维方式上的缺陷成为我的命门所在,我该怎么办?说实话我一直在走弯路,而且不知道现在的这条路是否对头。
        “善良的人在追求的中纵然迷茫,却终将意识到有一条正途.”-----《浮士德》
 

救命稻草--Martin Fowler的三层视角理论

     Martin Fowler在他的著作 《UML Distilled》中提到了三层视角(perspective):概念视角,规约视角,实现视角。

使用三种视角看软件开发,我们可以得到这样的描述:

概念视角:呈现所研究领域中的各种概念,得出概念模型的时候应该尽量少德或者不考虑它的实现,这个视角要回答的问题是:软件要负责什么?是策略性的结论

规约视角:我们现在考虑的是软件,但是我们关注的是软件的接口而不是实现。规约视角要回答的问题是:怎么使用软件?这个层次关注的是软件各部分的交流。

实现:这时我们考虑的是代码本身但是许多方面我们使用规约视角可能会更好,软件在规约层交流在实现层执行。

 

视角帮助我们将问题划分层次,隔离

        从上面的描述我们很明显得看到“软件开发”所设计牵扯的问题已经被划分到三个不同的层次,在每一个层次我们都要有特定的思考成果。在高层没有思考成熟的时候我们不往下一个层次进行,按照这样一个原则,细节被隔离在思维的围墙之外。
       下面一个问题就是,在设计中过程,三种视角对问题进行层次划分能起到什么作用?
 

三层视角--回头再说OO设计原则

         概念视角我们得到了领域的各种概念,对象要负责什么?这是一个高层策略,它通常是高度抽象的,是一个策略性的结论,关键一点:它是稳定的。只要概念不变请求者就和细节的变化隔离开了;细节问题的处理应该尽可能的往后推迟。关注对象要做什么,而不是怎么做,将这些细节实现隐藏起来帮助我们免于过早的介入细节。关注对象要做什么,就是在定义对象的责任,理解对象的最佳方式就是把它看作有责任的东西。

       规约视角,对象是一组可以被其它对象或对象自己调用的方法(也就是行为,怎么使用软件?);对象是有责任的我只需要关注对象的公共接口-这是我要求对象完成某些工作的交流渠道。只要对象的接口告诉我们它可以完成某项职责,不关注对象是怎样运行的。关注动机而非实现,是基本的OO设计原则,将实现隐藏在接口之后实际上是将对象的实现与它们的对象解耦了。

      实现视角,对象是代码和数据,以及它们之间的计算交互(软件怎么履行自己的责任?);走到这里我们才开始讨论实现细节。

      总结一下,从Martin Fowler的三个视角来看对象:

在概念层次上,对象是一组责任

规约层次上,对象是一组可以被其他对象或者对象自己调用的方法

在实现层次上对象是代码和数据,以及它们的计算交互

 

到这里我们需要对OO基本原则做一个再思考了:

        从概念层到规约层我们的思考成果都是抽象的,他们是一个个抽象的概念,是一个个的行为;我们在概念层是对象的责任清晰化,在规约层我们看拥有不同责任的对象之间是怎样协作的。协作方式就是一种对象与对象之间的契约。对象的责任怎么划分?对象应该对自己负责,对象的责任应该尽量单一,这就是SRP原则。规约层我们定义的是对象间的交互契约,也就是接口,关注接口而非实现,我们做到了。

       概念层是最稳定的,规约层次之,实现是最不稳定的。所以高层模块不应该依赖低层模块,两者都应该依赖于抽象。细节应该依赖于抽象,这就是DIP原则。这个原则隐含的意思是:对象之间只在概念层次存在耦合,在实现层次不能耦合!

       我们定义的所有交互都是使用基类,那如果实现的时候某一个子类不能完全代替基类呢?那是不是所有的交互契约都出现问题了?一个从基类派生的子类应该支持基类的所有行为,子类必能替换基类,这就是LSP原则。

        可以看出对象的责任和交互被尽早的定义出来,这个过程中我们没有考虑具体对象在什么时期创建,映射到我们的设计过程中,创建型设计模式的应用往往是在设计的后期。

 

       结论:

OO设计原则总结》一文中我提出了一个问题:如何更好的使用这些原则?怎样在实践中遵守这些原则,使用三种视角思考问题就是答案之一; 做设计时,注意力首先要放在高层关系上;  在面对对象设计开发过程中以概念 规约 实现三个视角类思考问题能提高我们的设计能力,避免过早陷入细节泥沼。

 

附: Martin Fowler著作 《UML Distilled》提到三种视角(perspective)的段落:
 

Following the lead of Steve Cook and John Daniels (1994), I say that there are three perspectives you can use in drawing class diagrams- or indeed any model, but this breakdown is most noticeable in connection with class diagrams.

  • Conceptual. 
    If you take the conceptual perspective, you draw a diagram that represents the concepts in the domain under study. These concepts will naturally relate to the classes that implement them, but there is often no direct mapping. Indeed, a conceptual model should be drawn with little or no regard for the software that might implement it, so it can be considered language-independent. (Cook and Daniels call this the essential perspective.)
  • Specification. 
    Now we are looking at software, but we are looking at the interfaces of the software, not the implementation. Object-oriented development puts a great emphasis on the difference between interface and implementation, but this is often overlooked in practice because the notion of class in an OO language combines both interface and implementation. This is a shame, because the key to effective OO programming is to program to a class's interface rather than to its implementation. There is a good discussion of this in the first chapter of Gamma, Helm, Johnson, and Vlissides (1995). You often hear the word "type" used to talk about an interface of a class; a type can have many classes that implement it, and a class can implement many types.
  • Implementation. 
    In this view, we really do have classes and we are laying the implementation bare. This is probably the perspective used most often, but in many ways the specification perspective is often a better one to take.

Understanding perspective is crucial to both drawing and reading class diagrams. Unfortunately, the lines between the perspectives are not sharp, and most modelers do not take care to get their perspective sorted out when they are drawing. Although I've found that this often does not matter too much between the conceptual perspective and the specification perspective, it is very important to separate the specification perspective and the implementation perspective.

As I talk about class diagrams further, I will stress how each element of the technique depends heavily on the perspective.

Perspective is not part of the formal UML, but I have found it extremely valuable when modeling and when reviewing models. The UML can be used with all three perspectives. By tagging classes with a stereotype (see page 79), you can provide an indication of the perspective. You mark classes with <<implementation class>> to show the implementation perspective, and with <<type>> for the specification and conceptual perspectives.


阅读更多
个人分类: Programming
上一篇python list排序
下一篇python的模块base64
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭