您的位置:首页 > 编程语言

编写代码之原则总结_MD

2017-12-01 21:38 260 查看

1. 避免重复原则 - DRY

避免重复原则(DRY - Don’t repeat yourself):编程的最基本原则是避免重复。在程序代码中总会有很多结构体,如循环、函数、类等等。一旦你重复某个语句或概念,就会很容易形成一个抽象体。

2. 抽象原则

抽象原则(Abstraction Principle):与DRY原则相关。要记住,程序代码中每一个重要的功能,只能出现在源代码的一个位置。

3. 别让我思考

别让我思考(Don’t make me think ):所编写的代码一定要易于读易于理解,这样别人才会欣赏,也能够给你提出合理化的建议。相反,若是繁杂难解的程序,其他人总是会避而远之的。

4. 开闭原则 - OCP

开闭原则(Open/Closed Principle,简称OCP):你所编写的软件实体(类、模块、函数等)最好是开源的,这样别人可以拓展开发。不过,对于你的代码,得限定别人不得修改。换句话说,别人可以基于你的代码进行拓展编写,但却不能修改你的代码。

遵循开闭原则设计出来的模块具有两个基本特征:

对于扩展是开放的(Open for extension):模块的行为可以扩展,当应用的需求改变时,可以对模块进行扩展,以满足新的需求。

对于更改是封闭的(Closed for modification):对模块行为扩展时,不必改动模块的源代码或二进制代码。

这两个特征看起来是相互矛盾的。扩展模块的行为通常需要修改该模块的源代码,而不允许修改的模块通常被认为是具有固定的行为。

实现开闭原则的关键在于抽象化。在Java中,抽象化的具体实现就是使用抽象类或接口。

4.1 使用抽象类

在设计类时,对于拥有共同功能的相似类进行抽象化处理,将公用的功能部分放到抽象类中,而将不同的行为封装在子类中。这样,在需要对系统进行功能扩展时,只需要依据抽象类实现新的子类即可。在扩展子类时,不仅可以拥有抽象类的共有属性和共有方法,还可以拥有自定义的属性和方法。

4.2 接口

与抽象类不同,接口只定义实现类应该实现的接口方法,而不实现公有的功能。在现在大多数的软件开发中,都会为实现类定义接口,这样在扩展子类时必须实现该接口。如果要改换原有的实现,只需要改换一个实现类即可。

注意:JDK1.8引入了默认方法,也就是说接口中可以定义默认方法。

5. 代码维护

代码维护(Write Code for the Maintainer):一个优秀的代码,应当使本人或是他人在将来都能够对它继续编写或维护。代码维护时,或许本人会比较容易,但对他人却比较麻烦。因此你写的代码要尽可能保证他人能够容易维护。用书中原话说“如果一个维护者不再继续维护你的代码,很可能他就有想杀了你的冲动。”

6. 单一责任原则

单一责任原则(Single Responsibility Principle):某个代码的功能,应该保证只有单一的明确的执行任务。

7. 低耦合原则

低耦合原则(Minimize Coupling):代码的任何一个部分应该减少对其他区域代码的依赖关系。尽量不要使用共享参数。低耦合往往是完美结构系统和优秀设计的标志。

8. 最大限度凝聚原则

最大限度凝聚原则(Maximize Cohesion):相似的功能代码应尽量放在一个部分。

9. 迪米特法则[最少知识原则] - LOD

迪米特法则又叫作最少知识原则(Law of Demeter):该代码只和与其有直接关系的部分连接。(比如:该部分继承的类,包含的对象,参数传递的对象等)。

10. 避免过早优化

避免过早优化(Avoid Premature Optimization):除非你的代码运行的比你想像中的要慢,否则别去优化。假如你真的想优化,就必须先想好如何用数据证明,它的速度变快了。

“过早的优化是一切罪恶的根源”——Donald Knuth

11. 代码重用原则

代码重用原则(Code Reuse is Good):重用代码能提高代码的可读性,缩短开发时间。

12. 里氏替换原则 - LSP

里氏替换原则(The Liskov Substitution Principle,简称LSP):在一个软件系统中,子类应该能够完全替换任何父类能够出现的地方,并且经过替换后,不会让调用父类的客户程序从行为上有任何改变。

