欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-12 10:22:07
最近看了《Head First Design Patterns》这本书。正如其名,这本书讲的是设计模式(Design Patterns),而这本书的第一章,讲的是很重要的一些设计原则(Design Principles)。 Identify the aspects of your applicati ......

最近看了《head first design patterns》这本书。正如其名,这本书讲的是设计模式(design patterns),而这本书的第一章,讲的是很重要的一些设计原则(design principles)。

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

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

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

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

文章中示例代码为原书中截图,c#代码参照文末提供链接。

开始

书中用了一个很形象的示例:模拟鸭子程序(simuduck)。系统的最初设计使用标准的oo技术,并创建了一个duck基类,所有其他duck类型都继承自该基类。

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

设计系统时考虑到鸭子都会发出叫声,而且都会游泳,于是将quack方法和swim方法定义到duck基类中并实现;此外,并不是所有的鸭子都是长得一样的,那么将display方法在duck基类中定义为抽象的,所有继承自duck基类的子类编写自己的实现。

新的需求产生了!我们需要让系统中的鸭子可以飞。从面向对象的角度来考虑,如果我们想要代码重用,只需要在duck基类中添加方法fly并实现它——所有的鸭子子类都是继承自duck基类的——就实现了让鸭子飞的功能。我们通过继承实现了代码重用,很轻松就解决了问题。

也许我们需要深入考虑一下,所有的鸭子都会飞吗?玩具橡胶鸭呢?我们把fly方法的定义及实现放到了duck基类中,所有继承自它的子类都继承到了fly方法,其中也包括了不应继承fly方法的子类。如果按照上面的方案,那我们只能在rubberduck橡胶鸭子类中重写父类的fly方法让rubberduck执行fly的时候什么都不做。

再深入一些,如果我们的系统中除了橡胶鸭外,还有其他各种鸭子,比如木头鸭子呢?这时decoyduck木头鸭子继承来的quack方法出现了问题——木头鸭子不会叫!我们只好再把decoyduck中的quack方法重写了......

如果我们改用接口会怎么样呢?把quackfly方法从基类中拿出来,分别在iquackableiflyable接口中定义,然后我们不同的子类根据需要来继承接口,并实现quackfly方法。

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

当我们有很多个子类鸭子的时候,就要分别为每个继承了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方法分别创建一些类,来实现各种不同的行为。

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

面向接口而不是实现编程

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

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

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

flybehavior是一个所有不同的fly类都要继承的接口或基类,其中定义了fly方法。不同的fly类有不同的fly方法实现。quackbehavior类似。

接下来我们对duck类进行更改,将flyquack委托出去,不再通过duck类或其子类的方法来实现。

  1. 首先我们在duck类中定义两个代表flybehaviorquackbehavior的变量。这两个变量的值是不同的duck所需要的特定flybehaviorquackbehavior的子类:【设计模式】设计原则--面向接口编程你理解的对吗?

  2. 然后实现performquack方法:【设计模式】设计原则--面向接口编程你理解的对吗?

  3. flybehaviorquackbehavior赋值:【设计模式】设计原则--面向接口编程你理解的对吗?

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

我们还可以动态设置duck的行为,只需要为duck类的flybehaviorquackbehavior提供set方法(在c#中,使用自动属性即可)。

优先考虑组成而不是继承

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

has-a(有一个)比is-a(是一个)要好。has-a在我们的duck系统中可以描述为:每一个duckhas-a有一个flybehavior,还has-a有一个quackbehaviorduck委托它们来处理flyquack的行为。优先考虑组合而不是继承让我们的系统拥有更多的灵活性,封装变化,还可以在运行时动态更改类的行为。

示例代码

示例代码