c# 如何用组合替代继承

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

继承 作为三大特性之一,近来却越来越不推荐使用,更有极端的语言,直接语法中就不支持继承,例如 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() 方法,抛出异常。

这违背了迪米特法则(也叫最少知识原则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。

解决方案二

通过 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();
}

虽然现在主流的思想都是多用组合少用继承,但是从上面的例子可以看出,继承改写成组合意味着要做更细粒度的类的拆分,要定义更多的类和接口。类和接口的增多也就或多或少地增加代码的复杂程度和维护成本。所以,在实际的项目开发中,我们还是要根据具体的情况,来具体选择该用继承还是组合。

以上就是c# 如何用组合替代继承的详细内容,更多关于c# 组合替代继承的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅谈C# 类的继承

    继承 一个类可以继承自另一个类.在 C#中,类与类之间只存在单一继承.也就是说,一个类的直接基类只能有一个.当类与类之间实现继承的时候,子类可以将它的直接基类的所有成员当做自己的成员,除了类的静态构造方法.实例构造方法和析构方法.但是,虽然基类的所有成员都可以当做子类的成员,但是如果基类的成员设置了不同的访问权限,则派生类可以访问的成员也随之不同.C#的继承是可以传递的,如果类C从类B派生,而类B从类A派生,则类C将继类B的所有成员,也继承类A的所有成员(各个基类的静态构造方法.实例构造方法和析

  • C#实现组合排列的方法

         C#实现组合排列的方法 最近在做数据分析系统,里面涉及到组合排列的问题,查找了很多的资料,但是感觉很多资料都是比较零散的,达不到项目需求. 后来经过一段的时间的探索,终于实现了组合排列的功能.下面我就来简单说说吧.      需求描述:   要实现的功能就是字符或数字的组合排列.例如:ab 的所有组合为:ab,ba :  ab的所有不重复排列为:ab. 其实这也是彩票中常说的直选和组选.效果图如下:     功能实现 这里就不多说了,直接贴上实现代码吧.       1.窗体界面 窗体

  • C#子类对基类方法的继承、重写与隐藏详解

    前言 提起子类.基类和方法继承这些概念,肯定大家都非常熟悉.毕竟,作为一门支持OOP的语言,掌握子类.基类是学习C#的基础.不过,这些概念虽然简单,但是也有一些初学者可能会遇到的坑,我们一起看看吧. 子类继承基类非私有方法 首先我们看最简单的一种,子类继承自基类,但子类对继承的方法没有任何改动 class Person { public void Greeting() { Console.WriteLine("Hello, I am Person"); } } class Employ

  • C# 设计模式系列教程-组合模式

    1. 概述 将对象组合成树形结构以表示"部分-整体"的层次结构.组合模式使得用户对单个对象和组合对象的使用具有一致性. 2. 解决的问题 当希望忽略单个对象和组合对象的区别,统一使用组合结构中的所有对象(将这种"统一"性封装起来). 3. 组合模式中的角色 3.1 组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口. 3.2 叶子(Leaf):在组合中表示子节点对象,叶子节点不能有子节点. 3.3 合成部件(Composite):定义有枝

  • C#正则匹配RegexOptions选项的组合使用方法

    C#中使用正则所需要引用的命名空间是 using System.Text.RegularExpressions 它包含了8个类,用得最多是的Regex,Regex不仅可以用来创建正则表达式,而且提供了很多有用的方法. 首先来看一下如何创建一个Regex对象: new Regex(string pattern) new Regex(string pattern,RegexOptions options) 第一个参数是正则表达式字符串,第二个参数正则配置的选项,有以下几种选项: IgnoreCase

  • C# 反射与dynamic最佳组合示例代码

    在 C# 中反射技术应用广泛,至于什么是反射.........你如果不了解的话,请看下段说明,否则请跳过下段.广告一下:喜欢我文章的朋友请关注一下我的blog,这也有助于提高本人写作的动力. 反射:当你背对一个美女或帅哥却不能回头仔细观察研究时(纯属虚构,如有巧合.纯属雷同),一面小镜子就能满足你的需求.在 C# 编程过程中也经常遇到类似的情况:有一个别人写的 dll 类库你想使用却没程序文档资料......此时通过 C# Runtime 提供的功能,你可以把该 dll 类库加载到你的程序中,并

  • C#类继承中构造函数的执行序列示例详解

    前言 大家都知道类的继承规则: 1.派生类自动包含基类的所有成员.但对于基类的私有成员,派生类虽然继承了,但是不能在派生类中访问. 2.所有的类都是按照继承链从顶层基类开始向下顺序构造.最顶层的基类是System.Object类,所有的类都隐式派生于它.只要记住这条规则,就能理解派生类在实例化时对构造函数的调用过程. 不知道大家在使用继承的过程中有木有遇到过调用构造函数时没有按照我们预期的那样执行呢?一般情况下,出现这样的问题往往是因为类继承结构中的某个基类没有被正确实例化,或者没有正确给基类构

  • 详解C#的排列组合

    排列组合的概念 排列:从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(Arrangement). 组合:从m个不同的元素中,任取n(n≤m)个元素为一组,叫作从m个不同元素中取出n个元素的一个组合. 排列组合实现代码 上一个项目做的一个水路的路径规划时,用到了排列的数据结构.求任意N个点里M个点的不同顺序的组合个数. 这样求最优路径.下面贴一段不知道哪里找的排列组合的算法. public class PermutationAndCombin

  • 深入分析c# 继承

    继承是面向对象程序设计中最重要的概念之一.继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易.同时也有利于重用代码和节省开发时间. 当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可.这个已有的类被称为的基类,这个新的类被称为派生类. 继承的思想实现了 属于(IS-A) 关系.例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物. 基类和派生类 一个类可以派生自多个

  • C#实现生成所有不重复的组合功能示例

    本文实例讲述了C#实现生成所有不重复的组合功能.分享给大家供大家参考,具体如下: 给你几个字母,比如(a,b,c,d,e,f),要求生成所有不重复的组合. 这里重复的定义是这样的,任意两个元素个数相同的组合,如果其包含的元素均两两相等,即视为重复,例如abc和cba就是重复. 嗯,大概意思就是这样,下面给出代码. static void Main(string[] args) { string[] values = { "a", "b", "c"

随机推荐