关于接口:C#中的多重继承

Multiple Inheritance in C#

由于多重继承是不好的(它使源代码更加复杂),C不直接提供这样的模式。但有时拥有这种能力会有所帮助。

例如,我可以使用接口和三个类似的类来实现缺少的多重继承模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst
{
    public void FirstMethod() { Console.WriteLine("First"); }
}

public class Second:ISecond
{
    public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

每次我向其中一个接口添加方法时,我都需要同时更改第一个和第二个类。

是否有一种方法将多个现有类注入到一个新类中,就像C++中的那样?

也许有一个使用某种代码生成的解决方案?

或者它看起来像这样(虚构的C语法):

1
2
public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

这样当我修改其中一个接口时就不需要首先更新类和第二个。

编辑

也许最好考虑一个实际的例子:

您有一个现有的类(例如基于ITextTCPClient的基于文本的TCP客户端),您已经在项目中的不同位置使用了它。现在,您觉得需要创建类的组件,以便Windows窗体开发人员轻松访问。

据我所知,你目前有两种方法可以做到这一点:

  • 编写从组件继承的新类,并使用类本身的实例实现texttcplient类的接口,如FirstAndSecond所示。

  • 编写一个从texttcpclient继承并以某种方式实现IComponent的新类(实际上尚未尝试此操作)。

  • 在这两种情况下,您都需要按方法而不是按类进行工作。因为您知道我们需要texttcpclient和component的所有方法,所以将这两个方法组合成一个类是最简单的解决方案。

    为了避免冲突,这可以通过代码生成来完成,在代码生成之后可以更改结果,但是手工输入这是一种纯粹的麻烦。


    考虑使用组合而不是尝试模拟多重继承。您可以使用接口定义组成组件的类,例如:ISteerable表示SteeringWheel类型的属性,IBrakable表示BrakePedal类型的属性等。

    完成后,可以使用添加到C 3.0的扩展方法功能进一步简化这些隐含属性上的调用方法,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public interface ISteerable { SteeringWheel wheel { get; set; } }

    public interface IBrakable { BrakePedal brake { get; set; } }

    public class Vehicle : ISteerable, IBrakable
    {
        public SteeringWheel wheel { get; set; }

        public BrakePedal brake { get; set; }

        public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
    }

    public static class SteeringExtensions
    {
        public static void SteerLeft(this ISteerable vehicle)
        {
            vehicle.wheel.SteerLeft();
        }
    }

    public static class BrakeExtensions
    {
        public static void Stop(this IBrakable vehicle)
        {
            vehicle.brake.ApplyUntilStop();
        }
    }


    public class Main
    {
        Vehicle myCar = new Vehicle();

        public void main()
        {
            myCar.SteerLeft();
            myCar.Stop();
        }
    }


    Since multiple inheritance is bad (it makes the source more complicated) C# does not provide such a pattern directly. But sometimes it would be helpful to have this ability.

    C和.NET clr没有实现mi,因为他们还没有得出它将如何在C、vb.net和其他语言之间进行交互操作的结论,而不是因为"它将使源代码更加复杂"

    mi是一个有用的概念,未回答的问题是这样的:"当在不同的超类中有多个公共基类时,您会怎么做?"

    Perl是我使用过的唯一一种语言,在这种语言中,mi工作得很好。.NET可能有一天会引入它,但现在还没有,clr已经支持mi了,但是正如我所说的,还没有为它提供语言构造。

