设计模式导读

来源:互联网 发布:什么手机优化软件最好 编辑:程序博客网 时间:2024/05/24 01:28

什么是设计模式。
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。(简单说设计模式就是经验的总结)

这个东西可以重复的使用相当于十个模型,比如说我想做个蛋糕,我看到一个巧克力蛋糕特别漂亮,我想做一个类似的蛋糕,我只要有蛋糕这个模型就可以了。我就可以做出一样的蛋糕出来。同理设计模式就类似这样一个东西。我模仿着它就能做出符合某种规则的东西来。

其实最早设计模式它来自于什么呢?来自于建筑学上。有个哥们儿叫克里斯托弗·亚历山大,写了一本书叫《建筑的永恒之道》在这本书里面他介绍了一下建筑学上的模式,模型之说。他的思想就是说,比如说我在上海见了一个高大上大厦,大楼,我回老家想建造一个一模一样的大楼。怎么办呢?我只要能找到我见的大厦的设计图纸,我回老家就可以建造一个一模一样的大楼出来。建筑学上是这样的,那么同理在软件开发中,发现有越来越多的软件有一些共性的东西。那么这些共性的东西被抽取出来,就渐渐的形成了我们今天的设计模式。

使用设计模式是为了可重用代码(可重用)、让代码更容易被他人理解(可读性)、保证代码可靠性。

设计模式不是一种方法和技术,而是一种思想。

设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序尽可能的复用。

学习设计模式能够促进对面向对象思想的理解,反之亦然,(反之就是指学习面向对象也能加深对设计模式的理解)。他们是相辅相成。

我们看一下设计模式在java程序技能知识储备图。
这里写图片描述
设计模式属于javaweb系统设计与架构层次,现在想说掌握设计模式可能还不太大,我现在充其量算是理解,掌握其形,没有掌握其神。了解它能让我更好的理解面向对象。读一些好的代码能够轻松的看懂其设计和思想。

通过概述能够明白设计模式是经验的总结。

设计模式分类
既然设计模式是面向对象思想的总结,我门就套用面向对象将设计模式分类,面向对像首先得有对象,所以有创建型模式,进行创建对象。

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这就是结构性模式。

在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,然后就有了行为型模式。

创建型模式 就是创建对象的,创建对象其本身是一个很耗时的操作。因为我们需要NEW 去申请内存空间。所以是比较耗时的。所以要找人专门帮我们创建对象。而不是自己造对象。

结构型模式,对象的组成,对象它本身有一些功能什么的,如果他的功能结构良好么?如果良好我们的程序就是做的比价好的。

行为型模式,就是对象的行为。对象的每一个操作。
分为这三种模型。这三种模型有那些呢?下边都列出来了。
创建模式:简单工厂、工厂方法、抽象工厂、单例、建造、模型;
结构模式:适配器、缺省适配、合成、装饰(把类的东西传给别人,别人又用它,这就叫装饰模式。例如IO流)、代理、享元、门面、桥梁;

行为模式:不变、策略、模板方法、观察者、迭代子、责任链、命令
备忘录、状态、访问者、解释器、调停者。(最后三种不讲)

不念了,反正念了也记不住,数一下总共是23 个基本的常见设计模式,但是常见的也就六、七个

其实我今天要讲的是单例和工厂模式。但是在讲之前呢。我们先讲一下面向对象设计原则。
在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象的思想的设计原则。
(也就是说你设计的东西要想好,那么别人是不是以前做过好的东西 ,总结过一些经验,来学习别人的,有这样几个原则)
总共六个。

单一职责原则

里氏替换原则

依赖倒置原则

接口隔离原则

迪米特法则

开闭原则

 单一职责原则

(高内聚:自己能做的就不麻烦别人,类不要产生太强的耦合)

每个类应该只有一个职责(职责应该比较单一,比如说在一个类里面做跟用户相关的功能,做学生相关,做老师相关的功能你单独做一个类是可以得 ,你在这里面即做学生又做老师是不合适的。),对外只能提供一种功能,而引起类变化的原因应该只有一个,(他的好处就是把他们的功能分的特别特别细,它只负责一个事情。)在设计模式中,所有的设计模式都遵循了这一个原则

其实说的简单点,单一原则就是为了把功能细化,细化再细化,每个东西只负责一件事情。

释义:一个类不能太“累”!一个类(大到模块,小到方法)承担的职责越多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则

