最近看了《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
设计系统时考虑到鸭子都会发出叫声,并且都会游泳,因而将Quack
方法和Swim
方法定义到Duck
基类中并实现;此外,并非全部的鸭子都是长得同样的,那么将Display
方法在Duck
基类中定义为抽象的,全部继承自Duck
基类的子类编写本身的实现。code
新的需求产生了!咱们须要让系统中的鸭子能够飞。从面向对象的角度来考虑,若是咱们想要代码重用,只须要在Duck
基类中添加方法Fly
并实现它——全部的鸭子子类都是继承自Duck
基类的——就实现了让鸭子飞的功能。咱们经过继承实现了代码重用,很轻松就解决了问题。orm
也许咱们须要深刻考虑一下,全部的鸭子都会飞吗?玩具橡胶鸭呢?咱们把Fly
方法的定义及实现放到了Duck
基类中,全部继承自它的子类都继承到了Fly
方法,其中也包括了不该继承Fly
方法的子类。若是按照上面的方案,那咱们只能在RubberDuck
橡胶鸭子类中重写父类的Fly
方法让RubberDuck
执行Fly
的时候什么都不作。对象
再深刻一些,若是咱们的系统中除了橡胶鸭外,还有其余各类鸭子,好比木头鸭子呢?这时DecoyDuck
木头鸭子继承来的Quack
方法出现了问题——木头鸭子不会叫!咱们只好再把DecoyDuck
中的Quack
方法重写了......
若是咱们改用接口会怎么样呢?把Quack
和Fly
方法从基类中拿出来,分别在IQuackable
和IFlyable
接口中定义,而后咱们不一样的子类根据须要来继承接口,并实现Quack
或Fly
方法。
当咱们有不少个子类鸭子的时候,就要分别为每一个继承了IQuackable
或IFlyable
接口的子类来编写Quack
或Fly
的实现方法,这彻底破坏了代码重用!值得注意的是,虽然咱们在这里使用了接口,但这并非面向接口编程。
这里引入第一条设计原则: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
的不一样子类中,Quack
和Fly
的行为是会发生变化的,那么咱们将Quack
和Fly
方法从Duck
基类中拿出来,并为Quack
和Fly
方法分别建立一些类,来实现各类不一样的行为。
设计原则:Program to an interface, not an implementation.(面向接口而不是实现编程。)
在这里,面向接口而不是实现编程,和封装变化是相辅相成的。值得注意的是,这里所说的接口,并非咱们代码层面上的interface
,"面向接口编程(Program to an interface)所表达的意思其实是面向基类编程(Program to a supertype),核心思想是利用面向对象编程的多态性。在代码的具体实现上,咱们既能够用Interface
来做为咱们所面向的接口,也能够用一个抽象的基类来做为咱们面向的接口。遵循面向接口编程,对模拟鸭子程序的Fly
和Quack
行为进行设计,咱们能够定义接口IFlyBehavior
、IQuackBehavior
来表明行为Fly
和Quack
,接口的实现则是行为具体的表现形式。咱们能够将接口的不一样实现类,来赋值给Duck
的不一样子类,从而利用继承及多态来实现面向接口编程。类图以下:
FlyBehavior
是一个全部不一样的Fly
类都要继承的接口或基类,其中定义了Fly
方法。不一样的Fly
类有不一样的Fly
方法实现。QuackBehavior
相似。
接下来咱们对Duck
类进行更改,将Fly
和Quack
委托出去,再也不经过Duck
类或其子类的方法来实现。
首先咱们在Duck
类中定义两个表明FlyBehavior
和QuackBehavior
的变量。这两个变量的值是不一样的Duck
所须要的特定FlyBehavior
、QuackBehavior
的子类:
而后实现PerformQuack
方法:
为FlyBehavior
和QuackBehavior
赋值:
至此咱们就实现了面向接口编程。
咱们还能够动态设置Duck
的行为,只须要为Duck
类的FlyBehavior
、QuackBehavior
提供Set
方法(在C#中,使用自动属性便可)。
Favor composition over inheritance.(优先考虑组成而不是继承。)
HAS-A(有一个)比IS-A(是一个)要好。HAS-A在咱们的Duck
系统中能够描述为:每个Duck
都HAS-A有一个FlyBehavior
,还HAS-A有一个QuackBehavior
,Duck
委托它们来处理Fly
和Quack
的行为。优先考虑组合而不是继承让咱们的系统拥有更多的灵活性,封装变化,还能够在运行时动态更改类的行为。