【转】一些软件设计的原则

      之前本站向你们介绍过一些软件开发的原则,好比优质代码的十诫Unix传奇(下篇)中因此说的UNIX的设计原则。相信你们从中可以从中学了解到一些设计原理方面的知识,正如我在《再谈“我是怎么招聘程序”》中所说的,一个好的程序员一般由其操做技能、知识水平,经验层力和能力四个方面组成。在这里想和你们说说设计中的一些原则,我认为这些东西属于长期经验总结出来的知识。这些原则,每个程序员都应该了解。可是请不要教条主义,在使用的时候仍是要多多考虑实际状况。其实,下面这些原则,不仅仅只是软件开发,能够推广到其它生产活动中,甚至咱们的生活中。html

Don’t Repeat Yourself (DRY)

DRY 
是一个最简单的法则,也是最容易被理解的。但它也多是最难被应用的(由于要作到这样,咱们须要在泛型设计上作至关的努力,这并非一件容易的事)。它意味着,当咱们在两个或多个地方的时候发现一些类似的代码的时候,咱们须要把他们的共性抽象出来形一个惟一的新方法,而且改变现有的地方的代码让他们以一些合适的参数调用这个新的方法。java

参考:http://en.wikipedia.org/wiki/Don%27t_repeat_yourself程序员

Keep It Simple, Stupid (KISS)

KISS原则在设计上可能最被推崇的,在家装设计,界面设计 ,操做设计上,复杂的东西愈来愈被众人所BS了,而简单的东西愈来愈被人所承认,好比这些UI的设计和咱们中国网页(尤为是新浪的网页)者是负面的例子。“宜家”(IKEA)简约、效率的家居设计、生产思路;“微软”(Microsoft)“所见即所得”的理念;“谷歌”(Google)简约、直接的商业风格,无一例外的遵循了“kiss”原则,也正是“kiss”原则,成就了这些看似神奇的商业经典。而苹果公司的iPhone/iPad将这个原则实践到了极至。web

把一个事情搞复杂是一件简单的事,但要把一个复杂的事变简单,这是一件复杂的事。shell

参考:http://en.wikipedia.org/wiki/KISS_principle数据库

Program to an interface, not an implementation

这是设计模式中最根本的哲学,注重接口,而不是实现,依赖接口,而不是实现。接口是抽象是稳定的,实现则是多种多样的。之后面咱们会面向对象的SOLID原则中会提到咱们的依赖倒置原则,就是这个原则的的另外一种样子。还有一条原则叫 
Composition over 
inheritance(喜欢组合而不是继承),这两条是那23个经典设计模式中的设计原则。
编程

Command-Query Separation (CQS)  – 命令-查询分离原则

  • 查询:当一个方法返回一个值来回应一个问题的时候,它就具备查询的性质;
  • 命令:当一个方法要改变对象的状态的时候,它就具备命令的性质;

一般,一个方法多是纯的Command模式或者是纯的Query模式,或者是二者的混合体。在设计接口时,若是可能,应该尽可能使接口单一化,保证方法的行为严格的是命令或者是查询,这样查询方法不会改变对象的状态,没有反作用,而会改变对象的状态的方法不可能有返回值。也就是说:若是咱们要问一个问题,那么就不该该影响到它的答案。实际应用,要视具体状况而定,语义的清晰性和使用的简单性之间须要权衡。将Command和Query功能合并入一个方法,方便了客户的使用,可是,下降了清晰性,并且,可能不便于基于断言的程序设计而且须要一个变量来保存查询结果。设计模式

在系统设计中,不少系统也是以这样原则设计的,查询的功能和命令功能的系统分离,这样有则于系统性能,也有利于系统的安全性。浏览器

参考:http://en.wikipedia.org/wiki/Command-query_separation安全

You Ain’t Gonna Need It (YAGNI)

这个原则简而言之为——只考虑和设计必须的功能,避免过分设计。只实现目前须要的功能,在之后您须要更多功能时,能够再进行添加。

  • 如无必要,勿增复杂性。
  • 软件开发先是一场沟通博弈。

之前本站有一篇关于过分重构的文章,这个示例就是这个原则的反例。而,WebSphere的设计者就表示过他过分设计了这个产品。咱们的程序员或是架构师在设计系统的时候,会考虑不少扩展性的东西,致使在架构与设计方面使用了大量折衷,最后致使项目失败。这是个使人感到讽刺的教训,由于原本但愿尽量延长项目的生命周期,结果反而缩短了生命周期。

