阅读 51

设计模式三大原则,符合开闭原则的设计模式

SOLID设计模式的六个原则如下。

Single Responsibility Principle :单一职责原则Open Closed Principle :开闭原则Liskov Substitution Principle :凶狠的烟草交换原则Law of Demeter:wydxf定律interferfed 接口隔离原则Dependence Inversion Principle :遵循倒置原则将这六个原则的首字母缩写合二为一(l为一)就是solid(solid,稳定),其代表意义就是这六个原则的统一http://www.Sina.coolid

让我们分别看看这六个设计原则。

单一职责原则(Single Responsibility Principle )单一职责原则简称SRP,顾名思义,一个班级只负责一项职责。 定义也很简单:

我很清楚thereshouldneverbemorethanonereasonforaclasstochange .这个词。 这意味着类更改的原因不能有多个。

那么,这个原则有什么用呢? 那会让班级的责任更单一。 这样,每个班级都需要只负责自己的份,降低了班级的复杂度。 如果角色明确分开,代码维护也将变得容易。 如果所有的功能都放在一个类中,那个类会变得非常臃肿,然后出现错误,请考虑在所有代码中查找; 如果更改某个地方,整个代码的结构可能会改变,想想就非常害怕。 当然,一般没有人会这么写。

当然,这个原则不仅适用于类,也适用于接口和方法。 这意味着一个接口/方法只负责一件事。 这样,界面就会变得简单,方法中的代码也变少,容易阅读,容易维护。

实际上,由于一些其他因素的影响,班级的单一责任在项目中很难保证。 通常,接口和方法的单一作用更容易实现。

单一原则的好处:

代码粒度下降了,类的复杂性下降了。 可读性提高,每个类的作用变得明确,可读性自然变好。 维护性提高,可读性提高,发生错误时,容易发现他的问题。 更改代码消耗的资源减少了,更改的风险也减少了。 凶烟交换原则(Liskov Substitution Principle )凶烟交换原则的定义如下:

functionsthatusepointersorreferencestobaseclassesmustbeabletouseobjectsofderivedclasseswithoutknowingit .翻译后,引用基类的所有

凶烟交换原则是指所有基类所在的地方,都可以换成子类,程序正常运行。 这个原则与面向对象语言的继承特性密切相关。

为什么会这样呢? 由于面向对象语言的继承特性,子类具有父类的所有方法,因此可以将基类替换为特定子类,子类也可以调用父类中的方法,但要确保完全调用,必须使用相同的光名称

3358 www.Sina.com/http://www.Sina.com /这样的话,呼叫没有问题。 否则,我将List类型的参数传递给父类,但在子类中重写的方法参数为ArrayList。 当客户端使用时,它会传递LinkedList类型的参数,而当使用父类时,程序可以正常运行。但是,根据LSP原则,如果将其替换为子类,程序将会出现问题。 同样,后置条件也是如此。

能很好地理解吗?

但是,这个原则没有这么简单,语法上肯定没有任何问题。 但是,同时,功能上也必须与置换前相同。

这有点困难。 由于子类将重写父类的方法,因此子类的方法通常与父类的方法不同。 但是,如果随意重写父类的方法,就会违反LSP原则。 请看以下示例:

首先有一个父母,水果类:

publicclassfruit(voidintroduce ) { System.out.println ) (我是水果之父…); }接下来是苹果的子类。

publicclassappleextendsfruit { @ overridevoidintroduce (} { system.out.println (我是水果的子类——苹果) ); }客户端代码如下。