单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。
这个原则是常常存在争议的。存在争议之处在哪里呢?就是对职责的定义,什么是类的职责,以及怎么划分类的职责。

定义

单一职责原则的定义是:应该有且仅有一个原因引起类的变更。

优点

单一职责原则有什么好处:
● 类的复杂性降低,实现什么职责都有清晰明确的定义;
● 可读性提高,复杂性降低,那当然可读性提高了;
● 可维护性提高,可读性提高,那当然更容易维护了;
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

 单一职责原则最难划分的就是职责。一个职责一个接口,但问题是“职责”没有一个量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑。

 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。

 对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的。

  总而言之单一原则是那么的简单,简单得不需要我们更加深入地思考,单从字面上大家都应该知道是什么意思,单一职责嘛!(举例就是修改用户密码的操作和要和修改用户信息的操作分开。筷子和刀叉。)
  对于单一职责原则,我的建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

 里氏替换原则

核心思想:在任何父类出现的地方都可以用它的子类来代替。
(因为子类是拥有比父类可能是更多的东西,所以父类能用的地方子类一定能用,但是子类用地方,父类就未必能用)
其实就是说: 同一个继承体系中的对象应该有共同的行为特征。

 在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

优点

● 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;

● 提高代码的重用性;

● 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;

● 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;

● 提高产品或项目的开放性。自然界的所有事物都是优点和缺点并存的,即使是鸡蛋,有时候也能挑出骨头来,继承的缺点如下:

● 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;

● 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;

● 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。

定义

 Java使用extends关键字来实现继承,它采用了单一继承的规则,C++则采用了多重继承的规则,一个子类可以继承多个父类。从整体上来看,利大于弊,怎么才能让“利”的因素发挥最大的作用,同时减少“弊”带来的麻烦呢?解决方案是引入里氏替换原则(Liskov Substitution Principle,LSP),

什么是里氏替换原则呢?它有两种定义:
● 第一种定义,也是最正宗的定义:If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.(如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。)(O1是子类,O2是父类)

● 第二种定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.(所有引用基类的地方必须能透明地使用其子类的对象。)
 第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

注意

 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。(新建一个对象左边等号左边应该是其接口或者父类右边为NEW 的子类或实现类)
 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

良好继承的规范

 里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。
1.子类必须完全实现父类的方法
2.子类可以有自己的个性(也就是子类的方法和属性)
 所以里氏替换原则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任。
3.覆盖或实现父类的方法时输入参数可以被放大
 子类扩大父类输入参数方法名虽然相同,但方法的输入参数不同,就不是覆写,加个@Override,会报错的,那这是什么呢?是重载(Overload)!不用大惊小怪的,不在一个类就不能是重载了?继承是什么意思,子类拥有父类的所有属性和方法,方法名相同,输入参数类型又不相同,是重载了。
 父类出现的地方子类就可以出现,那么当输入的参数是父类的参数就会调用父类的方法,不执行子类的方法,输入的参数是子类的参数时才会调用子类的方法。这就满足了里氏替换原则。
4. 覆写或实现父类的方法时输出结果可以被缩小
 无论是覆写或重载父类,子类返回值要小于等于父类返回值。
 

 依赖倒置原则

核心思想:要依赖于抽象,不要依赖于具体实现。(好多原则都强调要依赖抽象不依赖实现。)
也就是说在应用程序中,所有的类如果使用或者依赖于其他的类,则应该依赖这些其他类的抽象类,而不是这些其他类的具体类。(比如说我要创造动物,你不要直接传个狗过来传个动物过来,这样你将来传狗,传猫都可以。一但写了狗,以后就只能传狗不能传猫了。所以要依赖抽象,不依赖具体)为了实现这一原则,就要求我们在编程的时候针对,接口或者抽象变成,而不是针对具体实现编程。

依赖倒置原则(Dependence Inversion Principle,DIP),依赖倒置原则的原始定义是:
High level modules should not depend upon low level modules.Both should depend upon
abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻译过来,包含三层含义:

● 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
● 抽象不应该依赖细节;
● 细节应该依赖抽象。

因为里氏替换才可以依赖倒置;因为依赖倒置才可以实现开闭原则。
 高层模块和低层模块容易理解,每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象?什么又是细节呢?在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。依赖倒置原则在Java语言中的表现就是:
● 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
● 接口或抽象类不依赖于实现类;
● 实现类依赖接口或抽象类。