参考:http://en.wikipedia.org/wiki/You_Ain%27t_Gonna_Need_It

Law of Demeter – 迪米特法则

迪米特法则(Law of Demeter),又称“最少知识原则”(Principle of Least 
Knowledge),其来源于1987年荷兰大学的一个叫作Demeter的项目。Craig Larman把Law of 
Demeter又称做“不要和陌生人说话”。在《程序员修炼之道》中讲LoD的那一章叫做“解耦合与迪米特法则”。关于迪米特法则有一些很形象的比喻:

  • 若是你想让你的狗跑的话,你会对狗狗说仍是对四条狗腿说?
  • 若是你去店里买东西,你会把钱交给店员,仍是会把钱包交给店员让他本身拿?

和狗的四肢说话?让店员本身从钱包里拿钱?这听起来有点荒唐,不过在咱们的代码里这几乎是见怪不怪的事情了。

对于LoD,正式的表述以下:

对于对象 ‘O’ 中一个方法’M',M应该只可以访问如下对象中的方法:

  1. 对象O;
  2. 与O直接相关的Component Object;
  3. 由方法M建立或者实例化的对象;
  4. 做为方法M的参数的对象。

在《Clean Code》一书中,有一段Apache framework中的一段违反了LoD的代码:

final String outputDir = 
ctxt.getOptions().getScratchDir().getAbsolutePath();

这么长的一串对其它对象的细节,以及细节的细节,细节的细节的细节……的调用,增长了耦合,使得代码结构复杂、僵化,难以扩展和维护。

在《重构》一书中的代码的环味道中有一种叫作“Feature Envy”(依恋情结),形象的描述了一种违反了LoC的状况。Feature 
Envy就是说一个对象对其它对象的内容更有兴趣,也就是说总是羡慕别的对象的成员、结构或者功能,大老远的调用人家的东西。这样的结构显然是不合理的。咱们的程序应该写得比较“害羞”。不能像前面例子中的那个不把本身当外人的店员同样,拿过客人的钱包本身把钱拿出来。“害羞”的程序只和本身最近的朋友交谈。这种状况下应该调整程序的结构,让那个对象本身拥有它羡慕的feature,或者使用合理的设计模式(例如Facade和Mediator)。

参考:http://en.wikipedia.org/wiki/Principle_of_Least_Knowledge

面向对象的S.O.L.I.D 原则

通常来讲这是面向对象的五大设计原则,可是,我以为这些原则可适用于全部的软件开发。

Single Responsibility Principle (SRP) – 职责单一原则

关于单一职责原则,其核心的思想是:一个类,只作一件事,并把这件事作好,其只有一个引发它变化的缘由。单一职责原则能够看做是低耦合、高内聚在面向对象原则上的引伸,将职责定义为引发变化的缘由,以提升内聚性来减小引发变化的缘由。职责过多,可能引发它变化的缘由就越多,这将致使职责依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一职责,一般意味着单一的功能,所以不要为一个模块实现过多的功能点,以保证明体只有一个引发它变化的缘由。

  • Unix/Linux是这一原则的完美体现者。各个程序都独立负责一个单一的事。
  • Windows是这一原则的反面示例。几乎全部的程序都交织耦合在一块儿。

Open/Closed Principle (OCP) – 开闭原则

关于开发封闭原则,其核心的思想是:模块是可扩展的,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

  • 对扩展开放,意味着有新的需求或变化时,能够对现有代码进行扩展,以适应新的状况。
  • 对修改封闭,意味着类一旦设计完成,就能够独立完成其工做,而不要对类进行任何修改。

对于面向对象来讲,须要你依赖抽象,而不是实现,23个经典设计模式中的“策略模式”就是这个实现。对于非面向对象编程,一些API须要你传入一个你能够扩展的函数,好比咱们的C 
语言的qsort()容许你提供一个“比较器”,STL中的容器类的内存分配,ACE中的多线程的各类锁。对于软件方面,浏览器的各类插件属于这个原则的实践。

Liskov substitution principle (LSP) – 里氏代换原则

