【设计模式】设计原则--面向接口编程你理解的对吗?

最近看了《Head First Design Patterns》这本书。正如其名,这本书讲的是设计模式(Design Patterns),而这本书的第一章,讲的是很重要的一些设计原则(Design Principles)。git

  • Identify the aspects of your application that vary and separate them from what stays the same.(识别应用程序中各个方面的变化,并将它们与保持不变的部分分开。)github

  • Program to an interface, not an implementation.(面向接口而不是实现编程。)编程

  • Favor composition over inheritance.(优先考虑组成而不是继承。)设计模式

其中令我感触颇深的是,“面向接口而不是实现编程”颠覆了我一直以来的认识。app

文章中示例代码为原书中截图,C#代码参照文末提供连接。设计

开始

书中用了一个很形象的示例:模拟鸭子程序(SimUDuck)。系统的最初设计使用标准的OO技术,并建立了一个Duck基类,全部其余Duck类型都继承自该基类。3d

Duck Class Diagram

设计系统时考虑到鸭子都会发出叫声,并且都会游泳,因而将Quack方法和Swim方法定义到Duck基类中并实现;此外,并非全部的鸭子都是长得同样的,那么将Display方法在Duck基类中定义为抽象的,全部继承自Duck基类的子类编写本身的实现。code

新的需求产生了!咱们须要让系统中的鸭子能够飞。从面向对象的角度来考虑,若是咱们想要代码重用,只须要在Duck基类中添加方法Fly并实现它——全部的鸭子子类都是继承自Duck基类的——就实现了让鸭子飞的功能。咱们经过继承实现了代码重用,很轻松就解决了问题。orm

也许咱们须要深刻考虑一下,全部的鸭子都会飞吗?玩具橡胶鸭呢?咱们把Fly方法的定义及实现放到了Duck基类中,全部继承自它的子类都继承到了Fly方法,其中也包括了不该继承Fly方法的子类。若是按照上面的方案,那咱们只能在RubberDuck橡胶鸭子类中重写父类的Fly方法让RubberDuck执行Fly的时候什么都不作。对象

再深刻一些,若是咱们的系统中除了橡胶鸭外,还有其余各类鸭子,好比木头鸭子呢?这时DecoyDuck木头鸭子继承来的Quack方法出现了问题——木头鸭子不会叫!咱们只好再把DecoyDuck中的Quack方法重写了......

若是咱们改用接口会怎么样呢?把QuackFly方法从基类中拿出来,分别在IQuackableIFlyable接口中定义,而后咱们不一样的子类根据须要来继承接口,并实现QuackFly方法。

Interface Class Diagram

当咱们有不少个子类鸭子的时候,就要分别为每一个继承了IQuackableIFlyable接口的子类来编写QuackFly的实现方法,这彻底破坏了代码重用!值得注意的是,虽然咱们在这里使用了接口,但这并非面向接口编程。

封装变化

这里引入第一条设计原则:Identify the aspects of your application that vary and separate them from what stays the same.(识别应用程序中各个方面的变化,并将它们与保持不变的部分分开。)

换言之:take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don’t.(将变化的部分封装起来,以便之后能够更改或扩展变化的部分而不会影响那些不变的部分。)

这样带来的好处是,咱们能够进行更少的代码更改来实现需求功能,减小因代码更改而带来的意想不到的影响,而且提升了系统灵活性。

咱们知道Duck的不一样子类中,QuackFly的行为是会发生变化的,那么咱们将QuackFly方法从Duck基类中拿出来,并为QuackFly方法分别建立一些类,来实现各类不一样的行为。

Encapsulation Varies

面向接口而不是实现编程

设计原则:Program to an interface, not an implementation.(面向接口而不是实现编程。)

在这里,面向接口而不是实现编程,和封装变化是相辅相成的。值得注意的是,这里所说的接口,并非咱们代码层面上的interface,"面向接口编程(Program to an interface)所表达的意思其实是面向基类编程(Program to a supertype),核心思想是利用面向对象编程的多态性。在代码的具体实现上,咱们既能够用Interface来做为咱们所面向的接口,也能够用一个抽象的基类来做为咱们面向的接口。遵循面向接口编程,对模拟鸭子程序的FlyQuack行为进行设计,咱们能够定义接口IFlyBehaviorIQuackBehavior来表明行为FlyQuack,接口的实现则是行为具体的表现形式。咱们能够将接口的不一样实现类,来赋值给Duck的不一样子类,从而利用继承多态来实现面向接口编程。类图以下:

Program To Interface

FlyBehavior是一个全部不一样的Fly类都要继承的接口或基类,其中定义了Fly方法。不一样的Fly类有不一样的Fly方法实现。QuackBehavior相似。

接下来咱们对Duck类进行更改,将FlyQuack委托出去,再也不经过Duck类或其子类的方法来实现。

  1. 首先咱们在Duck类中定义两个表明FlyBehaviorQuackBehavior的变量。这两个变量的值是不一样的Duck所须要的特定FlyBehaviorQuackBehavior的子类:Instance Variables

  2. 而后实现PerformQuack方法:Implement PerformQuack

  3. FlyBehaviorQuackBehavior赋值:Assignment

至此咱们就实现了面向接口编程。

咱们还能够动态设置Duck的行为,只须要为Duck类的FlyBehaviorQuackBehavior提供Set方法(在C#中,使用自动属性便可)。

优先考虑组成而不是继承

Favor composition over inheritance.(优先考虑组成而不是继承。)

HAS-A(有一个)比IS-A(是一个)要好。HAS-A在咱们的Duck系统中能够描述为:每个DuckHAS-A有一个FlyBehavior,还HAS-A有一个QuackBehaviorDuck委托它们来处理FlyQuack的行为。优先考虑组合而不是继承让咱们的系统拥有更多的灵活性,封装变化,还能够在运行时动态更改类的行为。

示例代码

示例代码

相关文章
相关标签/搜索