    在此之前,您只能使用代理对象和多个接口:(


    我也希望这样——这是我个人所说的混合词,尽管我意识到这是一个超负荷的词。我希望能够指定用于实现接口的变量,并可以选择为特定方法提供自己的实现。

    我在博客中更详细地描述了这一点——尽管在一个蓄意夸大继承意义的背景下。

    我看不出为什么这不能在C编译器中实现-但这是另一种语言复杂性…


    我创建了一个C后编译器,它可以实现以下功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    using NRoles;

    public interface IFirst { void FirstMethod(); }
    public interface ISecond { void SecondMethod(); }

    public class RFirst : IFirst, Role {
      public void FirstMethod() { Console.WriteLine("First"); }
    }

    public class RSecond : ISecond, Role {
      public void SecondMethod() { Console.WriteLine("Second"); }
    }

    public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

    可以将后期编译器作为Visual Studio后期生成事件运行:

    C:\some_path
    roles-v0.1.0-bin
    utate.exe"$(TargetPath)"

    在同一个程序集中,您可以这样使用它:

    1
    2
    3
    var fas = new FirstAndSecond();
    fas.As<RFirst>().FirstMethod();
    fas.As<RSecond>().SecondMethod();

    在另一个程序集中,您可以这样使用它:

    1
    2
    3
    var fas = new FirstAndSecond();
    fas.FirstMethod();
    fas.SecondMethod();

    可以有一个抽象基类实现ifirst和isecond,然后仅从该基继承。


    mi并不坏,每个认真使用它的人都喜欢它,而且它不会使代码复杂化!至少不比其他构造更复杂的代码。坏代码是坏代码,不管mi是否在图片中。

    不管怎样,我有一个很好的解决方案来解决我想要分享的多重继承问题,它在:http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog上,或者你可以按照我的sig中的链接来做……:)


    是的,使用接口很麻烦,因为每当我们在类中添加方法时,都必须在接口中添加签名。另外,如果我们已经有了一个类,它有很多方法,但是没有接口呢?我们必须为所有要从中继承的类手动创建接口。最糟糕的是,如果子类要从多个接口继承,我们必须实现子类中接口中的所有方法。

    通过遵循门面设计模式,我们可以使用访问器模拟从多个类继承。在需要继承的类中使用get;set;将类声明为属性,并且所有公共属性和方法都来自该类,并且在子类的构造函数中实例化父类。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
     namespace OOP
     {
         class Program
         {
             static void Main(string[] args)
             {
                 Child somechild = new Child();
                 somechild.DoHomeWork();
                 somechild.CheckingAround();
                 Console.ReadLine();
             }
         }

         public class Father
         {
             public Father() { }
             public void Work()
             {
                 Console.WriteLine("working...");
             }
             public void Moonlight()
             {
                 Console.WriteLine("moonlighting...");
             }
         }


         public class Mother
         {
             public Mother() { }
             public void Cook()
             {
                 Console.WriteLine("cooking...");
             }
             public void Clean()
             {
                 Console.WriteLine("cleaning...");
             }
         }


         public class Child
         {
             public Father MyFather { get; set; }
             public Mother MyMother { get; set; }

             public Child()
             {
                 MyFather = new Father();
                 MyMother = new Mother();
             }

             public void GoToSchool()
             {
                 Console.WriteLine("go to school...");
             }
             public void DoHomeWork()
             {
                 Console.WriteLine("doing homework...");
             }
             public void CheckingAround()
             {
                 MyFather.Work();
                 MyMother.Cook();
             }
         }


     }

    通过这种结构,类子类可以访问类父类和类母的所有方法和属性,模拟多重继承,继承父类的一个实例。不完全一样,但很实用。


    如果您能够接受这样的限制,即ifirst和isecond的方法必须只与ifirst和isecond的合同交互(如您的示例中所示)。你可以用扩展方法做你要求的事情。实际上,这种情况很少发生。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface IFirst {}
    public interface ISecond {}

    public class FirstAndSecond : IFirst, ISecond
    {
    }

    public static MultipleInheritenceExtensions
    {
      public static void First(this IFirst theFirst)
      {
        Console.WriteLine("First");
      }

      public static void Second(this ISecond theSecond)
      {
        Console.WriteLine("Second");
      }
    }

    //