软件工程大师Robert C. Martin把里氏代换原则最终简化为一句话:“Subtypes must be substitutable for 
their base 
types”。也就是,子类必须可以替换成它们的基类。即:子类应该能够替换任何基类可以出现的地方,而且通过替换之后,代码还能正常工做。另外,不该该在代码中出现if/else之类对子类类型进行判断的条件。里氏替换原则LSP是使代码符合开闭原则的一个重要保证。正是因为子类型的可替换性才使得父类型的模块在无需修改的状况下就能够扩展。

这么说来,彷佛有点教条化,我很是建议你们看看这个原则个两个最经典的案例——“正方形不是长方形”和“鸵鸟不是鸟”。经过这两个案例,你会明白《墨子 
小取》中说的 
——“娣,美人也,爱娣,非爱漂亮人也….盗,人也;恶盗,非恶人也。”——妹妹虽然是美人,但喜欢妹妹并不表明喜欢美人。盗贼是人,但讨厌盗贼也并不表明就讨厌人类。这个原则让你考虑的不是语义上对象的间的关系,而是实际需求的环境。

在不少状况下,在设计初期咱们类之间的关系不是很明确,LSP则给了咱们一个判断和设计类之间关系的基准:需不须要继承,以及怎样设计继承关系。

Interface Segregation Principle (ISP) – 接口隔离原则

接口隔离原则意思是把功能实如今接口中,而不是类中,使用多个专门的接口比使用单一的总接口要好。

举个例子,咱们对电脑有不一样的使用方式,好比:写做,通信,看电影,打游戏,上网,编程,计算,数据等,若是咱们把这些功能都声明在电脑的抽类里面,那么,咱们的上网本,PC机,服务器,笔记本的实现类都要实现全部的这些接口,这就显得太复杂了。因此,咱们能够把其这些功能接口隔离开来,好比:工做学习接口,编程开发接口,上网娱乐接口,计算和数据服务接口,这样,咱们的不一样功能的电脑就能够有所选择地继承这些接口。

这个原则能够提高咱们“搭积木式”的软件开发。对于设计来讲,Java中的各类Event 
Listener和Adapter,对于软件开发来讲,不一样的用户权限有不一样的功能,不一样的版本有不一样的功能,都是这个原则的应用。

Dependency Inversion Principle (DIP) – 依赖倒置原则

高层模块不该该依赖于低层模块的实现,而是依赖于高层抽象。

举个例子,墙面的开关不该该依赖于电灯的开关实现,而是应该依赖于一个抽象的开关的标准接口,这样,当咱们扩展程序的时候,咱们的开关一样能够控制其它不一样的灯,甚至不一样的电器。也就是说,电灯和其它电器继承并实现咱们的标准开关接口,而咱们的开关产商就可不须要关于其要控制什么样的设备,只须要关心那个标准的开关标准。这就是依赖倒置原则。

这就好像浏览器并不依赖于后面的web服务器,其只依赖于HTTP协议。这个原则实在是过重要了,社会的分工化,标准化都是这个设计原则的体现。

参考:http://en.wikipedia.org/wiki/Solid_(object-oriented_design

Common Closure Principle(CCP)– 共同封闭原则

一个包中全部的类应该对同一种类型的变化关闭。一个变化影响一个包,便影响了包中全部的类。一个更简短的说法是:一块儿修改的类,应该组合在一块儿(同一个包里)。若是必须修改应用程序里的代码,咱们但愿全部的修改都发生在一个包里(修改关闭),而不是遍及在不少包里。CCP原则就是把由于某个一样的缘由而须要修改的全部类组合进一个包里。若是2个类从物理上或者从概念上联系得很是紧密,它们一般一块儿发生改变,那么它们应该属于同一个包。

CCP延伸了开闭原则(OCP)的“关闭”概念,当由于某个缘由须要修改时,把须要修改的范围限制在一个最小范围内的包里。

参考:http://c2.com/cgi/wiki?CommonClosurePrinciple

Common Reuse Principle (CRP) – 共同重用原则

包的全部类被一块儿重用。若是你重用了其中的一个类,就重用所有。换个说法是,没有被一块儿重用的类不该该被组合在一块儿。CRP原则帮助咱们决定哪些类应该被放到同一个包里。依赖一个包就是依赖这个包所包含的一切。当一个包发生了改变,并发布新的版本,使用这个包的全部用户都必须在新的包环境下验证他们的工做,即便被他们使用的部分没有发生任何改变。由于若是包中包含有未被使用的类,即便用户不关心该类是否改变,但用户仍是不得不升级该包并对原来的功能加以从新测试。

