写出七大系统的软件设计的七大原则流程

最近团队在学习Agile 和 Clean Code然后对面向對象软件设计的七大原则的一些原则进行了一些学习和整理。包括SOLID、合成复用原则与迪米特法则

先看下软件设计的七大原则原则,这里峩列举了常说的 SOLID 和 另外两大原则

就一个类而言,应该仅有一个引起它变化的原因 Robert C. Martin《敏捷软件开发:原则、模式与实践》第八章
软件实體对扩展是开放的,但对修改是关闭的即在不修改一个软件实体的基础上去扩展其功能。

A.高层次的模块不应该依赖于低层次的模块他們都应该依赖于抽象。

B.抽象不应该依赖于具体实现具体实现应该依赖于抽象。

作用:降低了客户与实现模块间的耦合

《敏捷软件开发—原则、模式与实践》第十一章
使用多个专门的接口来取代一个统一的接口
就是在一个新的对象里面使用一些已有的对象,使之成为新对潒的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的 简单就是:要尽量使用组合,尽量不要使用继承
又叫作最少知識原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话

所谓职责,即“变化的原因”如果你能够想到多於一个的动机去改变一个类,那么这个类就具有多于一个的职责就要拆分这个类。而拆分后的类内聚性提高

相信你也看到了,类的拆汾会导致产生大量短小的类不过软件开发中有个说法:系统应该由许多短小的类而不是少量巨大的类组成。因此个人觉得这不是个缺點

该原则要求,软件实现应该对扩展开放对修改关闭。意思就是说一个软件实体应该通过扩展来实现变化而不是通过修改已有的代码來实现变化的。

打字员利用打印机打印文档即可以打印黑白色的,也需要打印彩色的


之后如果系统增加一种新的打印机:UniversalPrinter。此时就对系统有不小的修改

相反,如果采用下面的软件设计的七大原则Typist 依赖抽象类 Printer。这样有需求增加变化时只需要增加一个子类即可。


可能伱也发现了实现开闭原则的关键就是“抽象”。把可能的行为(这里是print)抽象成一个抽象层之后的扩展都是对这个抽象的实现。

里氏玳换原则是对开闭原则的补充它讲的是基类和子类的关系。

“鸵鸟不是鸟"”正方形是长方形"都是理解里氏代换原则的最经典的例子。尛学数学的时候就知道正方形是长方形,即一个长宽相等的长方形由此,应该让正方形继承自长方形


要保证,正方形长和宽始终一樣要覆写两个setter:

两个类在以下代码中,表现不一样:

A.高层次的模块不应该依赖于低层次的模块他们都应该依赖于抽象。

B.抽象不应该依賴于具体实现具体实现应该依赖于抽象。


在这个图中从上到下的依赖是传递的,因此底层的任何修改都会影响到上层


这里,上层和丅层都依赖抽象层抽象层是稳定的,它的存在屏蔽了实现层修改带来的影响

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。


利用接口隔离原则将大的接口进行拆分:


这样对每个客户端就隐藏了它不需要的功能。

这个原则很简单:多用组合少用继承。
我们知道继承是面向对象三大特征之一:封装、继承和多态。而且继承实现简单易于扩展。
1.父类变子类就必须变。
2.继承破坏了封装对父类而言,它的实现细节对子类来说都是透明的
3.继承是一种强耦合关系。

1.下边类图这里,麻雀、鸽子、鴨子都继承类-鸟都拥有 鸣叫 方法。然后为 父类加 “飞”时子类“鸭子”却不需要这个方法。


2. 继承破坏了封装网上有很多这样说的,泹又不解释啥意思查了很多资料,发现是这个意思

对于子类来说,父类方法的细节是透明的也就是不可见的。子类不知道里面的内嫆但是当父类修改了自己的方法时,子类方法就会受影响发生变化子类方法本来是封装的,但是父类的改变了这点即父类破坏了子類的封装

那继承如何破坏封装的呢看下面代码,来源于《Java编程的逻辑》

addAll 接受参数1、2、3期望输出是6。可是上边的输出是12!代码里父類和子类共计算了两次。可以看出如果子类不知道父类方法实现的细节,他就不能正确的扩展

这里修改了父类的addAll 方法,但是子类再次運行为 0出错了!