里氏替换原则实现了开闭原则中的对扩展开放。实现开闭原则的关键步骤是抽象化,父类与子类之间的继承关系就是一种抽象化的体现。因此,里氏替换原则是实现抽象化的一种规范。违反里氏替换原则意味着违反了开闭原则,反之未必。里氏替换原则是使代码符合开闭原则的一个重要保证。

一般来说,只要有可能,就不要从具体类继承。在一个由继承关系形成的等级结构中,树叶节点都应当是具体类,树枝节点都应该是抽象类或者接口。

里氏替换原则是使代码符合开闭原则的一个重要的保证,同时,它体现了:

<1>类的继承原则

里氏替换原则常用来检查两个类是否为继承关系。在符合里氏替换原则的继承关系中,使用父类代码的地方,用子类代码替换后,能够正确的执行动作处理。换句话说,如果子类替换了父类后,不能够正确执行动作,那么他们的继承关系就是不正确的,应该重新设计它们之间的关系。

<2>动作正确性保证

里氏替换原则对子类进行了约束,所以在为已存在的类进行扩展,来创建一个新的子类时,符合里氏替换原则的扩展不会给已有的系统引入新的错误。

<3>对类的继承关系的定义

面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。

我们说类的继承关系就是一种“is-a”关系,实际上指的是行为上的“is-a”关系,可以把它描述为“表现为,act as”。

正方形在设置长度和宽度这两个行为上,与长方形显然是不同的。长方形的行为:设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到父类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。

<4>设计要依赖于用户需求和具体环境。

继承关系要求子类要具有基类全部的行为。这里的行为是指落在需求范围内的行为。

这里我们以另一个理解里氏替换原则的经典例子“鸵鸟非鸟”来做示例。生物学中对于鸟类的定义是“恒温动物,卵生,全身披有羽毛,身体呈流线形,有角质的喙,眼在头的两侧。前肢退化成翼,后肢有鳞状外皮,有四趾”。从生物学角度来看,鸵鸟肯定是一种鸟,是一种继承关系。但是根据上一个“正方形非长方形”的例子,鸵鸟和鸟之间的继承关系又可能不成立。那么,鸵鸟和鸟之间到底是不是继承关系如何判断呢?这需要根据用户需求来判断。

现在鸟类有四个对外的行为,其中两个行为分别落在A和B系统需求中,如下图所示。

A需求期望鸟类提供与飞翔有关的行为,即使鸵鸟跟普通的鸟在外观上就是100%的相像,但在A需求范围内,鸵鸟在飞翔这一点上跟其它普通的鸟是不一致的,它没有这个能力,所以,鸵鸟类无法从鸟类派生,鸵鸟不是鸟。

B需求期望鸟类提供与羽毛有关的行为,那么鸵鸟在这一点上跟其它普通的鸟一致的。虽然它不会飞,但是这一点不在B需求范围内,所以,它具备了鸟类全部的行为特征,鸵鸟类就能够从鸟类派生,鸵鸟就是鸟。

所有子类的行为功能必须和使用者对其父类的期望保持一致,如果子类达不到这一点,那么必然违反里氏替换原则。

13. 依赖倒转原则 - DIP

(Dependency Inversion Principle,简称DIP):依赖倒转原则是指将两个模块之间的依赖关系倒置为依赖抽象类或接口。

具体有两层含义:

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

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

具体耦合关系:发生在两个具体的(可实例化的)类之间,经由一个类对另一个具体类的直接引用造成。

抽象耦合关系:发生在一个具体类和一个抽象类(或接口)之间,使两个必须发生关系的类之间存有最大的灵活性。

面向接口编程:

在高层模块和低层模块之间定义一个抽象接口,高层模块调用抽象接口定义的方法,低层模块实现该接口。

依赖倒转原则的本质就是要求将类之间的关系建立在抽象接口的基础上的。通过上面的方式,将错误的依赖关系倒转过来,使具体实现类依赖于抽象类和接口。这就是依赖倒转原则中“倒转”的由来。

以抽象方式耦合是依赖倒转原则的关键。

14. 组合/聚合复用原则 - CARP

