Почему наследование использования вообще? [закрытый]

Я знаю, что вопрос был обсужден прежде, но это всегда кажется под предположением, что наследование по крайней мере иногда предпочтительно для состава. Я хотел бы бросить вызов тому предположению в надежде на получение некоторого понимания.

Мой вопрос - это: Так как можно выполнить что-нибудь с объектным составом, что Вы можете с классическим наследованием и так как классическим наследованием очень часто злоупотребляют [1] и так как объектный состав дает Вам, гибкость для изменения делегата возражает времени выполнения, почему Вас когда-нибудь использует классическое наследование?

Я могу отсортировать, понимают, почему Вы рекомендовали бы наследование на некоторых языках как Java и C++, которые не предлагают удобный синтаксис для делегации. На этих языках можно сохранить большой ввод при помощи наследования каждый раз, когда не явно неправильно сделать так. Но другие языки как Objective C и Ruby предлагают и классическое наследование и очень convienient синтаксис для делегации. Язык программирования Движения является единственным langage, который к моему знанию решил, что классическое наследование является большей проблемой, чем это стоит и поддерживает только делегацию к повторному использованию кода.

Другой способ указать мой вопрос является этим: Даже если Вы знаете, что классическое наследование весьма корректно для реализации определенной модели, той причины достаточно для использования его вместо состава?

[1] Многие люди используют классическое наследование для достижения полиморфизма вместо того, чтобы позволить их классам реализовать интерфейс. Целью наследования является повторное использование кода, не полиморфизм. Кроме того, некоторые люди используют наследование для моделирования, их интуитивное понимание "-" отношения, которые могут часто быть проблематичными.

Обновление

Я просто хочу разъяснить то, что я имею в виду точно, когда я говорю о наследовании:

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

Обновление 2

Я понимаю, что наследование является единственным способом достигнуть полиморфизма i C++. В этом случае очевидно, почему необходимо использовать его. Таким образом, мой вопрос ограничен языками, такими как Java или Ruby, которые предлагают отличные способы достигнуть полиморфизма (интерфейсы и утиный ввод, соответственно).

60
задан Community 23 May 2017 в 12:18
поделиться

11 ответов

Если вы делегируете все, что вы явно не переопределили, какому-то другому объекту, реализующему тот же интерфейс («базовый» объект), то вы в основном получаете наследование Greenspunned поверх композиции, но (на большинстве языков) с гораздо большим количеством подробностей и шаблонов. Цель использования композиции вместо наследования состоит в том, чтобы вы могли делегировать только то поведение, которое хотите делегировать.

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

12
ответ дан 24 November 2019 в 17:48
поделиться

Основная полезность классического наследования заключается в том, что у вас есть несколько связанных классов, которые будут иметь идентичную логику для методов, которые работают с переменными / свойствами экземпляра.

На самом деле есть 3 способа справиться с этим:

  1. Наследование.
  2. Дублировать код ( запах кода «Дублированный код»).
  3. Переместите логику в еще один класс (код пахнет «Ленивым классом», «Средним человеком», «Цепочками сообщений» и / или «Неуместной интимностью»).

Теперь может быть злоупотребление наследованием. Например, в Java есть классы InputStream и OutputStream . Их подклассы используются для чтения / записи файлов, сокетов, массивов, строк, а некоторые используются для обертывания других потоков ввода / вывода. Исходя из того, что они делают, это должны были быть интерфейсы, а не классы.

0
ответ дан 24 November 2019 в 17:48
поделиться

Вы писали:

[1] Многие используют классическую наследование для достижения полиморфизма вместо того, чтобы позволить своим занятиям реализовать интерфейс. Цель наследование - это повторное использование кода, а не полиморфизм. Кроме того, некоторые люди использовать наследование для моделирования своих интуитивное понимание «is-a» отношения, которые часто могут быть проблематично.

В большинстве языков грань между «реализацией интерфейса» и «наследованием класса от другого» очень тонкая. Фактически, в таких языках, как C ++, если вы выводите класс B из класса A, а класс A состоит только из чистых виртуальных методов, вы реализуете интерфейс.

Наследование касается повторного использования интерфейса , а не повторного использования реализации . Речь идет , а не о повторном использовании кода, как вы писали выше.

Наследование, как вы правильно указываете, предназначено для моделирования отношений IS-A (тот факт, что многие люди ошибаются, не имеет ничего общего с наследованием как таковым). Вы также можете сказать "ПОВЕДЕНИЕ-КАК-А". Однако то, что что-то имеет отношение IS-A к чему-то еще, не означает, что он использует тот же (или даже похожий) код для выполнения этих отношений.

Сравните этот пример C ++, в котором реализованы различные способы вывода данных; два класса используют (публичное) наследование, чтобы к ним можно было получить доступ полиморфно:

struct Output {
  virtual bool readyToWrite() const = 0;
  virtual void write(const char *data, size_t len) = 0;
};

struct NetworkOutput : public Output {
  NetworkOutput(const char *host, unsigned short port);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

struct FileOutput : public Output {
  FileOutput(const char *fileName);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

Теперь представьте, что это была Java. «Выход» был не структурой, а «интерфейсом». Его можно было бы назвать «записываемым». Вместо «общедоступный вывод» вы бы сказали «реализует возможность записи». В чем разница в дизайне?

Нет.

0
ответ дан 24 November 2019 в 17:48
поделиться

Я всегда дважды думаю, прежде чем использовать наследование, так как это может быстро стать сложным. При этом есть много случаев, когда он просто создает самый элегантный код.

4
ответ дан 24 November 2019 в 17:48
поделиться

Когда вы спросили:

Даже если вы знаете, что классическое наследование не является неправильным для реализации определенной модели, достаточно ли этой причины, чтобы использовать его вместо композиции?

Ответ отрицательный. Если модель некорректна (с использованием наследования), то использовать ее, несмотря ни на что, неправильно.

Вот некоторые проблемы с наследованием, которые я видел:

  1. Всегда необходимо тестировать тип времени выполнения указателей производных классов, чтобы увидеть, можно ли их привести вверх (или вниз тоже).
  2. Это «тестирование» может быть выполнено разными способами. У вас может быть какой-то виртуальный метод, который возвращает идентификатор класса. Или в противном случае вам, возможно, придется реализовать RTTI (идентификация типа времени выполнения) (по крайней мере, в c / c ++), что может дать вам снижение производительности.
  3. типы классов, которые не могут быть «преобразованы», могут быть потенциально проблематичными.
  4. Есть много способов привести тип вашего класса вверх и вниз по дереву наследования.
0
ответ дан 24 November 2019 в 17:48
поделиться

А как насчет шаблона метода шаблона? Допустим, у вас есть базовый класс с множеством точек для настраиваемых политик, но шаблон стратегии не имеет смысла по крайней мере по одной из следующих причин:

  1. Настраиваемые политики должны знать о базовый класс, может использоваться только с базовым классом и не имеет смысла ни в каком другом контексте. Вместо этого использовать стратегию можно, но это PITA, потому что и базовый класс, и класс политики должны иметь ссылки друг на друга.

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

2
ответ дан 24 November 2019 в 17:48
поделиться

Наследование предпочтительнее, если:

  1. Вам нужно предоставить весь API расширяемого класса (с делегированием вам нужно будет написать множество методов делегирования) и ваш язык не предлагает простого способа сказать «делегировать все неизвестные методы».
  2. Вам необходимо получить доступ к защищенным полям / методам для языков, в которых нет понятия «друзья».
  3. Преимущества делегирования несколько уменьшаются, если ваш язык допускает множественное наследование.
  4. Обычно делегирование не требуется, если ваш язык позволяет динамически наследовать от класса или даже от экземпляра во время выполнения. Вам это вообще не нужно, если вы можете контролировать, какие методы доступны (и как они отображаются) одновременно.

Мой вывод: делегирование - это обходной путь для ошибки в языке программирования.

6
ответ дан 24 November 2019 в 17:48
поделиться

Один из наиболее полезных способов использования наследования, который я вижу, - это объекты графического интерфейса.

0
ответ дан 24 November 2019 в 17:48
поделиться

Основная причина использования наследования не как форма композиции - это так, чтобы вы могли получить полиморфное поведение. Если вам не нужен полиморфизм, вам, вероятно, не следует использовать наследование, по крайней мере, в C ++.

16
ответ дан 24 November 2019 в 17:48
поделиться

Интерфейсы определяют только то, что объект может делать, но не как. Проще говоря, интерфейсы - это просто контракты. Все объекты, реализующие интерфейс, должны будут определить свою собственную реализацию контракта. В практическом мире это дает вам разделение забот. Представьте, что вы пишете приложение, которому нужно иметь дело с различными объектами, вы не знаете их заранее, но вам все равно нужно иметь с ними дело, единственное, что вы знаете, это то, какие различные вещи должны делать эти объекты. Поэтому вы определяете интерфейс и упоминаете все операции в контракте. Теперь вы напишете свое приложение на основе этого интерфейса. Позже тот, кто захочет использовать ваш код или приложение, должен будет реализовать интерфейс на объекте, чтобы заставить его работать с вашей системой. Ваш интерфейс заставит их объект определить, как должна выполняться каждая операция, определенная в контракте. Таким образом, любой может написать объекты, реализующие ваш интерфейс, чтобы они безупречно адаптировались к вашей системе, и все, что вы знаете, это то, что должно быть сделано, а объект должен определить, как это сделать.

В реальной разработке эта практика обычно известна как Программирование к интерфейсу, а не реализации.

Интерфейсы - это просто контракты или подписи, и они ничего не знают ничего о реализации.

Кодирование по интерфейсу означает, что клиентский код всегда содержит объект интерфейса, который поставляется фабрикой. Любой экземпляр, возвращаемый фабрикой, будет иметь тип Interface, который должен быть реализован любым классом-кандидатом фабрики. Таким образом, клиентская программа не беспокоится о реализации, а сигнатура интерфейса определяет, какие операции могут быть выполнены. Это может быть использовано для изменения поведения программы во время выполнения. Это также помогает писать гораздо лучшие программы с точки зрения сопровождения.

Вот вам базовый пример.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

alt text http://ruchitsurati.net/myfiles/interface.png

3
ответ дан 24 November 2019 в 17:48
поделиться

Цель наследования - повторное использование кода, а не полиморфизм.

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

При правильном использовании наследование не касается повторного использования существующего кода. Скорее, речь идет об использовании существующим кодом . То есть, если у вас есть существующий код, который может работать с существующим базовым классом, когда вы производите новый класс из этого существующего базового класса, этот другой код теперь также может автоматически работать с вашим новым производным классом.

Можно использовать наследование для повторного использования кода, но когда / если вы это сделаете, обычно это должно быть частное наследование, а не публичное наследование.Если язык, который вы используете, хорошо поддерживает делегирование, велики шансы, что у вас редко бывает много причин использовать частное наследование. OTOH, частное наследование, действительно поддерживает некоторые вещи, которых делегирование (обычно) не поддерживает. В частности, даже несмотря на то, что в данном случае полиморфизм является явно второстепенным, он может по-прежнему вызывать беспокойство, т. Е. С частным наследованием вы можете начать с базового класса, который почти что вы хотите, и (при условии, что это позволяет) переопределить те части, которые не совсем правильны.

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

49
ответ дан 24 November 2019 в 17:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: