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


70

"Абстрактний клас" та "інтерфейс" є подібними поняттями, причому інтерфейс є більш абстрактним з двох. Одним з відмінних факторів є те, що абстрактні класи забезпечують реалізацію методів для похідних класів, коли це необхідно. Однак у C # цей диференціюючий коефіцієнт був зменшений нещодавним введенням методів розширення, які дозволяють забезпечити реалізацію методів інтерфейсу. Ще один диференціюючий фактор полягає в тому, що клас може успадковувати лише один абстрактний клас (тобто багаторазового успадкування немає), але він може реалізовувати декілька інтерфейсів. Це робить інтерфейси менш обмежуючими та більш гнучкими. Отже, на C #, коли ми повинні використовувати абстрактні класи замість інтерфейсів із методами розширення?

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


2
І ми також можемо використовувати "Властивості" в інтерфейсах.
Гульшан

1
Звучить як специфічний C #, а не загальне питання щодо OOD. (Більшість інших мов OO не мають методів розширення та властивостей.)
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 на користь використання інтерфейсів + методів розширення. Також потенційним недоліком є використання відображення проти ієрархії об'єктів, розробленої таким чином.

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

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


Побий мене ..
Джорджі

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

@Gulshan: Оскільки річ можна зробити більш ніж одним способом, це не робить обраний шлях недійсним. Немає переваги у використанні інтерфейсів. Сам абстрактний клас - це інтерфейс. Вам не потрібні інтерфейси для підтримки декількох реалізацій.
ThomasX

Також можуть бути недоліки моделі інтерфейсу + розширення. Візьмемо, наприклад, бібліотеку Linq. Це чудово, але якщо вам потрібно буде використовувати його лише один раз у цьому кодовому файлі, повна бібліотека Linq буде доступна в IntelliSense НА ВСІМ ІНЮЧАСНОМ у вашому файлі коду. Це може значно уповільнити продуктивність IDE.
KeithS

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

25

Це все про те, що ви хочете моделювати:

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

Наприклад, собака - тварина, таким чином, ми маємо

class Dog : Animal { ... }

Оскільки не має сенсу насправді створювати загальну тварину (як це виглядатиме?), Ми робимо цей Animalбазовий клас abstract- але це все ще базовий клас. А Dogклас не має сенсу, не будучи ані Animal.

Інтерфейси з іншого боку - це інша історія. Вони не використовують успадкування, але надають поліморфізм (який може бути реалізований і з успадкуванням). Вони не моделюють є- відносини, але більше він підтримує .

Візьмемо, наприклад, IComparable- об'єкт, який підтримує порівняння з іншим .

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

В принципі, можна додавати та видаляти інтерфейси так само, як розширення, не змінюючи поведінку класу, просто збагачуючи доступ до нього. Ви не можете хоч забрати базовий клас, оскільки об'єкт став би безглуздим!


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

1
@Gulshan: Якщо це - з якихось причин - не має сенсу створювати екземпляр базового класу. Ви можете "створити" чихуахуа або білу акулу, але не можете створити тварину без додаткової спеціалізації - таким чином ви зробите Animalабстрактним. Це дає змогу записувати abstractфункції, які спеціалізуються на похідних класах. Або більше з точки зору програмування - це не робить , ймовірно , ніякого сенсу , щоб створити UserControl, але як Button цеUserControl , тому у нас є один і той же тип відносин клас / BaseClass.
Даріо

якщо у вас є абстрактні методи, то у вас є абстрактний клас. І абстрактні методи у вас є, коли для їх реалізації немає значення (наприклад, "йти" для тварин). І звичайно, іноді ви не хочете дозволити об'єкти якогось загального класу, ніж ви робите це абстрактним.
Даїній

Не існує жодного правила, яке говорить про те, що якщо граматично і семантично значимо сказати "B - це A", то A повинен бути абстрактним класом. Вам слід віддати перевагу інтерфейсам, поки ви дійсно не зможете уникнути класу. Спільний код можна здійснити за допомогою композиції інтерфейсів тощо. Це полегшує уникнення фарбування себе в куточку з дивними деревами спадщини.
сара

3

Я тільки зрозумів, що не використовував абстрактні класи (принаймні не створені) роками.

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

Навіть якщо є потреба у спільній поведінці між класами, що реалізують інтерфейс, я б використовував незалежний помічник, а не абстрактний клас.

Я б пішов на інтерфейси.


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

Те саме про відчуття поколювання. Класи - перша і головна особливість C # і Java - це особливість для створення типу та негайного навіювання його на одну реалізацію.
Джессі Мілікан

2

ІМХО, я думаю, що тут відбувається змішування понять.

Класи використовують певний тип успадкування, тоді як інтерфейси використовують інший тип успадкування.

Обидва види успадкування важливі і корисні, і кожен з них має свої плюси і мінуси.

Почнемо з початку.

Історія з інтерфейсами починається довгий час назад із C ++. У середині 1980-х, коли розроблявся C ++, ідея інтерфейсів як іншого типу ще не була реалізована (це прийшло б вчасно).

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

Щоб мати змогу застосувати рекомендацію GoF Book: "Програмувати для інтерфейсу, а не для реалізації", C ++ підтримує абстрактні класи та багатонаступне успадкування, щоб мати можливість робити те, що інтерфейси в C # здатні (і корисні!) Робити .

C # вводить новий тип типу, інтерфейси та новий вид успадкування, успадкування інтерфейсу, щоб запропонувати принаймні два різні способи підтримки "Програмувати для інтерфейсу, а не для реалізації", з одним важливим застереженням: лише C # підтримує множинне успадкування з успадкуванням інтерфейсу (а не з успадкуванням реалізації).

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

Я сподіваюся, що це може допомогти.

З повагою, Гастоне


1

Застосування методів розширення до інтерфейсу корисно для застосування загальної поведінки в класах, які можуть мати спільний інтерфейс. Такі класи вже можуть існувати і бути закритими для розширень за допомогою інших засобів.

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

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


Ні, це не єдиний безпечний спосіб. Є ще один безпечний спосіб. Це методи розширення. Клієнти взагалі не будуть заражені. Ось чому я згадав інтерфейси + методи розширення.
Гульшан

@Ed James Я думаю, що "менш потужний" = "більш гнучкий" Коли ви вибрали б більш потужний абстрактний клас замість більш гнучких інтерфейсів?
Гульшан

@Ed James У методах розширення asp.mvc використовувались з самого початку. Що ти думаєш про це?
Гульшан

0

Я думаю, що в C # множинне успадкування не підтримується, і тому концепція інтерфейсу введена в C #.

У випадку абстрактних класів також не підтримується багатократне успадкування. Тому легко зважитися на використання більш абстрактних класів або інтерфейсів.


Якщо бути точним, це чисті абстрактні класи, які в C # називаються "інтерфейсами".
Йохан Буле

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.