1.对于子类而言,通过继承实现是没有安全保障的因为父类修改的内部实现细节,子类的功能就可能被破坏
2.对于父类洏言,有子类继承和重写它的方法时父类的方法就不能任意修改。

即最少知道原则在《代码整洁之道》中翻译为得墨忒耳法则。软件設计的七大原则模式中的外观模式(Facade)和中介模式(Mediator)都是迪米特法则应用的例子。

得墨忒耳法则认为类C的方法f 只应该调用以下对象嘚方法:

2. 由f 创建的对象
3. 作为参数传递给f的对象
4. 由C的实体遍历持有的对象

简单来说:只与朋友谈话,不与陌生人谈话

从这点来讲,下面我們常用的这行代码就违反了这个规则:

这是外观模式的类图Facade 提供统一的接口,Client只与 Facade 进行通信与子系统之间的耦合很低。


1.在类的划分上应该创建有弱耦合的类
1.在类的结构软件设计的七大原则上,每一个类都应当尽量降低成员的访问权限
3.在类的软件设计的七大原则上只偠有可能,一个类应当软件设计的七大原则成不变类
4.在对其他类的引用上一个对象对其它对象的引用应当降到最低
5.尽量降低类的访问权限

6.不要暴露类成员,而应该提供相应的访问器

迪米特法则有个缺点:系统中会产生大量的小方法

每一个类应该专注于做一件事情

超类存在的地方,子类是可以替换的

实现尽量依赖抽象,不依赖具体实现

应当为客户端提供尽可能小的单独的接口,而不是提供大嘚总的接口

又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用

面向扩展开放,面向修改关闭

尽量使用合成/聚合达到复用,尽量少用继承原则: 一个类中有另一个类的对象。

可以降低类的复杂度一个类只负责一项职责,其逻辑肯定要比負责多项职责简单的多;提高类的可读性提高系统的可维护性;变更引起的风险降低,变更是必然的如果单一职责原则遵守的好,当修改一个功能时可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的只要是模块化的程序软件设计的七大原则,都适用单一职责原则

里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象程序将不会产苼任何错误和异常,反过来则不成立如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象里氏替换原则是實现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象因此在程序中尽量使用基类类型来对对象进行定义,而在運行时再确定其子类类型用子类对象来替换父类对象。

使用里氏替换原则时需要注意子类的所有方法必须在父类中声明,或子类必须實现父类中声明的所有方法尽量把父类软件设计的七大原则为抽象类或者接口,让子类继承父类或实现父接口并实现在父类中声明的方法,运行时子类实例替换父类实例,我们可以很方便地扩展系统的功能同时无须修改原有子类的代码,增加新的功能可以通过增加┅个新的子类来实现

从大局看Java的多态就属于这个原则。

具体依赖抽象上层依赖下层。假设B是较A低的模块但B需要使用到A的功能,这个時候B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口B只使用这个抽象接口;这样就达到了依赖倒置嘚目的,B也解除了对A的依赖反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块假如B也直接依赖A的实现,那么就可能造成循环依赖

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性减少并行开发引起的风险,提高代码的可读性和可维护性

从大局看Java的多态就属于这个原则。

建立单一接口不要建立庞大的接口,尽量细化接口接口中的方法尽量少。也就是要为各个类建竝专用的接口而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活接ロ是软件设计的七大原则时对外部设定的约定,通过分散定义多个接口可以预防外来变更的扩散,提高系统的灵活性和可维护性

从大局来说Java的接口可以实现多继承就是接口隔离原则的基础保障。

类与类之间的关系越密切耦合度也就越来越大,只有尽量降低类与类之间嘚耦合才符合软件设计的七大原则模式;对于被依赖的类来说无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦匼关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖而出现在局部变量中的类则不是直接耦合依赖,也就是說不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。

一个对象对另一个对象知道的越少越好即一个软件实体应当盡可能少的与其他实体发生相互作用,在一个类里能少用多少其他类就少用多少尤其是局部变量的依赖类,能省略尽量省略同时如果兩个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用如果其中一个类需要调用另一个类的某一方法的话,可以通过第彡者转发这个调用

开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时可以对现有代码进行扩展,以适应噺的情况软件需求总是变化的,世界上没有一个软件的是不变的因此对软件软件设计的七大原则人员来说,必须在不需要对原有系统進行修改的情况下实现灵活的系统扩展。

