若是问面向对象的三大特性是什么,多数人都能回答出来:封装、继承、多态。java
继承 做为三大特性之一,近来却愈来愈不推荐使用,更有极端的语言,直接语法中就不支持继承,例如 Go。这又是为何呢?设计模式
假设咱们要设计一个关于鸟的类。ide
咱们将“鸟类”定义为一个抽象类 AbstractBird
。全部更细分的鸟,好比麻雀、鸽子、乌鸦等,都继承这个抽象类。ui
大部分鸟都会飞,那咱们可不能够在 AbstractBird
抽象类中,定义一个 Fly()
方法呢?设计
答案是否认的。尽管大部分鸟都会飞,但也有特例,好比鸵鸟就不会飞。鸵鸟继承具备 Fly()
方法的父类,那鸵鸟就具备“飞”这样的行为,这显然不符合咱们对现实世界中事物的认识。code
解决方案一对象
在鸵鸟这个子类中重写 Fly()
方法,让它抛出异常。继承
public class AbstractBird { public virtual void Fly() { Console.WriteLine("I'm flying."); } } //鸵鸟 public class Ostrich : AbstractBird { public override void Fly() { throw new NotImplementedException("I can't fly."); } }
这种设计思路虽然能够解决问题,但不够优美。由于除了鸵鸟以外,不会飞的鸟还有不少,好比企鹅。对于这些不会飞的鸟来讲,咱们都须要重写 Fly()
方法,抛出异常。接口
这违背了迪米特法则(也叫最少知识原则),暴露不应暴露的接口给外部,增长了类使用过程当中被误用的几率。开发
解决方案二
经过 AbstractBird
类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird
和不会飞的鸟类 AbstractUnFlyableBird
,让麻雀、乌鸦这些会飞的鸟都继承 AbstractFlyableBird
,让鸵鸟、企鹅这些不会飞的鸟,都继承 AbstractUnFlyableBird
类。
此时,继承关系变成了三层,还行得通。
若是要再添加一个游泳 Swim()
的方法,那状况就复杂了,要分为四中状况:
若是再有其余行为加入,抽象类的数量就会几何级数增加。
咱们要搞清楚某个类具备哪些方法、属性,必须阅读父类的代码、父类的父类的代码……一直追溯到最顶层父类的代码。
针对“会飞”这样一个行为特性,咱们能够定义一个 Flyable
接口,只让会飞的鸟去实现这个接口。针对会游泳,定义一个 Swimable
接口,会叫定义一个 Tweetable
接口。
public interface Flyable { void Fly(); } public interface Swimable { void Swim(); } public interface Tweetable { void Tweet(); } //麻雀 public class Sparrow : Flyable, Tweetable { public void Fly() => Console.WriteLine("I am flying."); public void Tweet() => Console.WriteLine("!@#$%^&*……"); } //企鹅 public class Penguin : Swimable, Tweetable { public void Swim() => Console.WriteLine("I am swimming."); public void Tweet() => Console.WriteLine("!@#$%^&*……"); }
麻雀和企鹅都会叫,Tweet
实现了两遍,这是坏味道。咱们能够用组合来消除这个坏味道。
public interface Flyable { void Fly(); } public interface Swimable { void Swim(); } public interface Tweetable { void Tweet(); } public class FlyAbility : Flyable { public void Fly() => Console.WriteLine("I am flying."); } public class SwimAbility : Swimable { public void Swim() => Console.WriteLine("I am swimming."); } public class TweetAbility : Tweetable { public void Tweet() => Console.WriteLine("!@#$%^&*……"); } //麻雀 public class Sparrow : Flyable, Tweetable { FlyAbility flyAbility = new FlyAbility(); TweetAbility tweetAbility = new TweetAbility(); public void Fly() => flyAbility.Fly(); public void Tweet() => tweetAbility.Tweet(); } //企鹅 public class Penguin : Swimable, Tweetable { SwimAbility swimAbility = new SwimAbility(); TweetAbility tweetAbility = new TweetAbility(); public void Swim() => swimAbility.Swim(); public void Tweet() => tweetAbility.Tweet(); }
虽然如今主流的思想都是多用组合少用继承,可是从上面的例子能够看出,继承改写成组合意味着要作更细粒度的类的拆分,要定义更多的类和接口。类和接口的增多也就或多或少地增长代码的复杂程度和维护成本。因此,在实际的项目开发中,咱们仍是要根据具体的状况,来具体选择该用继承仍是组合。
本文出自极客时间 王争 老师的课程《设计模式之美》。原文示例为 java,由于我是作 C# 的,因此本文示例代码我改为了 C# 。