Чи варто використовувати абстрактні чи віртуальні методи?


11

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

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

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

Анотація версії:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Віртуальна версія:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Чому ми вважаємо, що інтерфейс не бажаний?
Ентоні Пеграм

Без часткової проблеми важко сказати, що одна краща за іншу.
Кодизм

@Anthony: Інтерфейс небажаний, оскільки є корисна функціональність, яка також перейде в цей клас.
Данк

4
return ReturnType.NotImplemented? Серйозно? Якщо ви не можете відхилити незавершений тип під час компіляції (ви можете використовувати абстрактні методи), принаймні киньте виняток.
Ян Худек

3
@Dunk: Тут вони є необхідними. Повернені значення не будуть відмічені.
Ян Худек

Відповіді:


15

Мій голос, якби я споживав ваші речі, був би за абстрактні методи. Це йде разом із "рано провалюватися". Можливо, під час декларування додавати всі методи (хоча будь-який гідний інструмент рефакторингу зробить це швидко), але, принаймні, я знаю, в чому проблема, і вирішити її. Я вважаю за краще, ніж потім налагоджувати 6 місяців і 12 людей варто змінити, щоб зрозуміти, чому ми раптом отримуємо не реалізований виняток.


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

3
+1 - Окрім того, що не вдалося домогтися, спадкоємці можуть одразу побачити, які методи їх потрібно застосувати за допомогою «Реалізувати абстрактний клас», а не робити те, що, на їхню думку, було достатньо, а потім провалитись під час виконання.
Теластин

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

25

Віртуальна версія є схильною до помилок і семантично неправильною.

Анотація говорить: "Цей метод тут не реалізований. Ви повинні застосувати його, щоб цей клас працював"

Віртуальний говорить: "У мене реалізація за замовчуванням, але ви можете змінити мене, якщо вам потрібно"

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


3

Це залежить від використання вашого класу.

Якщо методи мають якусь розумну «порожню» реалізацію, у вас є безліч методів, і ви часто перекриваєте лише деякі з них, то використання virtualметодів має сенс. НаприкладExpressionVisitor реалізується таким чином.

В іншому випадку, я думаю, вам слід скористатися abstract методи.

В ідеалі у вас не повинно бути методів, які не реалізуються, але в деяких випадках це найкращий підхід. Але якщо ви вирішили це зробити, такі методи повинні кидати NotImplementedException, а не повертати якесь особливе значення.


Зауважу, що "NotImplementedException" часто вказує на помилку пропуску, тоді як "NotSupportedException" вказує на явний вибір. Крім цього, я згоден.
Ентоні Пеграм

Варто зазначити, що багато методів визначаються терміном "Робіть все необхідне, щоб задовольнити будь-які зобов'язання, пов'язані з X". Викликати такий метод на об'єкт, у якого немає елементів, пов'язаних з X, може бути безглуздим, але все-таки буде чітко визначеним. Крім того, якщо об’єкт може мати або не мати будь-яких зобов'язань, пов’язаних з X, це, як правило, чистіше і ефективніше беззастережно сказати "Задовольнити всі свої зобов'язання, пов'язані з X", ніж спочатку запитати, чи є у нього якісь зобов'язання, і умовно попросити його задовольнити їх .
supercat

1

Я б запропонував вам переглянути свій окремий інтерфейс, визначений базовим класом, а потім дотримуватися абстрактного підходу.

Код візуалізації таким чином:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Це вирішує ці проблеми:

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

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

напр.

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

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


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

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

-2

Введення залежності залежить від інтерфейсів. Ось короткий приклад. Студент класу має функцію CreateStudent, яка потребує параметра, який реалізує інтерфейс "IReporting" (методом ReportAction). Після того, як він створює студент, він викликає ReportAction за параметром конкретного класу. Якщо система створена для надсилання електронного листа після створення студента, ми надсилаємо в конкретний клас, який надсилає електронну пошту в його реалізації ReportAction, або ми можемо надіслати інший конкретний клас, який надсилає вихід на принтер у своїй реалізації ReportAction. Відмінно підходить для повторного використання коду.


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