Đa kế thừa trong C #


212

Vì nhiều kế thừa là xấu (nó làm cho nguồn phức tạp hơn), C # không cung cấp mẫu trực tiếp như vậy. Nhưng đôi khi nó sẽ hữu ích để có khả năng này.

Chẳng hạn, tôi có thể triển khai mẫu thừa kế bị thiếu bằng cách sử dụng các giao diện và ba lớp như thế:

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

Mỗi lần tôi thêm một phương thức vào một trong các giao diện tôi cũng cần thay đổi lớp FirstAndSecond .

Có cách nào để tiêm nhiều lớp hiện có vào một lớp mới giống như trong C ++ không?

Có lẽ có một giải pháp sử dụng một số loại tạo mã?

Hoặc nó có thể trông như thế này (cú pháp c # tưởng tượng):

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

Vì vậy, sẽ không cần phải cập nhật lớp FirstAndSecond khi tôi sửa đổi một trong các giao diện.


BIÊN TẬP

Có lẽ sẽ tốt hơn nếu xem xét một ví dụ thực tế:

Bạn có một lớp hiện có (ví dụ: máy khách TCP dựa trên văn bản dựa trên ITextTcpClient) mà bạn đã sử dụng tại các vị trí khác nhau trong dự án của mình. Bây giờ bạn cảm thấy cần phải tạo một thành phần của lớp để dễ dàng truy cập cho các nhà phát triển biểu mẫu windows.

Theo tôi biết bạn hiện có hai cách để làm điều này:

  1. Viết một lớp mới được kế thừa từ các thành phần và thực hiện giao diện của lớp TextTcpClient bằng cách sử dụng một thể hiện của chính lớp đó như được hiển thị với FirstAndSecond.

  2. Viết một lớp mới kế thừa từ TextTcpClient và bằng cách nào đó thực hiện IComponent (chưa thực sự thử điều này).

Trong cả hai trường hợp, bạn cần thực hiện công việc theo phương thức chứ không phải mỗi lớp. Vì bạn biết rằng chúng ta sẽ cần tất cả các phương thức của TextTcpClient và Thành phần, đây sẽ là giải pháp đơn giản nhất để kết hợp cả hai thành một lớp.

Để tránh xung đột, điều này có thể được thực hiện bằng cách tạo mã trong đó kết quả có thể được thay đổi sau đó nhưng gõ nó bằng tay là một nỗi đau thuần túy ở mông.


Trong phạm vi mà điều này không chỉ đơn giản là thừa kế nhiều lần ngụy trang, làm thế nào nó ít phức tạp hơn?
harpo

Suy nghĩ về các phương thức mở rộng mới trong 3.5 và cách thức hoạt động (tạo cuộc gọi thành viên tĩnh), đây có thể là một trong những tiến hóa ngôn ngữ .NET tiếp theo.
Larry

Đôi khi tôi tự hỏi tại sao mọi người không chỉ làm ... lớp A: lớp B: lớp C?
Chibueze Opata

@NazarMerza: Liên kết đã thay đổi. Bây giờ: Vấn đề với nhiều kế thừa .
Craig McQueen

9
Đừng để tuyên truyền đánh lừa bạn. Ví dụ của bạn cho thấy rằng nhiều kế thừa là hữu ích và giao diện chỉ là một cách giải quyết cho việc thiếu nó
Kemal Erdogan

Câu trả lời:


125

Vì nhiều kế thừa là xấu (nó làm cho nguồn phức tạp hơn), C # không cung cấp mẫu trực tiếp như vậy. Nhưng đôi khi nó sẽ hữu ích để có khả năng này.

C # và .net CLR chưa triển khai MI vì họ chưa kết luận cách thức hoạt động giữa C #, VB.net và các ngôn ngữ khác, không phải vì "nó sẽ khiến nguồn phức tạp hơn"

MI là một khái niệm hữu ích, các câu hỏi chưa được trả lời là những câu hỏi như: - "Bạn làm gì khi bạn có nhiều lớp cơ sở chung trong các siêu lớp khác nhau?

Perl là ngôn ngữ duy nhất tôi từng làm việc với nơi MI hoạt động và hoạt động tốt. .Net cũng có thể giới thiệu nó một ngày nhưng chưa, CLR đã hỗ trợ MI nhưng như tôi đã nói, không có cấu trúc ngôn ngữ nào cho nó ngoài điều đó.

Cho đến lúc đó bạn bị mắc kẹt với các đối tượng Proxy và nhiều Giao diện thay thế :(


39
CLR không hỗ trợ kế thừa nhiều triển khai, chỉ kế thừa nhiều giao diện (cũng được hỗ trợ trong C #).
Jordão

4
@ Jordão: Để hoàn thiện vì lợi ích: các trình biên dịch có thể tạo MI cho các kiểu của chúng trong CLR. Nó có cảnh báo, ví dụ như nó không tuân thủ CLS. Để biết thêm thông tin, xem này (2004) Điều blogs.msdn.com/b/csharpfaq/archive/2004/03/07/...
dvdvorle

2
@MrHappy: Bài viết rất thú vị. Tôi thực sự đã điều tra một số cách thành phần Trait cho C #, hãy xem.
Jordão

10
@MandeepJanjua Tôi không yêu cầu bất kỳ điều gì như vậy, tôi nói 'cũng có thể giới thiệu nó' Sự thật vẫn là CLR tiêu chuẩn ECMA cung cấp cho máy móc IL để thừa kế, chỉ là không có gì sử dụng nó hoàn toàn.
IanNorton

4
Nhiều kế thừa của FYI không phải là xấu và không làm cho mã trở nên phức tạp. Chỉ cần nghĩ rằng tôi sẽ đề cập đến nó.
Dmitri Nesteruk

214

Hãy xem xét chỉ sử dụng thành phần thay vì cố gắng mô phỏng Nhiều kế thừa. Bạn có thể sử dụng Giao diện để xác định các lớp tạo nên thành phần, ví dụ: ISteerablengụ ý một thuộc tính của loại SteeringWheel, IBrakablengụ ý một thuộc tính của loại BrakePedal, v.v.

Khi bạn đã thực hiện điều đó, bạn có thể sử dụng tính năng Phương thức tiện ích mở rộng được thêm vào C # 3.0 để đơn giản hóa hơn nữa các phương thức gọi trên các thuộc tính ngụ ý đó, ví dụ:

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

13
Đó là điểm quan trọng - một ý tưởng như thế này sẽ dễ dàng sáng tác.
Jon Skeet

9
Có, nhưng có những trường hợp sử dụng mà bạn thực sự cần các phương thức như một phần của đối tượng chính
David Pierre

9
Thật không may, dữ liệu biến thành viên không thể truy cập được trong các phương thức mở rộng, do đó bạn phải phơi bày chúng dưới dạng công khai nội bộ hoặc (xấu), mặc dù tôi nghĩ rằng sáng tác theo hợp đồng là cách tốt nhất để giải quyết nhiều kế thừa.
cfeduke

4
Câu trả lời tuyệt vời! Súc tích, dễ hiểu, minh họa rất hữu ích. Cảm ơn bạn!
AJ.

3
Chúng tôi có thể muốn kiểm tra xem myCarđã lái xong chưa trước khi gọi Stop. Nó có thể lăn qua nếu Stopáp dụng trong khi myCarở tốc độ quá cao. : D
Devraj Gadhavi

16

Tôi đã tạo một trình biên dịch C # cho phép loại điều này:

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> { }

Bạn có thể chạy trình biên dịch hậu kỳ dưới dạng sự kiện hậu xây dựng Visual Studio:

C: \ some_path \ nroles-v0.1.0-bin \ nutate.exe "$ (TargetPath)"

Trong cùng một hội đồng bạn sử dụng nó như thế này:

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

Trong một hội đồng khác, bạn sử dụng nó như thế này:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();

6

Bạn có thể có một lớp cơ sở trừu tượng thực hiện cả IFirst và ISecond, và sau đó kế thừa từ cơ sở đó.


Đây có lẽ là giải pháp tốt nhất, nhưng không nhất thiết là ý tưởng tốt nhất: p
leppie

1
bạn vẫn sẽ phải chỉnh sửa lớp trừu tượng khi bạn thêm các phương thức vào các liên kết chứ?
Rik

Rik: bạn lười đến mức nào, khi bạn chỉ phải làm điều này một lần?
leppie

3
@leppie - "Mỗi lần tôi thêm một phương thức vào một trong các giao diện tôi cũng cần thay đổi lớp FirstAndSecond." Phần này của câu hỏi ban đầu không được giải quyết bằng giải pháp này, phải không?
Rik

2
Bạn sẽ phải chỉnh sửa lớp trừu tượng, nhưng bạn KHÔNG phải chỉnh sửa bất kỳ lớp nào khác phụ thuộc vào nó. Cái xô dừng ở đó, thay vì tiếp tục xếp tầng cho toàn bộ bộ sưu tập các lớp.
Joel Coehoorn

3

MI KHÔNG tệ, mọi người đã (nghiêm túc) sử dụng nó YÊU THÍCH nó và nó KHÔNG làm phức tạp mã! Ít nhất là không còn nữa so với các cấu trúc khác có thể làm phức tạp mã. Mã xấu là mã xấu bất kể có trong MI hay không.

Dù sao, tôi đã có một giải pháp nhỏ tuyệt vời cho Đa kế thừa mà tôi muốn chia sẻ, đó là tại; http://ra-ajax.org/lsp-liskov-substlation-principl-to-be-or-not-to-be.blog hoặc bạn có thể theo liên kết trong sig của tôi ... :)


Có thể có nhiều kế thừa và trong khi có các chương trình phát sóng và truyền phát được bảo tồn danh tính? Các giải pháp tôi biết về các vấn đề của nhiều thừa kế xoay quanh việc có các phôi không bảo toàn danh tính (nếu myFoothuộc loại Foo, thừa hưởng từ MooGoo, cả hai đều thừa kế từ Boođó (Boo)(Moo)myFoo, và (Boo)(Goo)myFoosẽ không tương đương). Bạn có biết về bất kỳ phương pháp bảo tồn danh tính?
supercat

1
Bạn có thể sử dụng web.archive.org hoặc tương tự để theo liên kết, nhưng hóa ra đó là một cuộc thảo luận chi tiết hơn về chính xác giải pháp được cung cấp trong câu hỏi ban đầu ở đây.
MikeBeaton

2

Trong triển khai của riêng tôi, tôi thấy rằng việc sử dụng các lớp / giao diện cho MI, mặc dù "hình thức tốt", có xu hướng trở nên phức tạp hơn vì bạn cần thiết lập tất cả nhiều kế thừa đó chỉ cho một vài lệnh gọi chức năng cần thiết và trong trường hợp của tôi, cần phải được thực hiện theo nghĩa đen hàng chục lần dư thừa.

Thay vào đó, dễ dàng hơn để tạo ra các "hàm gọi các hàm gọi hàm" trong các loại mô-đun khác nhau như là một loại thay thế OOP. Giải pháp tôi đang thực hiện là "hệ thống chính tả" cho một game nhập vai, trong đó các hiệu ứng cần phải thực hiện chức năng kết hợp và kết hợp rất nhiều để tạo ra nhiều loại phép thuật cực kỳ mà không cần viết lại mã, giống như ví dụ cho thấy.

Hầu hết các hàm bây giờ có thể là tĩnh vì tôi không nhất thiết cần một thể hiện cho logic chính tả, trong khi kế thừa lớp thậm chí không thể sử dụng các từ khóa ảo hoặc trừu tượng trong khi tĩnh. Các giao diện không thể sử dụng chúng cả.

Mã hóa dường như nhanh hơn và sạch hơn theo cách này IMO. Nếu bạn chỉ đang thực hiện các chức năng và không cần các thuộc tính được kế thừa , hãy sử dụng các chức năng.


2

Với C # 8 bây giờ bạn thực tế có nhiều kế thừa thông qua triển khai mặc định của các thành viên giao diện:

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
}

4
Có, nhưng lưu ý rằng, ở trên, bạn sẽ không thể làm được new ConsoleLogger().Log(someEception)- đơn giản là nó sẽ không hoạt động, bạn phải chuyển đối tượng của mình thành một ILoggerphương thức giao diện mặc định. Vì vậy, tính hữu dụng của nó có phần hạn chế.
Dmitri Nesteruk

1

Nếu bạn có thể sống với hạn chế rằng các phương thức IFirst và ISecond chỉ phải tương tác với hợp đồng của IFirst và ISecond (như trong ví dụ của bạn) ... bạn có thể làm những gì bạn yêu cầu với các phương thức mở rộng. Trong thực tế, điều này hiếm khi xảy ra.

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

Vì vậy, ý tưởng cơ bản là bạn xác định việc triển khai được yêu cầu trong các giao diện ... công cụ cần thiết này sẽ hỗ trợ việc triển khai linh hoạt trong các phương thức mở rộng. Bất cứ lúc nào bạn cần "thêm phương thức vào giao diện" thay vì bạn thêm phương thức mở rộng.


1

Có sử dụng Giao diện là một rắc rối vì bất cứ khi nào chúng tôi thêm một phương thức trong lớp, chúng tôi phải thêm chữ ký trong giao diện. Ngoài ra, nếu chúng ta đã có một lớp với một loạt các phương thức nhưng không có Giao diện cho nó thì sao? chúng ta phải tự tạo Giao diện cho tất cả các lớp mà chúng ta muốn kế thừa từ đó. Và điều tồi tệ nhất là, chúng ta phải thực hiện tất cả các phương thức trong Giao diện trong lớp con nếu lớp con được kế thừa từ nhiều giao diện.

Bằng cách làm theo mẫu thiết kế mặt tiền, chúng tôi có thể mô phỏng kế thừa từ nhiều lớp sử dụng accessors . Khai báo các lớp dưới dạng các thuộc tính với {get; set;} bên trong lớp cần kế thừa và tất cả các thuộc tính và phương thức công khai là từ lớp đó và trong hàm tạo của lớp con khởi tạo các lớp cha.

Ví dụ:

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


 }

với lớp cấu trúc này, Con sẽ có quyền truy cập vào tất cả các phương thức và thuộc tính của Lớp cha và Mẹ, mô phỏng nhiều thừa kế, kế thừa một thể hiện của các lớp cha. Không hoàn toàn giống nhau nhưng nó là thực tế.


2
Tôi không đồng ý với đoạn đầu tiên. Bạn chỉ thêm chữ ký của các phương thức bạn muốn trong lớp MERYI vào giao diện. Nhưng bạn có thể thêm bao nhiêu phương thức bổ sung vào bất kỳ lớp nào bạn muốn. Ngoài ra, có một nhấp chuột phải, trích xuất giao diện làm cho công việc trích xuất các giao diện đơn giản. Cuối cùng, ví dụ của bạn không phải là bất kỳ cách thừa kế nào (nhiều hay nói cách khác), tuy nhiên nó là một ví dụ tuyệt vời về bố cục. Than ôi, nó sẽ có nhiều công đức hơn nếu bạn đã sử dụng các giao diện để chứng minh DI / IOC bằng cách sử dụng hàm xây dựng / thuộc tính. Trong khi tôi không bỏ phiếu, tôi không nghĩ đó là một câu trả lời hay.
Francis Rodgers

1
Nhìn lại chủ đề này một năm sau, tôi đồng ý bạn có thể thêm bao nhiêu phương thức bạn muốn trong lớp mà không cần thêm chữ ký trong giao diện, tuy nhiên, điều này sẽ làm cho giao diện không hoàn chỉnh. Ngoài ra, tôi không thể tìm thấy giao diện nhấp chuột phải - trích xuất trong IDE của mình, có lẽ tôi đã thiếu một cái gì đó. Nhưng, mối quan tâm lớn hơn của tôi là, khi bạn chỉ định một chữ ký trong giao diện, các lớp kế thừa phải thực hiện chữ ký đó. Tôi nghĩ rằng đây là công việc kép và có thể dẫn đến sao chép mã.
Yogi

Giao diện EXTRACT: nhấp chuột phải vào chữ ký lớp, sau đó giải nén giao diện ... trong cùng một quy trình VS2015 ngoại trừ bạn phải nhấp chuột phải, sau đó chọn Quick Actions and Refactorings...điều này là phải biết, nó sẽ giúp bạn tiết kiệm rất nhiều thời gian
Chef_Code

1

Tất cả chúng ta dường như đang đi xuống con đường giao diện với điều này, nhưng khả năng khác rõ ràng, ở đây, là làm những gì OOP phải làm và xây dựng cây thừa kế của bạn ... (đây không phải là thiết kế lớp học trong khoảng?)

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; }
}