更加精简的定义就是“面向接口编程”——OOD(Object-Oriented Design,面向对象设计)的精髓之一。

优点:
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。(奔驰宝马车样例)

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,我们怎么在项目中使用这个规则呢?只要遵循以下的几个规则就可以:
● 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
这是依赖倒置的基本要求,接口和抽象类都是属于抽象的,有了抽象才可能依赖倒置。
● 变量的表面类型尽量是接口或者是抽象类
很多书上说变量的类型一定要是接口或者是抽象类,这个有点绝对化了,比如一个工类,xxxUtils一般是不需要接口或是抽象类的。还有,如果你要使用类的clone方法,就必须使用实现类,这个是JDK提供的一个规范。
● 任何类都不应该从具体类派生
如果一个项目处于开发状态,确实不应该有从具体类派生出子类的情况,但这也不是绝对的,因为人都是会犯错误的,有时设计缺陷是在所难免的,因此只要不超过两层的继承都是可以忍受的。特别是负责项目维护的同志,基本上可以不考虑这个规则,为什么?维护工作基本上都是进行扩展开发,修复行为,通过一个继承关系,覆写一个方法就可以修正一个很大的Bug,何必去继承最高的基类呢?(当然这种情况尽量发生在不甚了解父类或者无法获得父类代码的情况下。)
● 尽量不要覆写基类的方法
如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写。类间依赖的是
抽象,覆写了抽象方法,对依赖的稳定性会产生一定的影响。
● 结合里氏替换原则使用
接口负责定义public属性和方法,并且声明与其他
对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

倒置

依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程,倒置是根据系统设计的需要产生了抽象间的依赖,代替了人们传统思
维中的事物间的依赖,“倒置”就是从这里产生的。

 接口隔离原则

核心思想:不应该强迫程序依赖他们不需要使用的方法。
(什么意思呢?我有一个接口里面有四个方法,增删改查,四个方法,现在做一个用户类,我这个用户类只做增加功能,删除,修改查询,都不用做,那么我直接来实现这个接口合理么?不合理。我这个用户类一旦实现这个接口,就要实现这个接口是四个功能。)
也就是说:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口中。(其实做四个接口分别是添加 ,删除,修改,查询,用户类只实现一个借口就可以了)

在讲接口隔离原则之前,先明确一下我们的主角——接口。接口分为两种:

● 实例接口(Object Interface)
在Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事物的描述,这是一种接口。比如你定义Person这个类,然后使用Person zhangSan=new Person()产生了一个实例,这个实例要遵从的标准就是Person这类,Person类就是zhangSan的接口。Java中的类也是一种接口。

● 类接口(Class Interface)
Java中经常使用的interface关键字定义的接口。

隔离的定义,如下所示:

● Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依赖它不需要的接口。)
● The dependency of one class to another one should depend on the smallest possible interface.
(类间的依赖关系应该建立在最小的接口上。)
 我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。看到这里大家有可能要疑惑了,这与单一职责原则不是相同的吗?错,接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。
 
 例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口指什么?就是指提供给每个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。

接口隔离的原则

 接口隔离原则是对接口进行规范约束,其包含以下4层含义:
● 接口要尽量小
 接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则
● 接口要高内聚
高内聚就是提高接口、类、模块的处理能力,减少对外的交互。不讲任何条件、立刻完成任务的行为就是高内聚的表现。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
● 定制服务
定制服务就是单独为一个个体提供优良的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的接口采用定制服务。采用定制服务就必然有一个要求:只提供访问者需要的方法
● 接口设计是有限度的
接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接设计一定要注意适度,这个“度”如何来判断呢?根据经验和常识判断,没有一个固化或可测量的标准。

最佳实践

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。但是,这个原子该怎么划分是设计模式中的一大难题,在实践中可以根据以下几个规则来衡量:
● 一个接口只服务于一个子模块或业务逻辑;
● 通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
● 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
● 了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,,环境不同,接口拆分的标准就不同。深入了解业务逻辑,最好的接口设
计就出自你的手中!

接口隔离原则和其他设计原则一样,都需要花费较多的时间和精力来进行设计和筹划,但是它带来了设计的灵活性,让你可以在业务人员提出“无理”要求时轻松应付。贯彻使用接口隔离原则最好的方法就是一个接口一个方法。根据经验和常识决定接口的粒度大小,接口粒度太小,导致接口数据剧增,开发人员呛死在接口的海洋里;接口粒度太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。

 迪米特法则