publicstaticvoidmain (string [ ] args ) { Fruit fruit=new Fruit ); HashMap map=new HashMap (; fruit.introduce (; }执行结果:

我是水果父母. 那么,如果遵循LSP的原则,所有父母都会出现在那里。

方都能换成其子类,代码如下:


public static void main(String[] args) { Apple fruit = new Apple(); HashMap map = new HashMap<>(); fruit.introduce();}

那么运行结果就会变成:

我是水果子类——苹果

与原来的输出不同,程序的功能被改变了,违背了 LSP 原则。

因此,可以看到, LSP 原则最重要的一点就是:避免子类重写父类中已经实现的方法。这就是 LSP 原则的本质。这里由于 Fruit 父类已经实现了 introduce 方法,因此子类应该避免再对其进行重写,如果需要增加个性化,就应该对父类进行扩展,而不是重写,否则也会违背开闭原则。

一般来讲,程序中父类大多是抽象类,因为父类只是一个框架,具体功能还需要子类来实现。因此很少直接去 new 一个父类。而如果出现这种情况,那么就说明父类中实现的代码已经很好了,子类只需要对其进行扩展就会,尽量避免对其已经实现的方法再去重写。

依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则的原始定义是这样的:

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语言中的表现就是:

模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。接口或抽象类不依赖于实现类。实现类依赖于接口或抽象类。

简而言之,我们要尽可能使用接口或抽象类。也就是“面向接口编程” 或者说 “面向抽象编程” ,也就是说程序中要尽可能使用抽象类或是接口。

可能现在还没有使用抽象的习惯,可以看一个例子:比如我们要写一个人吃苹果的程序,首先创建一个苹果类:

public class Apple { public void eaten() { System.out.println("正在吃苹果..."); }}

然后写人的类:

public class Person { void eat(Apple apple) { apple.eaten(); } }

这样,在客户端中我们就可以这样调用:

Person xiaoMing = new Person();Apple apple = new Apple();xiaoMing.eat(apple);

但是这样就有一个问题,如果我不仅仅想吃苹果,还想吃橘子怎么办,在 Person 类中再加一个函数处理橘子?那么吃别的水果呢??总不能程序中每增加一种水果就要在其中增加一个函数吧。

这时候就需要接口了(抽象类也是可以的)

程序就可以这样更改,增加一个水果接口:

public interface Fruit { void eaten(); }

让苹果类实现该接口:

public class Apple implements Fruit { @Override public void eaten() { System.out.println("正在吃苹果..."); }}

然后将Person类中的函数的参数稍作修改:

public class Person { void eat(Fruit fruit) { fruit.eaten(); }}

这样,客户端代码也稍作修改:

Person xiaoMing = new Person();Fruit apple = new Apple();xiaoMing.eat(apple);

这样改完之后,如果再增加一种新的水果,就不需要改变 Person 类了,方便吧。

那么再回来说我们的依赖倒置原则,依赖就是类与类之间的依赖,主要是函数传参,上面的例子已经很明白地介绍了,参数要尽可能使用抽象类或接口,这就是“高层模块不应该依赖底层模块,两者都应该依赖其抽象” 的解释。那么如果要实现这个,就要求每个实现类都应该尽可能从抽象中派生,这就是上面的 “细节应该依赖抽象”

简而言之,该原则主要有下面几点要求:

每个类都尽量要有接口或抽象类,或者两者都有变量的表面类型尽量是接口或者抽象类(比如程序中的 Fruit apple = new Apple() Fruit 是表面类型, Apple 是实际类型)任何类都不应该从具体类中派生 接口隔离原则(Interface Segregation Principle)

首先声明,该原则中的接口,是一个泛泛而言的接口,不仅仅指Java中的接口,还包括其中的抽象类。

首先,给出该原则的定义,该原则有两个定义:

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.

类间的依赖关系应该建立在最小的接口上。

这是什么意思呢,这是让我们把接口进行细分。举个例子,如果一个类实现一个接口,但这个接口中有它不需要的方法,那么就需要把这个接口拆分,把它需要的方法提取出来,组成一个新的接口让这个类去实现,这就是接口隔离原则。简而言之,就是说,接口中的所有方法对其实现的子类都是有用的。否则,就将接口继续细分。

看起来,该原则与单一职责原则很相像。确实很像,二者都是强调要将接口进行细分,只不过分的方式不同。单一职责原则是按照 职责 进行划分接口的;而接口隔离原则则是按照实现类对方法的使用来划分的。可以说,接口隔离原则更细一些。

要想完美地实现该原则,基本上就需要每个实现类都有一个专用的接口。但实际开发中,这样显然是不可能的,而且,这样很容易违背单一职责原则(可能出现同一个职责分成了好几个接口的情况),因此我们能做的就是尽量细分。

该原则主要强调两点:

接口尽量小。

就像前面说的那样,接口中只有实现类中有用的方法。

接口要高内聚

就是说,在接口内部实现的方法,不管怎么改,都不会影响到接口外的其他接口或是实现类,只能影响它自己。

wydxf法则(Law of Demeter)

wydxf法则也叫最少知道原则(Least Knowledge Principle, LKP ),虽然名称不同,但都是同一个意思:一个对象应该对其他对象有最少的了解。

该原则也很好理解,我们在写一个类的时候,应该尽可能的少暴露自己的接口。什么意思呢?就是说,在写类的时候,能不 public 就不 public ,所有暴露的属性或是接口,都是不得不暴露的,这样的话,就能保证其他类对这个类有最少的了解了。

这个原则也没什么需要多讲的,调用者只需要知道被调用者公开的方法就好了,至于它内部是怎么实现的或是有其他别的方法,调用者并不关心,调用者只关心它需要用的。反而,如果被调用者暴露太多不需要暴露的属性或方法,那么就可能导致调用者滥用其中的方法,或是引起一些其他不必要的麻烦。

开闭原则(Open Closed Principle)

开闭原则所有设计模式原则中,最基础的那个原则。首先,还是先来看一下它的定义:

Software entities like classes, modules and functions should be open for extension but closed for modifications.

翻译过来就是:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。

开闭原则之前也提到过,在 LSP 中,我们说,要避免子类重写父类中已经实现的方法。这个时候,继承父类就是对其进行扩展,但没有进行修改。这就是开闭原则一个很好的体现。

那是不是开闭原则与 LSP 原则混淆了呢?并不是, LSP 原则强调的是基类与子类的关系,只是其中的一种实现方式用到了开闭原则而已。

那么开闭原则具体是什么呢?可以说,开闭原则贯穿于以上五个设计模式原则。开闭原则中的对扩展开放,就是说,如果在项目中添加一个功能的时候,可以直接对代码进行扩展;如果要修改某一部分的功能时,我们应该做的是,尽量少做修改(完全不修改是不可能的),但是修改的时候,要保留原来的功能,只是在上面扩展出新的功能,就像版本更新一样,更新后,依然支持旧版本。

开闭原则是一个特别重要的原则,无论是在设计模式中还是在其他领域中,这都是一个非常基础的设计理念。

总结

总而言之,这六个设计模式原则是以后学习设计模式的基础,它们的共同目的就是 SOLID ——建立稳定、灵活、健壮的设计。

但是原则归原则,有时候由于种种原因,这些条条框框是不得不去打破的,一味地遵循它是不会有好果子吃的(就像接口隔离原则,不可能创建那么多的接口)。因此我们应该正确使用这些原则,主要目的还是为了我们软件的稳定性、灵活性、健壮性和可维护性。

注:本文所有原则定义皆出自《设计模式之禅》。


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