Cấu trúc này cung cấp các khối mã có thể tái sử dụng và, chắc chắn, là cách viết mã OOP?

Nếu cách tiếp cận cụ thể này không hoàn toàn phù hợp với dự luật, chúng ta chỉ cần tạo các lớp mới dựa trên các đối tượng được yêu cầu ...

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; }
}

0

Đa kế thừa là một trong những điều thường gây ra nhiều vấn đề hơn nó giải quyết. Trong C ++, nó phù hợp với mô hình cung cấp cho bạn đủ dây để tự treo, nhưng Java và C # đã chọn đi theo con đường an toàn hơn là không cung cấp cho bạn tùy chọn. Vấn đề lớn nhất là phải làm gì nếu bạn kế thừa nhiều lớp có một phương thức có cùng chữ ký mà người thừa kế không thực hiện. Nên chọn phương thức nào của lớp? Hay là không nên biên dịch? Nhìn chung có một cách khác để thực hiện hầu hết những thứ không dựa vào nhiều kế thừa.


8
Xin đừng phán xét MI bằng C ++, giống như đánh giá OOP bằng PHP hoặc ô tô của Pintos. Vấn đề đó rất dễ giải quyết: ở Eiffel, khi bạn kế thừa từ một lớp, bạn cũng phải xác định phương thức nào bạn muốn kế thừa và bạn có thể đổi tên chúng. Không có sự mơ hồ ở đó và cũng không có bất ngờ.
Jörg W Mittag

