设计模式六大原则概述
在面向对象软件开发过程中,一些有经验的软件开发人员通常会采用设计模式来解决一些日常工作中的一些问题。设计模式是前人在软件开发的过程中总结出来的一些解决问题的方案,并且经受住了时间的考验和广大软件开发人员的不断验证。在软件开发过程中,如果我们合理的使用设计模式可以提高代码的复用性和可维护性。为了保证引用设计模式的合理性,我们在使用设计模式的时候一定要遵守面向对象设计的六大原则:单一职责原则、里氏替换原则、依赖倒转原则、接口隔离原则、迪米特法则、开闭原则。设计模式就是通过实现这些原则,来达到代码复用和增加可维护性的目的的。
下面分别介绍一下,设计模式的六大原则:
■单一职责原则(Single Responsibility Principle)
定义:单一职责原则规定一个类应该只有一个引起它发生变化的原因,即一个类应该只有一个职责。
概述:如果一个类承担多个职责,就相当于把这些职责耦合在了一起,如果其中的一个职责发生变化,有可能会影响到其它职责的正常运行。另外多个职责耦合在一起,还会降低类的复用性。在软件开发过程中,如果想要避免这种现象的发生,就要使程序尽可能的遵守单一职责原则。
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。也就是说职责P1和P2被耦合在了一起。
产生问题的原因:程序设计不合理,或是职责扩散。
◆职责扩散:因为某种原因,某一职责被划分为了多个粒度更细的职责。
解决问题的方法:在写程序时遵守单一职责原则,将不同的职责P1和P2分别封装到不同的类或模块中。能够引起职责扩散的原因比较多,所以我们在前期进行程序设计的时候,应尽量的遵守单一职责原则。
遵守单一职责原则能给我们的程序带来的好处有:
◆让一个类只负责一项职责,增强类的可读性,提高系统的可维护性。
◆使我们能够更容易的写出符合高内聚低耦合核心思想的程序。
注意事项:过度的遵守单一职责原则也会带来一些弊端,遵守单一职责原则势必会造成代码量的增加。如果过度的使用,会使代码显得过于臃肿,当一个类的逻辑比较简单,类中的方法数量比较少,并且不需要经常改动的时候,我们可以根据情况决定是否遵守单一职责原则。
■里氏转换原则(Liskov Substitution Principle)
定义:里氏转换原则规定在程序里面只要是基类出现的地方,都可以用子类进行替换。
概述:里氏转换原则描述的是父类和子类之间的关系,只有继承关系存在时,里氏转换原则才存在。里氏转换原则是继承复用的基础,只有当子类可以替换掉父类,并且软件单位的功能不受到任何影响时,父类才可以被复用。
问题由来:类A有一职责P1,现在需要对类A的职责进行扩展,新增加职责P2, 扩展职责的任务在子类B中完成,则子类B在完成新职责P2的同时,有可能会导致原有职责P1不能正常运行。
解决问题的方法:在继承时,遵守里氏转换原则。类B继承父类A时,除添加完成职责P2 的方法外,尽量不要重写父类的非抽象方法。
遵守里氏转换原则能给我们的程序带来的好处有:
◆子类继承父类之后,可以增加自己特有的职责方法。
◆可以给面向抽象编程提供基础,合理的遵守里氏转换原则可以使我们的程序的稳定性和可扩展性更好。
注意事项:在使用里氏转换原则时需要注意的地方是,父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵守这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承系统造成破坏。通俗的讲就是:子类可以扩展父类的功能,但是不能改变父类的原有功能。
■依赖倒置原则(Dependency Inversion Principle)
定义:在程序里高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
概述:依赖倒置原则的核心思想是面向抽象编程,它基于这样一个事实,相对于细节的多变性,抽象的东西要更加的稳定,以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在C# 中,抽象指的是接口或抽象类,细节就是具体的实现类,使用接口或者是抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
问题由来:类A直接依赖类B,如果要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种情况,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决问题的方法:让类A依赖接口I,类B和类C各自实现接口I,类A通过接口I间接和类B或者类C发生联系,这样就可以解决上面的问题。就像在三层架构中,让业务逻辑层和数据库连接层通过依赖抽象取得联系,这样我们可以在数据库连接层随意切换数据源,而不影响业务逻辑层的正常功能。
遵守依赖倒置原则能给我们的程序带来的好处有:
遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低因修改程序造成的风险。采用依赖倒置原则给多人并行开发带来了极大的便利,参与协作开发的人越多、项目越庞大,采用依赖倒置原则的意义就越大。
注意事项: 在实际编程中,我们一般需要做到,低层模块尽量都要有抽象类或接口,变量的声明类型尽量是抽象类或接口,使用继承时遵循里氏转换原则。
■接口隔离原则(Interface Segregation Principle)
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
概述: 在程序设计中,不要让类去依赖一个含有它不需要的方法的接口。如果让一个类依赖了含有它不需要的方法的接口,那么就面临着由于这些不需要的方法发生了改变,而给该类带来的改变。一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类B和类D来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决问题的方法:采用接口隔离原则,将接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。
遵守依赖倒置原则能给我们的程序带来的好处有:
这样避免了建立的接口过于臃肿,使子类中只需要去实现它自身需要的方法。可以提高程序设计的灵活性和接口的可复用性,降低类之间的耦合,避免和不相关的类发生联系。
注意事项: 使用接口隔离原则需要注意的地方:接口应尽量的小,但是要有限度,对接口细化可以提高程序设计的灵活性,但是如果接口过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。
■迪米特法则(Law of Demeter)
定义:迪米特法则规定,一个对象应该对其它对象保持最少的了解。
概述:迪米特法则又叫最少知道原则,通俗的讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都应尽量的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄露任何信息。迪米特法则还有一个更简单的定义就是只与直接的朋友通信。
问题由来:类与类之间的关系越密切,耦合度就越大,类之间的相互影响也就越大。
解决问题的方法:遵循迪米特法则,如果两个类不需要直接通信,那么这两个类就不应当发生直接的相互引用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过一个中间类来调用。
遵守迪米特法则能给我们的程序带来的好处有:
采用迪米特法则的程序设计可以降低类之间的耦合,由于每个类都减少了对其他类的依赖,因此,很容易使得系统的功能模块更加的独立,进而也就减少了类之间的相互依赖关系。
注意事项:使用迪米特法则是为了降低类之间的耦合度,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。但是凡事都要有度,虽然可以避免与非直接的类通信,但是要通信,必然会通过一个中间类来发生联系。如果过分的使用迪米特法则,就会产生大量的中间类,导致系统复杂度变大。所以在采用迪米特法则时要做好权衡,既要做到结构清晰,又要高内聚低耦合。
■开闭原则(Open Closed Principle)
定义:一个软件实体如类、模块或函数应该对扩展开发,对修改关闭。
概述:开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。在实际开发的过程中,如果让我们在设计阶段就罗列出系统所有可能的行为,并把这些行为加入到抽象底层,这种做法是不现实的,也是不可取的。因此在做程序从设计的时候,我们应该接受修改,拥抱变化,使我们的代码对扩展开放,对修改关闭。只有这样做才能使我们的程序架构更稳定。
问题由来:在软件的生命周期内,我们会因为功能的变化或系统的升级等原因需要对软件原有代码进行修改。在修改时,可能会给原有代码带来严重的错误,导致我们不得不对整个功能进行重构,并且需要将原有代码经过重新测试。
解决问题的方法:遵守开闭原则,尽量通过扩展软件实体的行为来实现变化,而不是修改已有的代码来实现变化。
遵守开闭原则能给我们的程序带来的好处有:
◆使模块具有更好的可复用性:
在软件完成以后,我们仍然可以对软件进行扩展,可以通过增加新组件的方式非常灵活的加入新功能。因此,我们的软件系统就可以通过不断地增加新组件的方式,来满足不断变化的需求。
◆可维护性:
对于已有的软件系统的组件,特别是它的抽象底层我们不去做修改。这样,我们就不用担心软件系统中原有组件的稳定性。这就使得在变化中,软件系统有一定的稳定性和延续性。
■总结:
在实际开发过程中,我们不应该刻板的遵守设计模式六大原则,而是应该根据实际情况灵活的应用。只有遵守的合理才能够设计出可维护性好、复用性高的软件系统。 通篇没有代码展示,似乎不太合理,下面是我在练习时写的一个完整的项目,托管在了github(SafetyMS) 上,里面的程序设计基本上符合了设计模式的六大原则。