    1
    2
    3
    4
    5
    6
    public void Test()
    {
      FirstAndSecond fas = new FirstAndSecond();
      fas.First();
      fas.Second();
    }

    所以,基本的想法是在接口中定义所需的实现…这些必需的东西应该支持扩展方法中的灵活实现。任何时候你需要"向接口添加方法"而不是添加一个扩展方法。


    由于多重继承(MI)问题不时出现,我想添加一种方法来解决组合模式的一些问题。

    我建立在问题中提出的IFirstISecondFirstSecondFirstAndSecond方法的基础上。我将示例代码减少到IFirst,因为无论接口/mi基类的数量如何,模式都保持不变。

    假设使用mi-FirstSecond,两者都将派生自相同的基类BaseClass,只使用来自BaseClass的公共接口元素。

    这可以通过在FirstSecond实现中添加对BaseClass的容器引用来表示:

    1
    2
    3
    4
    5
    6
    class First : IFirst {
      private BaseClass ContainerInstance;
      First(BaseClass container) { ContainerInstance = container; }
      public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); }
    }
    ...

    当引用来自BaseClass的受保护接口元素,或者当FirstSecond是mi中的抽象类时,情况变得更加复杂,需要它们的子类实现一些抽象部分。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class BaseClass {
      protected void DoStuff();
    }

    abstract class First : IFirst {
      public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
      protected abstract void DoStuff(); // base class reference in MI
      protected abstract void DoSubClassStuff(); // sub class responsibility
    }

    C允许嵌套类访问其包含类的受保护/私有元素,因此可以使用它链接来自First实现的抽象位。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class FirstAndSecond : BaseClass, IFirst, ISecond {
      // link interface
      private class PartFirst : First {
        private FirstAndSecond ContainerInstance;
        public PartFirst(FirstAndSecond container) {
          ContainerInstance = container;
        }
        // forwarded references to emulate access as it would be with MI
        protected override void DoStuff() { ContainerInstance.DoStuff(); }
        protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
      }
      private IFirst partFirstInstance; // composition object
      public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
      public FirstAndSecond() {
        partFirstInstance = new PartFirst(this); // composition in constructor
      }
      // same stuff for Second
      //...
      // implementation of DoSubClassStuff
      private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
    }

    虽然涉及到很多样板文件,但是如果firstmethod和secondmethod的实际实现足够复杂,并且访问的私有/受保护方法的数量适中,那么这种模式可能有助于克服缺乏多重继承的问题。


    我知道我知道尽管它是不允许的,等等,有时你实际上需要它,所以对于那些:

    1
    2
    3
    class a {}
    class b : a {}
    class c : b {}

    就像我想做的那样B类:窗体(是Windows.Forms)C类:B{}

    因为函数的一半是相同的,并且与接口u必须重写它们全部


    这与劳伦斯·温厄姆的回答是一致的,但根据您的用例,这可能是一个改进,也可能不是改进——您不需要设置器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public interface IPerson {
      int GetAge();
      string GetName();
    }

    public interface IGetPerson {
      IPerson GetPerson();
    }

    public static class IGetPersonAdditions {
      public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the"ViaPerson" in the name in case the object has another Age property.
        IPerson person = getPerson.GetPersion();
        return person.GetAge();
      }
      public static string GetNameViaPerson(this IGetPerson getPerson) {
        return getPerson.GetPerson().GetName();
      }
    }

    public class Person: IPerson, IGetPerson {
      private int Age {get;set;}
      private string Name {get;set;}
      public IPerson GetPerson() {
        return this;
      }
      public int GetAge() {  return Age; }
      public string GetName() { return Name; }
    }

    现在,任何知道如何获取人员的对象都可以实现IGetPerson,它将自动拥有getAgeViaPerson()和getNameViaPerson()方法。从这一点上来说,基本上所有的个人代码都进入了igetperson,而不是进入了iperson,除了新的ivars,这两者都必须进入。在使用这些代码时,您不必担心您的igetPerson对象本身是否是一个iperson。


    多重继承通常会导致比它解决的问题更多的问题之一。在C++中,它适合给你足够的绳子来挂起你自己的模式,但是Java和C *选择了更安全的路线,不给你选择。最大的问题是,如果继承的多个类具有继承者未实现的相同签名的方法,那么该怎么做。它应该选择哪个类的方法?还是不编译?通常还有另一种方法来实现大多数不依赖多重继承的事情。


    我们似乎都在沿着这个接口路径前进,但显然,这里的另一个可能性是做OOP应该做的,并建立您的继承树…(这不是什么课堂设计吗?)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Program
    {
        static void Main(string[] args)
        {
            human me = new human();
            me.legs = 2;
            me.lfType ="Human";
            me.name ="Paul";
            Console.WriteLine(me.name);
        }
    }

    public abstract class lifeform
    {
        public string lfType { get; set; }
    }

    public abstract class mammal : lifeform
    {
        public int legs { get; set; }
    }

    public class human : mammal
    {
        public string name { get; set; }
    }

    这个结构提供了可重用的代码块,当然,OOP代码应该如何编写?

    如果这种特殊的方法不太适合这个账单,我们只需根据所需的对象创建新的类…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class Program
    {
        static void Main(string[] args)
        {
            fish shark = new fish();
            shark.size ="large";
            shark.lfType ="Fish";
            shark.name ="Jaws";
            Console.WriteLine(shark.name);
            human me = new human();
            me.legs = 2;
            me.lfType ="Human";
            me.name ="Paul";
            Console.WriteLine(me.name);
        }
    }

    public abstract class lifeform
    {
        public string lfType { get; set; }
    }

    public abstract class mammal : lifeform
    {
        public int legs { get; set; }
    }

    public class human : mammal
    {
        public string name { get; set; }
    }

    public class aquatic : lifeform
    {
        public string size { get; set; }
    }

    public class fish : aquatic
    {
        public string name { get; set; }
    }

    在我自己的实现中,我发现为MI使用类/接口虽然"形式良好",但往往过于复杂,因为您只需要为几个必要的函数调用设置所有的多重继承,而在我的情况下,实际上需要重复执行几十次。

    相反,只需简单地将不同模块化类型的静态"调用调用函数的函数"作为OOP替换就可以了。我正在研究的解决方案是RPG的"拼写系统",在这个系统中,效果需要大量混合和匹配函数调用,以在不重新编写代码的情况下给出极其多样的拼写,就像例子所示。

    现在大多数函数都可以是静态的,因为我不需要拼写逻辑实例,而类继承甚至不能在静态时使用虚拟或抽象关键字。接口根本不能使用它们。

    在IMO中,编码似乎更快、更清晰。如果您只是在做函数,而不需要继承的属性,请使用函数。


    如果x从y继承,则有两个稍微正交的效果:

  • Y将为X提供默认功能,因此X的代码只需包含与Y不同的内容。
  • 几乎任何一个地方都会出现y,可以用x来代替。

    虽然继承提供了这两个特性,但不难想象在没有其他特性的情况下两者都可以使用的情况。我所知道的任何.NET语言都没有直接实现第一个没有第二个的方法,尽管可以通过定义一个从未直接使用的基类来获得这样的功能,并且拥有一个或多个直接从它继承而不添加任何新的类(这样的类可以共享它们的所有代码,但不会是可替换的彼此之间)。但是,任何符合CLR的语言都允许使用提供接口的第二个特性(可替换性)的接口,而不需要使用第一个特性(成员重用)。



    现在使用C 8,通过接口成员的默认实现,您实际上拥有多个继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface ILogger
    {
        void Log(LogLevel level, string message);
        void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
    }

    class ConsoleLogger : ILogger
    {
        public void Log(LogLevel level, string message) { ... }
        // Log(Exception) gets default implementation
    }