2
@mP: không, Eiffel cung cấp nhiều kế thừa thực hiện. Đổi tên không có nghĩa là mất chuỗi thừa kế, cũng sẽ không làm mất khả năng truyền của các lớp.
Abel

0

Nếu X thừa hưởng từ Y, điều đó có hai hiệu ứng trực giao:

  1. Y sẽ cung cấp chức năng mặc định cho X, vì vậy mã cho X chỉ phải bao gồm những thứ khác với Y.
  2. Hầu như bất cứ nơi nào Y sẽ được mong đợi, X có thể được sử dụng thay thế.

Mặc dù tính kế thừa cung cấp cho cả hai tính năng, nhưng không khó để tưởng tượng các trường hợp có thể được sử dụng mà không có tính năng khác. Không có ngôn ngữ .net nào tôi biết có cách trực tiếp triển khai lớp thứ nhất mà không có ngôn ngữ thứ hai, mặc dù người ta có thể có được chức năng đó bằng cách định nghĩa một lớp cơ sở không bao giờ được sử dụng trực tiếp và có một hoặc nhiều lớp kế thừa trực tiếp từ nó mà không cần thêm bất cứ thứ gì mới (các lớp như vậy có thể chia sẻ tất cả mã của chúng, nhưng sẽ không thể thay thế cho nhau). Tuy nhiên, bất kỳ ngôn ngữ tuân thủ CLR nào cũng sẽ cho phép sử dụng các giao diện cung cấp tính năng thứ hai của giao diện (khả năng thay thế) mà không cần ngôn ngữ thứ nhất (tái sử dụng thành viên).