组合/聚合复用原则(Composite/Aggregation Reuse Principle,CARP):组合/聚合复用原则是指要尽量使用组合/聚合而非继承来达到复用目的。另一种解释是在一个新的对象中使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象委托功能达到复用这些对象的目的。

<1>组合/聚合复用

我们知道组合/聚合都是关联关系的特殊种类,二者都是体现整体与部分的关系,也就是两个类之间的是“has-a”关系,它表示某一个角色具有某一项责任。由于组合/聚合都可以将已有的对象加入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,从而实现对象复用。

使用组合/聚合实现复用有如下好处:

新对象存取成分对象的唯一方法是通过成分对象的接口。

这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。

这种复用所需的依赖较少。

每一个新的类可以将焦点集中在一个任务上。

这种复用可以在运行时间内动态进行,作为整体的新对象可以动态地引用与部分对象类型相同的对象。也就是说,组合/聚合是动态行为,即运行时行为。可以通过使用组合/聚合的方式在设计上获得更高的灵活性。

当然,这种复用也有缺点。其中最主要的缺点就是系统中会有较多的对象需要管理。

一般来说,如果一个角色得到了更多的责任,就可以使用组合/聚合关系将新的责任委派到合适的对象上。

<2>继承复用

继承是面向对象语言特有的复用工具。由于使用继承关系时,新的实现较为容易,因父类的大部分功能可以通过继承的关系自动进入子类;同时,修改和扩展继承而来的实现较为容易。于是,在面向对象设计理论的早期,程序设计师十分热衷于继承,好像继承就是最好的复用手段,于是继承也成为了最容易被滥用的复用工具。然而,继承有多个缺点:

继承复用破坏封装,因为继承将父类的实现细节暴露给子类。由于父类的内部细节常常是对于子类透明的,所以这种复用是透明的复用,又称“白箱”复用。

如果父类发生改变,那么子类的实现也不得不发生改变。

从父类继承而来的实现是静态的,也就是编译时行为,不可能在运行时间内发生改变,没有足够的灵活性。

正是因为继承有上述缺点,所以应首先使用组合/聚合,其次才考虑继承,达到复用的目的。并且在使用继承时,要严格遵循里氏替换原则。

要正确的选择组合/聚合和继承,必须透彻的理解里氏替换原则和Coad法则。里氏替换原则前面学习过,Coad法则由Peter Coad提出,总结了一些什么时候使用继承作为复用工具的条件。

<3>Coad条件全部被满足时,才应当使用继承关系

子类是父类的一个特殊种类,而不是父类的一个角色,也就是区分“has-a”和“is-a”。只有“is-a”关系才符合继承关系,“has-a”关系应当用组合/聚合来描述。

永远不会出现需要将子类换成另外一个类的子类的情况。如果不能肯定将来是否会变成另外一个子类的话,就不要使用继承。

子类具有扩展父类的责任,而不是具有置换(重写)或注销掉父类的责任。如果一个子类需要大量的置换掉父类的行为,那么这个类就不应该是这个父类的子类。

只有在分类学角度上有意义时,才可以使用继承。不要从工具类继承。

15.接口隔离原则 - ISP

接口隔离原则(Interface Segregation Principle,简称ISP):接口隔离原则是指客户不应该依赖它们用不到的方法,只给每个客户它所需要的接口。换句话说,就是不能强迫用户去依赖那些他们不使用的接口。

<1> 接口的设计原则

接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口,使用多个专门的接口比使用单一的总接口要好。

<2> 接口的继承原则

如果一个接口A继承另一个接口B,则接口A相当于继承了接口B的方法,那么继承了接口B后的接口A也应该遵循上述原则:不应该包含用户不使用的方法。反之,则说明接口A被B给污染了,应该重新设计它们的关系。

<3> 通过多重继承分离接口

多重继承可以有两个方式,第一种方式是同时实现两个接口,属于多重接口继承;第二种方式是实现一个接口,同时继承一个具体类,实际上也是一种多重继承。

<4> 通过委托分离接口

使用聚合和组合来将部分实现交给已知类实现。自己在实现部分接口。

参考资料:

OOAD之设计原则

优秀程序设计的18大原则

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: