Когда использовать абстрактные классы вместо интерфейсов с методами расширения в C #?


70

«Абстрактный класс» и «интерфейс» являются похожими понятиями, причем интерфейс является более абстрактным из двух. Одним из отличительных факторов является то, что абстрактные классы предоставляют реализации методов для производных классов, когда это необходимо. Однако в C # этот дифференцирующий фактор был уменьшен недавним введением методов расширения, которые позволяют предоставлять реализации для методов интерфейса. Другим отличительным фактором является то, что класс может наследовать только один абстрактный класс (т. Е. Множественное наследование отсутствует), но он может реализовывать несколько интерфейсов. Это делает интерфейсы менее строгими и более гибкими. Итак, когда в C # мы должны использовать абстрактные классы вместо интерфейсов с методами расширения?

Примечательным примером модели метода interface + extension является LINQ, где функциональность запросов предоставляется для любого типа, который реализуется с IEnumerableпомощью множества методов расширения.


2
И мы можем использовать «Свойства» в интерфейсах также.
Гульшан

1
Похоже на C #, а не на общий вопрос о OOD. (Большинство других ОО-языков не имеют ни методов расширения, ни свойств.)
Péter Török


4
Методы расширения не совсем решают проблему, которую решают абстрактные классы, поэтому причина не отличается от той, что существует в Java или каком-либо другом подобном языке с единым наследованием.
Работа

3
Потребность в реализации методов абстрактного класса не была уменьшена методами расширения. Методы расширения не могут получить доступ к закрытым полям-членам, а также не могут быть объявлены виртуальными и могут быть переопределены в производных классах.
zooone9243

Ответы:


63

Когда вам нужна часть класса для реализации. Лучший пример, который я использовал, это шаблонный шаблон.

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

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

Методы расширений не обязательно удовлетворяют части уравнения, которая должна быть частью класса. Кроме того, методы расширения iirc не могут быть (по-видимому) чем-то иным, кроме общедоступных.

редактировать

Вопрос более интересный, чем я изначально считал. После дальнейшего изучения Джон Скит ответил на такой вопрос в SO в пользу использования интерфейсов + методов расширения. Кроме того, потенциальным недостатком является использование отражения против иерархии объектов, разработанных таким образом.

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

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


Удар меня к этому ..
Giorgi

1
Но то же самое можно сделать с помощью интерфейсов и методов расширения. Там методы, которые вы определяете в абстрактном базовом классе как, Do()будут определены как метод расширения. И методы, оставленные для определения производных классов как, DoThis()будут членами основного интерфейса. А с помощью интерфейсов вы можете поддерживать несколько реализаций / наследование. И посмотрите это
Гульшан

@Gulshan: потому что вещь может быть сделана более чем одним способом, это не делает выбранный способ недействительным. Нет никаких преимуществ в использовании интерфейсов. Сам абстрактный класс является интерфейсом. Вам не нужны интерфейсы для поддержки нескольких реализаций.
ThomasX

Также могут быть недостатки у интерфейса + модель расширения. Возьмите библиотеку Linq, например. Это здорово, но если вам нужно использовать его только один раз в этом кодовом файле, полная библиотека Linq будет доступна в IntelliSense на КАЖДОЙ IEnumerable в вашем кодовом файле. Это может значительно снизить производительность IDE.
KeithS

-1 потому что вы не продемонстрировали, каким образом абстрактные классы лучше подходят для Template Template, чем интерфейсы
Бенджамин Ходжсон,

25

Это все о том, что вы хотите моделировать:

Абстрактные классы работают по наследству . Будучи только специальные базовые классы, они моделируют некоторые есть- -relationship.

Например, собака является животным, таким образом , мы имеем

class Dog : Animal { ... }

Поскольку нет смысла создавать универсальное животное (как бы оно выглядело?), Мы создаем этот Animalбазовый класс, abstractно он все еще является базовым классом. И Dogкласс не имеет смысла, не будучи Animal.

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

Возьмите, например, IComparable- объект, который поддерживает сравнение с другим .

Функциональность класса не зависит от интерфейсов, которые он реализует, интерфейс просто обеспечивает общий способ доступа к функциональности. Мы могли бы еще Disposeв Graphicsили FileStreamбез их реализации IDisposable. Интерфейс просто относится методы вместе.

