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的所有方法,所以将这两个方法组合成一个类是最简单的解决方案。
为了避免冲突,这可以通过代码生成来完成,在代码生成之后可以更改结果,但是手工输入这是一种纯粹的麻烦。
考虑使用组合而不是尝试模拟多重继承。您可以使用接口定义组成组件的类,例如:
完成后,可以使用添加到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 |
在另一个程序集中,您可以这样使用它:
1 2 3 |
可以有一个抽象基类实现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 |
所以,基本的想法是在接口中定义所需的实现…这些必需的东西应该支持扩展方法中的灵活实现。任何时候你需要"向接口添加方法"而不是添加一个扩展方法。
由于多重继承(MI)问题不时出现,我想添加一种方法来解决组合模式的一些问题。
我建立在问题中提出的
假设使用mi-
这可以通过在
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(); } } ... |
当引用来自
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允许嵌套类访问其包含类的受保护/私有元素,因此可以使用它链接来自
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继承,则有两个稍微正交的效果:
虽然继承提供了这两个特性,但不难想象在没有其他特性的情况下两者都可以使用的情况。我所知道的任何.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 } |