核心思想:一个对象,应到当对其他对象尽可能少的了解。
(这就是低耦合,跟别人关系不要太强。降低各个关系间的耦合。)
也就是说:降低各个对象之间的耦合,提高系统的可维护性,在模块之间应该只通过接口编程,而不理会模块的内部工作原理。(调用接口方法,具体是那个实现并不关心。)它可以使各个模块耦合度降低,促进软件复用。

(这些所有的原则都是为了提高程序的可维护性、可扩展性、可复用性。只有遵循了这些原则,将来你设计的程序,才会具有较好的可扩展性、可复用性、可维护性。当然这样描述一遍是记不住的,只有以后写程序时多想想,才能理解这些思想的精髓是什么。)

迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least KnowledgePrinciple,LKP)虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。

迪米特法则强调在类的设计结构上,每一个类都应当尽量降低成员的访问权限。其根本细想就是强调了类之间的松耦合。类之间的耦合越弱,越有利于重复利用。一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说信息的隐藏提高了类的复用性。

迪米特法则对类的低耦合提出了明确的要求,其包含以下4层含义。
1. 只和朋友交流
迪米特法则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通
信。)什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。
朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类
2. 朋友间也是有距离的
人和人之间是有距离的,太远关系逐渐疏远,最终形同陌路;太近就相互刺伤。对朋友关系描述最贴切的故事就是:两只刺猬取暖,太远取不到暖,太近刺伤了对方,必须保持一个既能取暖又不刺伤对方的距离。迪米特法则就是对这个距离进行描述,即使是朋友类之间也不能无话不说,无所不知。

注意 

迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
3. 是自己的就是自己的
在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?你可以坚持这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

最佳实践

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。

 开闭原则

核心思想:一个对象对扩展开放,对修改关闭。(也就是一个类开发好了,就不要去动这个类的代码,因为你一动就可能对其他的一些类造成影响,所以对修改关闭,对扩展开放就是,我不动你的,我自己开建一个新的类【举例手机的例子 老手机只能打电话,不能玩游戏,想玩游戏,新建一个手机类,有玩游戏的功能。,对就旧手机没有影响。】)
其实开闭原则的意思就是:对类的改动是通过增加代码进行的,而不是修改现有代码。
什么原因呢?
也就是说开发人员一旦泄露可以运行的代码,就不应去改动它,而是要保证它能一直运行下去,如何能做到这一点呢?就要借助于抽象和多态,把可能变化的内容抽象出来,而使抽象的部分是相对稳定的,而具体的实现是可以改变和扩展的。(这就是编程尽量的面向抽象类,接口进行编程,不要面向具体类编程。一旦写具体类的时候,将来改动就会很麻烦)。

开闭原则是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统,先来看开闭原则的定义:
Software entities like classes,modules and functions should be open for extension but closed for
modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
开闭原则的定义已经非常明确地告诉我们:软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。就是对程序的修改是通过增加新代码进行的而不是更改现有代码。

软件实体包括以下几个部分:
● 项目或软件产品中按照一定的逻辑规则划分的模块。
● 抽象和类。
● 方法。
一个软件产品只要在生命期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。

开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。我们可以把变化归纳为以下三种类型:
● 逻辑变化
只变化一个逻辑,而不涉及其他模块,比如原有的一个算法是a*b+c,现在需要修改为
a*b*c,可以通过修改原有类中的方法的方式来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理。
● 子模块变化
一个模块变化,会对其他的模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化时,高层次的模块修改是必然的。
● 可见视图变化
可见视图是提供给客户使用的界面,如JSP程序、Swing界面等,该部分的变化一般会引起连锁反应。

开闭原则在面向对象设计领域中的地位就类似于牛顿第一定律在力学、勾股定律在几何学、质能方程在狭义相对论中的地位,其地位无人能及。

开闭原则约束:
1. 抽象约束
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:第一,通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;第三,抽象层尽量保持稳定,一旦确定即不允许修改。
2. 元数据(metadata)控制模块行为
什么是元数据?用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
3. 制定项目章程
在一个团队中,建立项目章程是非常重要的,因为章程中指定了所有人员都必须遵守的约定,对项目来说,约定优于配置。
4. 封装变化
对变化的封装包含两层含义:第一,将相同的变化封装到一个接口或抽象类中;第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。

0 0
原创粉丝点击