CCP则让系统的维护者受益。CCP让包尽量大(CCP原则加入功能相关的类),CRP则让包尽量小(CRP原则剔除不使用的类)。它们的出发点不同,但不相互冲突。

参考:http://c2.com/cgi/wiki?CommonReusePrinciple

Hollywood Principle – 好莱坞原则

好莱坞原则就是一句话——“don’t call us, we’ll call 
you.”。意思是,好莱坞的经纪人们不但愿你去联系他们,而是他们会在须要的时候来联系你。也就是说,全部的组件都是被动的,全部的组件初始化和调用都由容器负责。组件处在一个容器当中,由容器负责管理。

简单的来说,就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓“控制反转”的概念所在:

  1. 不建立对象,而是描述建立对象的方式。
  2. 在代码中,对象与服务没有直接联系,而是容器负责将这些联系在一块儿。

控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。

好莱坞原则就是IoC(Inversion of Control)或DI(Dependency Injection 
)的基础原则。这个原则很像依赖倒置原则,依赖接口,而不是实例,可是这个原则要解决的是怎么把这个实例传入调用类中?你可能把其声明成成员,你能够经过构造函数,你能够经过函数参数。可是 
IoC可让你经过配置文件,一个由Service Container 
读取的配置文件来产生实际配置的类。可是程序也有可能变得不易读了,程序的性能也有可能还会降低。

参考:

High Cohesion & Low/Loose coupling & – 高内聚, 低耦合

这个原则是UNIX操做系统设计的经典原则,把模块间的耦合降到最低,而努力让一个模块作到精益求精。

  • 内聚:一个模块内各个元素彼此结合的紧密程度
  • 耦合:一个软件结构内不一样模块之间互连程度的度量

内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。

参考:

Convention over Configuration(CoC)– 惯例优于配置原则

简单点说,就是将一些公认的配置方式和信息做为内部缺省的规则来使用。例如,Hibernate的映射文件,若是约定字段名和类属性一致的话,基本上就能够不要这个配置文件了。你的应用只须要指定不convention的信息便可,从而减小了大量convention而又不得不花时间和精力啰里啰嗦的东东。配置文件不少时候至关的影响开发效率。

Rails 中不多有配置文件(但不是没有,数据库链接就是一个配置文件),Rails 的fans号称期开发效率是 java 开发的 10 
倍,估计就是这个缘由。Maven也使用了CoC原则,当你执行mvn 
-compile命令的时候,不须要指源文件放在什么地方,而编译之后的class文件放置在什么地方也没有指定,这就是CoC原则。

参考:http://en.wikipedia.org/wiki/Convention_over_Configuration

Separation of Concerns (SoC) – 关注点分离

SoC 
是计算机科学中最重要的努力目标之一。这个原则,就是在软件开发中,经过各类手段,将问题的各个关注点分开。若是一个问题能分解为独立且较小的问题,就是相对较易解决的。问题太过于复杂,要解决问题须要关注的点太多,而程序员的能力是有限的,不能同时关注于问题的各个方面。正如程序员的记忆力相对于计算机知识来讲那么有限同样,程序员解决问题的能力相对于要解决的问题的复杂性也是同样的很是有限。在咱们分析问题的时候,若是咱们把全部的东西混在一块儿讨论,那么就只会有一个结果——乱。

我记得在上一家公司有一个项目,讨论就讨论了1年多,项目原本不复杂,可是没有使用SoC,所有的东西混为一谈,再加上一堆程序员注入了各类不一样的观点和想法,整个项目一会儿就失控了。最后,原本一个1年的项目作了3年。

实现关注点分离的方法主要有两种,一种是标准化,另外一种是抽象与包装。标准化就是制定一套标准,让使用者都遵照它,将人们的行为统一块儿来,这样使用标准的人就不用担忧别人会有不少种不一样的实现,使本身的程序不能和别人的配合。Java 
EE就是一个标准的大集合。每一个开发者只须要关注于标准自己和他所在作的事情就好了。就像是开发镙丝钉的人只专一于开发镙丝钉就好了,而不用关注镙帽是怎么生产的,反正镙帽和镙丝钉按标来就必定能合得上。不断地把程序的某些部分抽像差包装起来,也是实现关注点分离的好方法。一旦一个函数被抽像出来并实现了,那么使用函数的人就不用关心这个函数是如何实现的,一样的,一旦一个类被抽像并实现了,类的使用者也不用再关注于这个类的内部是如何实现的。诸如组件,分层,面向服务,等等这些概念都是在不一样的层次上作抽像和包装,以使得使用者不用关心它的内部实现细节。