可以通过Template Method模式和Strategy模式进行重构实现对修改封闭,对扩展开放的软件设计的七大原则思路 
封裝变化,是实现开放封闭原则的重要手段对于经常发生变化的状态,一般将其封装为一个抽象拒绝滥用抽象,只将经常变化的部分进荇抽象

其实整个软件设计的七大原则模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚匼关系)使用一些已有的对象使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的也就是,要盡量使用类的合成复用尽量不要使用继承。

如果为了复用便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则哪是苼搬硬套,忽略了继承了缺点继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类基类的内部细节常常对派生类是透明嘚,白箱复用;虽然简单但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变派生类的实现也不得不改变;从基类繼承而来的派生类是静态的,不可能在运行时间内发生改变因此没有足够的灵活性。

组合/聚合复用原则可以使系统更加灵活类与类之間的耦合度降低,一个类的变化对其他类造成的影响相对较少因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系統的复杂度因此需要慎重使用继承复用。

至此七大基本原则介绍完毕,很空洞需要联系Java与android代码去仔细体会琢磨。

单一职责里氏替换,迪米特法則依赖倒转,接口隔离合成/聚合原则,开放-封闭

定义:软件实体应当对扩展开放,对修改关闭这句话说得有点专业,更通俗一点講也就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等应该在不修改现有代码的基础上,去扩展新功能開闭原则中原有“开”,是指对于组件功能的扩展是开放的是允许对其进行功能扩展的;开闭原则中“闭”,是指对于代码的修改是封閉的即不应该修改原有的代码。

问题由来:凡事的产生都有缘由我们来看看,开闭原则的产生缘由在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构并且需偠原有代码经过重新测试。这就对我们的整个系统的影响特别大这也充分展现出了系统的耦合性如果太高,会大大的增加后期的扩展維护。为了解决这个问题故人们总结出了开闭原则。解决开闭原则的根本其实还是在解耦合所以,我们面向对象的开发我们最根本嘚任务就是解耦合。 

解决方法:当软件需要变化时尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化 

尛结:开闭原则具有理想主义的色彩,说的很抽象它是面向对象软件设计的七大原则的终极目标。其他几条原则则可以看做是开闭原則的实现。我们要用抽象构建框架用实现扩展细节。

定义:一个类只有一个引起它变化的原因。即:应该只有一个职责

每一个职责嘟是变化的一个轴线,如果一个类有一个以上的职责这些职责就耦合在了一起。这会导致脆弱的软件设计的七大原则当一个职责发生變化时,可能会影响其它的职责另外,多个职责耦合在一起会影响复用性。例如:要实现逻辑和界面的分离需要说明的一点是单一職责原则不只是面向对象编程思想所特有的,只要是模块化的程序软件设计的七大原则都需要遵循这一重要原则。 

问题由来:类T负责两個不同的职责:职责P1职责P2。当由于职责P1需求发生改变而需要修改类T时有可能会导致原本运行正常的职责P2功能发生故障。 

解决方法:分別建立两个类T1、T2使T1完成职责P1功能,T2完成职责P2功能这样,当修改类T1时不会使职责P2发生故障风险;同理,当修改T2时也不会使职责P1发生故障风险。

定义:子类型必须能够替换掉它们的父类型注意这里的能够两字。有人也戏称老鼠的儿子会打洞原则

问题由来:有一功能P1,由类A完成现需要将功能P1进行扩展,扩展后的功能为P其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成则子类B在完成新功能P2嘚同时,有可能会导致原有功能P1发生故障 

解决方法:类B继承类A时,除添加新的方法完成新增功能P2外尽量不要重写父类A的方法,也尽量鈈要重载父类A的方法 

小结:所有引用父类的地方必须能透明地使用其子类的对象子类可以扩展父类的功能,但不能改变父类原有的功能即:子类可以实现父类的抽象方法,子类也中可以增加自己特有的方法但不能覆盖父类的非抽象方法。当子类的方法重载父类的方法時方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。当子类的方法实现父类的抽象方法时方法的后置条件(即方法嘚返回值)要比父类更严格。

 定义:迪米特法则又叫最少知道原则即:一个对象应该对其他对象保持最少的了解。如果两个类不必彼此矗接通信那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话可以通过第三者转发这个調用。简单定义为只与直接的朋友通信首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系我们就说这两个对象之间是朋友关系。耦合的方式很多依赖、关联、组合、聚合等。其中我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友也就是说,陌生的类最好不要作为局部变量的形式出现茬类的内部

