[英]Multiple Inheritance in C#
由於多重繼承不好(它使源代碼更復雜),C# 不直接提供這樣的模式。 但有時擁有這種能力會很有幫助。
例如,我能夠使用接口和類似的三個類來實現缺少的多重繼承模式:
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(); }
}
每次我向其中一個接口添加方法時,我也需要更改類FirstAndSecond 。
有沒有辦法像在 C++ 中那樣將多個現有類注入一個新類?
也許有使用某種代碼生成的解決方案?
或者它可能看起來像這樣(虛構的 c# 語法):
public class FirstAndSecond: IFirst from First, ISecond from Second
{ }
這樣當我修改其中一個接口時就不需要更新類 FirstAndSecond。
也許考慮一個實際的例子會更好:
您有一個現有的類(例如,基於 ITextTcpClient 的基於文本的 TCP 客戶端),您已經在項目中的不同位置使用了該類。 現在您覺得有必要創建您的類的一個組件,以便 Windows 窗體開發人員可以輕松訪問。
據我所知,您目前有兩種方法可以做到這一點:
編寫一個從組件繼承的新類,並使用類本身的實例實現 TextTcpClient 類的接口,如 FirstAndSecond 所示。
編寫一個繼承自 TextTcpClient 並以某種方式實現 IComponent 的新類(尚未實際嘗試過)。
在這兩種情況下,您都需要按方法而不是按類進行工作。 因為您知道我們將需要 TextTcpClient 和 Component 的所有方法,所以將這兩個組合到一個類中是最簡單的解決方案。
為了避免沖突,這可以通過代碼生成來完成,在代碼生成之后可以更改結果,但是手動輸入是一件很麻煩的事情。
考慮只使用組合而不是嘗試模擬多重繼承。 您可以使用接口來定義構成組合的類,例如: ISteerable
表示SteeringWheel
類型的屬性, IBrakable
表示BrakePedal
類型的屬性等。
完成此操作后,您可以使用添加到 C# 3.0 的擴展方法功能來進一步簡化對這些隱含屬性的調用方法,例如:
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();
}
}
由於多重繼承不好(它使源代碼更復雜)C# 沒有直接提供這樣的模式。 但有時擁有這種能力會有所幫助。
C# 和 .net CLR 還沒有實現 MI,因為他們還沒有得出結論,它將如何在 C#、VB.net 和其他語言之間進行互操作,而不是因為“它會使源代碼更復雜”
MI 是一個有用的概念,未回答的問題如下:-“當您在不同的超類中有多個公共基類時,您會怎么做?
Perl 是我曾經使用過的唯一一種在 MI 工作並且運行良好的語言。 .Net 可能有朝一日會推出它,但目前還沒有,CLR 確實已經支持 MI,但正如我所說,除此之外還沒有任何語言結構。
在那之前,你會被代理對象和多個接口卡住:(
我創建了一個C# 后編譯器來實現這種功能:
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\nroles-v0.1.0-bin\nutate.exe "$(TargetPath)"
在同一個程序集中,您可以像這樣使用它:
var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();
在另一個程序集中,您可以像這樣使用它:
var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
您可以擁有一個實現 IFirst 和 ISecond 的抽象基類,然后僅從該基類繼承。
現在使用 C# 8,您實際上可以通過接口成員的默認實現實現多重繼承:
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
}
MI並不壞,每個擁有(嚴重)使用它的人都喜歡它並且它不會使代碼復雜化! 至少不會比其他構造更復雜化代碼。 不管代碼是否在圖片中,壞代碼都是錯誤的代碼。
無論如何,我有一個很好的小解決方案,我希望分享多重繼承,它是在; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog或者您可以按照我的信號中的鏈接... :)
這與 Lawrence Wenham 的回答類似,但根據您的用例,它可能是也可能不是改進——您不需要二傳手。
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() 方法。 從這一點開始,基本上所有的 Person 代碼都進入 IGetPerson,而不是 IPerson,除了新的 ivars,它們都必須進入。 在使用這樣的代碼時,您不必擔心您的 IGetPerson 對象本身是否實際上是 IPerson。
我們似乎都在沿着接口路徑走下去,但這里明顯的另一種可能性是做 OOP 應該做的事情,並建立你的繼承樹...... (這不就是類設計的全部嗎?大約?)
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 代碼?
如果這種特殊方法不太符合要求,我們只需根據所需對象創建新類......
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 編碼似乎更快、更清晰。 如果您只是在做函數,並且不需要繼承屬性,請使用函數。
如果您可以忍受 IFirst 和 ISecond 的方法只能與 IFirst 和 ISecond 的合同交互的限制(就像在您的示例中一樣)......您可以使用擴展方法執行您所要求的操作。 在實踐中,這種情況很少見。
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");
}
}
///
public void Test()
{
FirstAndSecond fas = new FirstAndSecond();
fas.First();
fas.Second();
}
所以基本思想是你在接口中定義所需的實現......這個必需的東西應該支持擴展方法中的靈活實現。 任何時候您需要“向接口添加方法”,而不是添加擴展方法。
是的,使用接口很麻煩,因為每當我們在類中添加方法時,我們都必須在接口中添加簽名。 另外,如果我們已經有一個包含一堆方法但沒有接口的類怎么辦? 我們必須為我們想要繼承的所有類手動創建接口。 最糟糕的是,如果子類要從多個接口繼承,我們必須在子類的Interfaces中實現所有方法。
通過遵循 Facade 設計模式,我們可以使用訪問器模擬從多個類繼承。 用 {get;set;} 將類聲明為需要繼承的類內部的屬性,所有公共屬性和方法都來自該類,並在子類的構造函數中實例化父類。
例如:
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();
}
}
}
有了這個結構,子類就可以訪問父類和母類的所有方法和屬性,模擬多重繼承,繼承父類的一個實例。 不太一樣,但很實用。
多重繼承是導致問題多於解決的問題之一。 在 C++ 中,它符合給你足夠的繩索來吊死自己的模式,但 Java 和 C# 選擇了不給你選擇的更安全的路線。 最大的問題是如果你繼承了多個類,這些類的方法具有相同的簽名,而被繼承者沒有實現。 應該選擇哪個類的方法? 還是不應該編譯? 通常還有另一種方法可以實現大多數不依賴於多重繼承的東西。
由於多重繼承(MI)的問題不時出現,我想添加一種方法來解決組合模式的一些問題。
我建立在IFirst
、 ISecond
、 First
、 Second
、 FirstAndSecond
方法的基礎上,正如問題中所提出的那樣。 我將示例代碼簡化為IFirst
,因為無論接口/ MI 基類的數量如何,模式都保持不變。
讓我們假設,MI First
和Second
都從同一個基類BaseClass
派生,僅使用來自BaseClass
的公共接口元素
這可以通過在First
和Second
實現中添加對BaseClass
的容器引用來表達:
class First : IFirst {
private BaseClass ContainerInstance;
First(BaseClass container) { ContainerInstance = container; }
public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); }
}
...
當引用來自BaseClass
的受保護接口元素時,或者當First
和Second
將是 MI 中的抽象類時,事情變得更加復雜,要求它們的子類實現一些抽象部分。
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
實現中的抽象位。
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 的實際實現足夠復雜並且訪問的私有/受保護方法的數量適中,那么這種模式可能有助於克服缺乏多重繼承的問題。
如果 X 繼承自 Y,則有兩個有點正交的效果:
盡管繼承提供了這兩個特性,但不難想象在沒有另一個的情況下可以使用其中一個。 我所知道的任何.net語言都沒有直接的方法來實現第一個而沒有第二個,盡管可以通過定義一個從不直接使用的基類來獲得這樣的功能,並且有一個或多個直接從它繼承的類而不添加任何東西新的(這樣的類可以共享它們的所有代碼,但不能相互替代)。 然而,任何符合 CLR 的語言都將允許使用提供接口的第二個特性(可替換性)而沒有第一個特性(成員重用)的接口。
我知道我知道即使它不允許等等,有時你實際上需要它,所以對於那些:
class a {}
class b : a {}
class c : b {}
就像我的情況一樣,我想做這個類 b:Form(是的 windows.forms)類 c:b {}
因為一半的功能是相同的,並且接口你必須全部重寫
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.