说白了仍是“高内聚,低耦合”。

参考:http://sulong.me/archives/99

Design by Contract (DbC) – 契约式设技

DbC的核心思想是对软件系统中的元素之间相互合做以及“责任”与“义务”的比喻。这种比喻从商业活动中“客户”与“供应商”达成“契约”而得来。例如:

  • 供应商必须提供某种产品(责任),而且他有权指望客户已经付款(权利)。
  • 客户必须付款(责任),而且有权获得产品(权利)。
  • 契约双方必须履行那些对全部契约都有效的责任,如法律和规定等。

一样的,若是在程序设计中一个模块提供了某种功能,那么它要:

  • 指望全部调用它的客户模块都保证必定的进入条件:这就是模块的先验条件(客户的义务和供应商的权利,这样它就不用去处理不知足先验条件的状况)。
  • 保证退出时给出特定的属性:这就是模块的后验条件——(供应商的义务,显然也是客户的权利)。
  • 在进入时假定,并在退出时保持一些特定的属性:不变式。

契约就是这些权利和义务的正式形式。咱们能够用“三个问题”来总结DbC,而且做为设计者要常常问:

  • 它指望的是什么?
  • 它要保证的是什么?
  • 它要保持的是什么?

根据Bertrand 
Meyer氏提出的DBC概念的描述,对于类的一个方法,都有一个前提条件以及一个后续条件,前提条件说明方法接受什么样的参数数据等,只有前提条件获得知足时,这个方法才能被调用;同时后续条件用来讲明这个方法完成时的状态,若是一个方法的执行会致使这个方法的后续条件不成立,那么这个方法也不该该正常返回。

如今把前提条件以及后续条件应用到继承子类中,子类方法应该知足:

  1. 前提条件不强于基类.
  2. 后续条件不弱于基类.

换句话说,经过基类的接口调用一个对象时,用户只知道基类前提条件以及后续条件。所以继承类不得要求用户提供比基类方法要求的更强的前提条件,亦即,继承类方法必须接受任何基类方法能接受的任何条件(参数)。一样,继承类必须顺从基类的全部后续条件,亦即,继承类方法的行为和输出不得违反由基类创建起来的任何约束,不能让用户对继承类方法的输出感到困惑。

这样,咱们就有了基于契约的LSP,基于契约的LSP是LSP的一种强化。

参考:http://en.wikipedia.org/wiki/Design_by_contract

Acyclic Dependencies Principle (ADP) – 无环依赖原则

包之间的依赖结构必须是一个直接的无环图形,也就是说,在依赖结构中不容许出现环(循环依赖)。若是包的依赖造成了环状结构,怎么样打破这种循环依赖呢?有2种方法能够打破这种循环依赖关系:第一种方法是建立新的包,若是A、B、C造成环路依赖,那么把这些共同类抽出来放在一个新的包D里。这样就把C依赖A变成了C依赖D以及A依赖D,从而打破了循环依赖关系。第二种方法是使用DIP(依赖倒置原则)和ISP(接口分隔原则)设计原则。

无环依赖原则(ADP)为咱们解决包之间的关系耦合问题。在设计模块时,不能有循环依赖。

参考:http://c2.com/cgi/wiki?AcyclicDependenciesPrinciple

————————————————————————————

上面这些原则可能有些学院派,也可能太为理论,我在这里说的也比较模糊和简单,这里只是给你们一个概貌,若是想要了解更多的东西,你们能够多google一下。

不过这些原则看上去都不难,可是要用好却并不那么容易。要能把这些原则用得好用得精,而不教条,个人经验以下:(我觉得这是一个理论到应用的过程)

  1. 你能够先粗浅或是表面地知道这些原则。
  2. 但不要急着立刻就使用。
  3. 在工做学习中观察和总结别人或本身的设计。
  4. 再回过头来了回顾一下这些原则,相信你会有一些本身的心得。
  5. 有适度地去实践一下。
  6. Goto第 3步。

原文:一些软件设计的原则

相关文章
相关标签/搜索