В принципе, можно добавлять и удалять интерфейсы, как расширения, не изменяя поведение класса, а просто расширяя доступ к нему. Вы не можете убрать базовый класс, поскольку объект станет бессмысленным!


Когда бы вы решили оставить базовый класс абстрактным?
Гульшан

1
@Gulshan: Если это произойдет - по какой-то причине - не имеет смысла создавать экземпляр базового класса. Вы можете «создать» чихуахуа или белую акулу, но вы не можете создать животное без дальнейшей специализации - таким образом, вы сделаете Animalабстракцию. Это позволяет вам писать abstractфункции, которые будут специализированы производными классами. Или, скорее, в терминах программирования - вероятно, нет смысла создавать a UserControl, но как a Button есть a UserControl, поэтому мы имеем такой же тип отношений класс / базовый класс.
Дарио

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

Нет правила, гласящего, что если грамматически и семантически значимо говорить «B - это A», то A должен быть абстрактным классом. Вы должны предпочитать интерфейсы, пока вы действительно не можете избежать класса. Совместное использование кода может быть выполнено с помощью композиции интерфейсов и т. Д. Это позволяет избежать рисования себя в углу со странными деревьями наследования.
Сара

3

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

Абстрактный класс - это несколько странный зверь между интерфейсом и реализацией. В абстрактных классах есть что-то, что вызывает у меня чувство паука. Я бы сказал, что никоим образом не следует «выставлять» реализацию интерфейса (или делать какие-либо привязки).

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

Я бы пошел за интерфейсами.


2
-1: я не уверен, сколько вам лет, но вы можете изучить шаблоны проектирования, в частности шаблонный метод. Шаблоны проектирования сделают вас лучшим программистом и положат конец вашему незнанию абстрактных классов.
Джим Г.

То же самое с ощущением покалывания. Классы, первая и самая главная особенность C # и Java, - это функция для создания типа и немедленного связывания его с одной реализацией.
Джесси Милликен

2

ИМХО, я думаю, что здесь происходит смешение понятий.

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

Оба вида наследования важны и полезны, и у каждого есть свои плюсы и минусы.

Давайте начнем с самого начала.

История с интерфейсами начинается давно с C ++. В середине 1980-х годов, когда был разработан C ++, идея интерфейсов как другого типа еще не была реализована (это придет вовремя).

C ++ имел только один тип наследования, тип наследования, используемый классами, называемый наследованием реализации.

Чтобы иметь возможность применять рекомендацию GoF Book: «Для программирования для интерфейса, а не для реализации», C ++ поддерживает абстрактные классы и множественное наследование реализации, чтобы иметь возможность делать то, что интерфейсы в C # способны (и полезны!) Делать ,

C # представляет новый тип типа, интерфейсы и новый тип наследования, наследование интерфейса, чтобы предложить как минимум два различных способа поддержки «Программирование для интерфейса, а не для реализации», с одним важным предупреждением: только C # поддерживает множественное наследование с наследованием интерфейса (а не с наследованием реализации).

Итак, архитектурные причины интерфейсов в C # - это рекомендация GoF.

Я надеюсь, что это может помочь.

С уважением, Гастон


1

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

Для новых разработок я предпочел бы использовать методы расширения на интерфейсах. Для иерархии типов методы расширения предоставляют средства для расширения поведения без необходимости открывать всю иерархию для модификации.

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


Нет, это не единственный безопасный способ. Есть еще более безопасный способ. Это методы расширения. Клиенты не будут заражены вообще. Вот почему я упомянул интерфейсы + методы расширения.
Гюльшан

@ Эд Джеймс Я думаю, что «менее мощный» = «более гибкий» Когда бы вы выбрали более мощный абстрактный класс вместо более гибкого интерфейса?
Гульшан

@Ed James В asp.mvc методы расширения использовались с самого начала. Что ты об этом думаешь?
Гюльшан

0

Я думаю, что в C # множественное наследование не поддерживается, и поэтому концепция интерфейса введена в C #.

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


Чтобы быть точным, это чисто абстрактные классы, которые называются «интерфейсами» в C #.
Йохан Буле

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.