0

tôi biết tôi biết mặc dù nó không được phép và vân vân, đôi khi bạn thực sự cần nó cho những người đó:

class a {}
class b : a {}
class c : b {}

như trong trường hợp của tôi, tôi muốn làm lớp này b: Form (yep the windows.forms) class c: b {}

vì một nửa chức năng giống hệt nhau và với giao diện u phải viết lại tất cả


1
Ví dụ của bạn không mô tả nhiều kế thừa, vậy bạn đang cố gắng giải quyết vấn đề gì? Một ví dụ đa thừa kế thực sự sẽ hiển thị class a : b, c(thực hiện bất kỳ lỗ hổng hợp đồng cần thiết nào). Có lẽ ví dụ của bạn chỉ đơn giản hơn?
M.Babcock

0

Do câu hỏi về tính kế thừa (MI) thỉnh thoảng xuất hiện, tôi muốn thêm một cách tiếp cận giải quyết một số vấn đề với mẫu bố cục.

Tôi xây dựng dựa trên các IFirst, ISecond, First, Second, FirstAndSecondcách tiếp cận, vì nó đã được trình bày trong câu hỏi. Tôi giảm mã mẫu xuống IFirst, vì mẫu vẫn giữ nguyên bất kể số lượng giao diện / lớp cơ sở MI.