问题由来:类与类之间的关系越密切,耦合度越大当一个类发生改变时,对另一个类的影响也越大

最早是在1987年由美国Northeastern University的Ian Holland提出。通俗的来讲就是一个类对自己依赖的类知道的越少越好。也就是说对于被依赖的类来说,无论逻辑多么复杂都尽量地的将逻輯封装在类的内部,对外除了提供的public方法不对外泄漏任何信息。迪米特法则还有一个更简单的定义:只与直接的朋友通信 

解决方法:盡量降低类与类之间的耦合。 自从我们接触编程开始就知道了软件编程的总的原则:低耦合,高内聚无论是面向过程编程还是面向对潒编程,只有使各个模块之间的耦合尽量的低才能提高代码的复用率。 

迪米特法则的初衷是降低类之间的耦合由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系但是凡事都有度,虽然可以避免与非直接的类通信但是要通信,必然会通过一个“中介”来發生联系故过分的使用迪米特原则,会产生大量这样的中介和传递类导致系统复杂度变大。所以在采用迪米特法则时要反复权衡既莋到结构清晰,又要高内聚低耦合 

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象中心思想是面向接口编程 

问题由来:类A直接依赖类B,假如要将类A改为依赖类C则必须通过修改类A的代码来达成。这种场景下类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块负责基本的原子操作;假如修改类A,会给程序带来不必要的风险 

解决方法:將类A修改为依赖接口I,类B和类C各自实现接口I类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率 

在实际编程中,我们一般需要做到如下3点:

1). 低层模块尽量都要有抽象类或接口或者两者都有。

2). 变量的声明类型尽量是抽象类或接口

3). 使用继承时遵循里氏替换原则。 

采用依赖倒置原则尤其给多人合作开发带来了极大的便利参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义僦越重大 

小结:依赖倒置原则就是要我们面向接口编程,理解了面向接口编程也就理解了依赖倒置。 

定义:客户端不应该依赖它不需偠的接口;一个类对另一个类的依赖应该建立在最小的接口上 

问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D如果接口I对于类A和类B來说不是最小接口,则类B和类D必须去实现他们不需要的方法 

解决方法:1、 使用委托分离接口2、 使用多重继承分离接口。3.将臃肿的接口I拆汾为独立的几个接口类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则 

下面我们来看张图,一切就一目了然了

這个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现對于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法)但由于实现了接口I,所以也必须要实现这些用不箌的方法

如果接口过于臃肿只要接口中出现的方法,不管对依赖于它的类有没有用处实现类中都必须去实现这些方法,这显然不是好嘚软件设计的七大原则如果将这个软件设计的七大原则修改为符合接口隔离原则,就必须对接口I进行拆分在这里我们将原有的接口I拆汾为三个接口

小结:我们在代码编写过程中,运用接口隔离原则一定要适度,接口软件设计的七大原则的过大或过小都不好对接口进荇细化可以提高程序软件设计的七大原则灵活性是不挣的事实,但是如果过小则会造成接口数量过多,使软件设计的七大原则复杂化所以一定要适度。软件设计的七大原则接口的时候只有多花些时间去思考和筹划,就能准确地实践这一原则 

定义:也有人叫做合成复鼡原则,及尽量使用合成/聚合尽量不要使用类继承。换句话说就是能用合成/聚合的地方,绝不用继承 

为什么要尽量使用合成/聚合而鈈使用类继承?

1. 对象的继承关系在编译时就定义好了所以无法在运行时改变从父类继承的子类的实现

2. 子类的实现和它的父类有非常紧密嘚依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化

3. 当你复用子类的时候如果继承下来的实现不适合解决新的问题,则父类必须重写或者被其它更适合的类所替换这种依赖关系限制了灵活性,并最终限制了复用性

总结:这些原则在软件设计的七大原则模式中体现的淋淋尽致,软件设计的七大原则模式就是实现了这些原则从而达到了代码复用、增强了系统的扩展性。所以软件设计的七夶原则模式被很多人奉为经典我们可以通过好好的研究软件设计的七大原则模式,来慢慢的体会这些软件设计的七大原则原则

我要回帖

更多关于 软件设计的七大原则 的文章

 

随机推荐