Множественное наследование в C # - PullRequest
202 голосов
/ 07 октября 2008

Так как множественное наследование является плохим (это усложняет исходный код), 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 при изменении одного из интерфейсов.


EDIT

Может быть, было бы лучше рассмотреть практический пример:

У вас есть существующий класс (например, текстовый TCP-клиент на основе ITextTcpClient), который вы уже используете в разных местах внутри вашего проекта. Теперь вы чувствуете необходимость создания компонента вашего класса, который был бы легко доступен для разработчиков форм Windows.

Насколько я знаю, у вас есть два способа сделать это:

  1. Напишите новый класс, который унаследован от компонентов и реализует интерфейс класса TextTcpClient, используя экземпляр самого класса, как показано в FirstAndSecond.

  2. Напишите новый класс, который наследуется от TextTcpClient и каким-то образом реализует IComponent (еще не пробовал этого).

В обоих случаях вам нужно выполнять работу для каждого метода, а не для класса. Поскольку вы знаете, что нам понадобятся все методы TextTcpClient и Component, было бы самым простым решением просто объединить эти два в один класс.

Чтобы избежать конфликтов, это может быть сделано с помощью генерации кода, где результат может быть изменен впоследствии, но ввод этого вручную - чистая боль в заднице.

Ответы [ 16 ]

0 голосов
/ 04 апреля 2017

Мы все, похоже, идем по пути интерфейса с этим, но очевидная другая возможность здесь - это сделать то, что должен делать ООП, и построить ваше дерево наследования ... (не это что за классный дизайн?)

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

Эта структура предоставляет многократно используемые блоки кода и, конечно, как должен быть написан код ООП?

Если этот конкретный подход не совсем соответствует требованиям, мы просто создаем новые классы на основе требуемых объектов ...

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 голосов
/ 20 февраля 2017

Это соответствует ответу Лоуренса Уэнама, но в зависимости от вашего варианта использования это может быть или не быть улучшением - вам не нужны сеттеры.

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.

0 голосов
/ 12 октября 2015

Поскольку время от времени возникает вопрос множественного наследования (MI), я хотел бы добавить подход, который решает некоторые проблемы с шаблоном композиции.

Я опираюсь на подход IFirst, ISecond, First, Second, FirstAndSecond, как это было представлено в вопросе. Я уменьшил пример кода до IFirst, поскольку шаблон остается неизменным независимо от количества интерфейсов / базовых классов MI.

Предположим, что с MI First и Second оба будут производными от одного базового класса BaseClass, используя только открытые элементы интерфейса из BaseClass

Это можно выразить, добавив ссылку на контейнер к BaseClass в реализации First и Second:

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 достаточно сложна, а количество доступных закрытых / защищенных методов умеренное, то этот шаблон может помочь преодолеть отсутствие множественного наследования.

0 голосов
/ 28 марта 2012

я знаю, я знаю даже если это не разрешено и так далее, иногда вам действительно нужно это для тех:

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

как в моем случае я хотел сделать это класс b: форма (да, windows.forms) класс c: b {}

потому что половина функции была идентична и с интерфейсом вы должны переписать их все

0 голосов
/ 24 августа 2011

Если X наследует от Y, это имеет два несколько ортогональных эффекта:

  1. Y обеспечит функциональность по умолчанию для X, поэтому код для X должен включать только то, что отличается от Y.
  2. Почти в любом месте, где можно было бы ожидать Y, вместо него можно использовать X.

Хотя наследование обеспечивает обе функции, нетрудно представить обстоятельства, при которых одна из них может быть полезной без другой. Ни один из известных мне языков .net не имеет прямого способа реализации первого без второго, хотя такую ​​функциональность можно получить, определив базовый класс, который никогда не используется напрямую, и имея один или несколько классов, которые наследуются напрямую от него, не добавляя ничего new (такие классы могут совместно использовать весь свой код, но не могут заменять друг друга). Однако любой CLR-совместимый язык позволит использовать интерфейсы, которые предоставляют вторую функцию интерфейсов (заменяемость) без первой (повторное использование элемента).

0 голосов
/ 07 октября 2008

Множественное наследование - это одна из тех вещей, которая обычно вызывает больше проблем, чем решает. В C ++ это соответствует схеме предоставления вам достаточного количества веревки, чтобы повеситься, но Java и C # решили пойти по более безопасному пути, не давая вам выбора. Самая большая проблема заключается в том, что делать, если вы наследуете несколько классов, у которых есть метод с той же сигнатурой, которую наследник не реализует. Какой класс метод должен выбрать? Или это не должно компилироваться? Как правило, существует другой способ реализации большинства вещей, который не основан на множественном наследовании.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...