Giả sử rằng với MI FirstSecondcả hai đều xuất phát từ cùng một lớp cơ sở BaseClass, chỉ sử dụng các thành phần giao diện công cộng từBaseClass

Điều này có thể được thể hiện, bằng cách thêm một tham chiếu container BaseClassvào FirstSecondthực hiện:

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

Mọi thứ trở nên phức tạp hơn, khi các yếu tố giao diện được bảo vệ khỏi BaseClassđược tham chiếu hoặc khi FirstSecondsẽ là các lớp trừu tượng trong MI, yêu cầu các lớp con của chúng thực hiện một số phần trừu tượng.

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 # cho phép các lớp lồng nhau truy cập các phần tử được bảo vệ / riêng tư của các lớp chứa của chúng, vì vậy điều này có thể được sử dụng để liên kết các bit trừu tượng từ việc Firsttriển khai.

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"); }
}

Có khá nhiều nồi hơi liên quan, nhưng nếu việc triển khai thực tế FirstMethod và SecondMethod đủ phức tạp và số lượng phương thức riêng tư / được bảo vệ truy cập là vừa phải, thì mẫu này có thể giúp khắc phục việc thiếu nhiều kế thừa.


0

Đây là câu trả lời của Lawrence Wenham, nhưng tùy thuộc vào trường hợp sử dụng của bạn, nó có thể hoặc không phải là một cải tiến - bạn không cần setters.

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; }
}

Bây giờ bất kỳ đối tượng nào biết cách để một người có thể thực hiện IGetPerson và nó sẽ tự động có các phương thức GetAgeViaPerson () và GetNameViaPerson (). Từ thời điểm này, về cơ bản, tất cả mã Người đi vào IGetPerson, không phải vào IPerson, ngoại trừ ngà voi mới, phải đi vào cả hai. Và khi sử dụng mã như vậy, bạn không phải lo lắng về việc liệu đối tượng IGetPerson của bạn có thực sự là một IPerson hay không.


0

Bây giờ đây là partialcác lớp máng có thể , mỗi lớp có thể tự kế thừa một lớp, làm cho đối tượng cuối cùng kế thừa tất cả các lớp cơ sở. Bạn có thể tìm hiểu thêm về nó ở đây .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.