【设计模式】如何用组合替代继承

若是问面向对象的三大特征是什么,多数人都能回答出来:封装、继续、多态。

继续 作为三大特征之一,最近却越来越不推荐使用,更有极端的语言,直接语法中就不支持继续,例如 Go。这又是为什么呢?

为什么不推荐使用继续?

假设我们要设计一个关于鸟的类。

我们将“鸟类”界说为一个抽象类 AbstractBird。所有更细分的鸟,好比麻雀、鸽子、乌鸦等,都继续这个抽象类。

大部分鸟都市飞,那我们可不可以在 AbstractBird 抽象类中,界说一个 Fly() 方式呢?

谜底是否认的。只管大部分鸟都市飞,但也有特例,好比鸵鸟就不会飞。鸵鸟继续具有 Fly() 方式的父类,那鸵鸟就具有“飞”这样的行为,这显然不符合我们对现实天下中事物的熟悉。

解决方案一

在鸵鸟这个子类中重写 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() 方式,抛出异常。

这违反了迪米特规则(也叫最少知识原则),露出不应露出的接口给外部,增添了类使用过程中被误用的概率。

Golang简单入门教程——函数进阶篇

解决方案二

通过 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# 。

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/15846.html