Чи є сенс визначати інтерфейс, якщо у мене вже є абстрактний клас?


12

У мене є клас з деякою функцією за замовчуванням / спільним функціонуванням. Я використовую abstract classдля цього:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

Як бачите, у мене також є інтерфейс ITypeNameMapper. Чи є сенс визначати цей інтерфейс, якщо у мене вже є абстрактний клас TypeNameMapperабо abstract classдостатньо?

TypeDefinition і в цьому мінімальному прикладі є абстрактним.


4
Просто FYI - в OOD, перевірка типів у коді є свідченням того, що ваша ієрархія класів неправильна - це говорить вам про створення похідних класів від типу, який ви перевіряєте. Отже, у цьому випадку потрібно інтерфейс ITypeMapper, який визначає метод Map (), а потім реалізувати цей інтерфейс у класах TypeDefinition та ClassDefinition. Таким чином, ваш ClassAssembler просто повторює список ITypeMappers, викликаючи Map () на кожному, і отримує потрібний рядок обох типів, оскільки кожен з них має власну реалізацію.
Родні П. Барбаті

1
Використання базового класу замість інтерфейсу, якщо це абсолютно не потрібно, називається "спалюванням бази". Якщо це можна зробити за допомогою інтерфейсу, зробіть це з інтерфейсом. Потім абстрактний клас стає корисним додатком. artima.com/intv/dotnet.html Зокрема, для видалення потрібно походити від MarshalByRefObject, тому, якщо ви хочете, щоб його видалили, ви не можете отримати нічого іншого.
Бен

@ RodneyP.Barbati не працюватиме, якщо я хочу мати багато картографів одного типу, на які я можу переключитися залежно від ситуації
Конрад,

1
@Ben спалює базу, що цікаво. Дякую за дорогоцінний камінь!
Конрад

@Konrad Це невірно - ніщо не заважає вам реалізувати інтерфейс, викликаючи іншу реалізацію інтерфейсу, отже, кожен тип може мати підключувану реалізацію, яку ви відкриваєте через впровадження у тип.
Родні П. Барбаті

Відповіді:


31

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

Тож якщо у мене є клас, який є і TypeNameMapper, і SomethingelseMapper, я можу зробити:

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath: Однозначна відповідальність насправді є фактором, що сприяє успадкуванню. Розглянемо три можливих риси: ICanFly, ICanRun, ICanSwim. Вам потрібно створити 8 класів істот, по одному для кожного поєднання ознак (YYY, YYN, YNY, ..., NNY, NNN). SRP вказує, що ви реалізуєте кожну поведінку (літати, бігати, плавати) один раз, а потім повторно реалізовувати їх на 8 істот. Склад був би тут навіть кращим, але ігноруючи склад на секунду, ви вже повинні побачити необхідність успадковувати / впроваджувати кілька окремих способів багаторазового використання через SRP.
Flater

4
@Konrad ось що я маю на увазі. Якщо ви пишете інтерфейс, і він ніколи не потрібен, то вартість становить 3 LoC. Якщо ви не пишете інтерфейс і вважаєте, що він вам потрібен, ціна може призвести до рефакторингу всієї вашої програми.
Еван

3
(1) Реалізація інтерфейсів - це спадщина? (2) Питання та відповіді повинні бути кваліфіковані. "Це залежить." (3) Для багаторазового успадкування на ньому потрібна попереджувальна наклейка. (4) Я міг би показати вам K з краптальних формальних interfaceнедобросовісних дій. Зайві реалізації, які повинні бути в основі - які розвиваються незалежно !, умовні зміни логіки, що призводять до цього мотлоху, потрібні шаблонні методи, зламана інкапсуляція, неіснуюча абстракція. Моя улюблена: класи 1-методу interface, кожен з яких реалізує свій власний 1-метод , кожен w / лише один екземпляр. ... і т. д. і т. д. тощо
radarbob

3
У коді є невидимий коефіцієнт тертя. Ви відчуваєте це, коли змушені читати зайві інтерфейси. Тоді він показує себе нагріванням, як космічний човник, що врізається в атмосферу, як реалізація інтерфейсу, що спонукає абстрактний клас. Це зона сутінків, і це човен Challenger. Приймаючи свою долю, я спостерігаю за буйною плазмою з безстрашним, відстороненим дивом. Непомітне тертя розриває фасад досконалості, осаджуючи зруйнований корпус у виробництво з громовим боком.
radarbob

4
@radarbob Поетичний. ^ _ ^ Я погодився б; хоча вартість інтерфейсів низька, вона не існує. Не стільки в їх написанні, але в ситуації налагодження це ще 1-2 кроки, щоб дійти до фактичної поведінки, яка може швидко скластися, коли ви отримаєте півдесятка рівнів абстракції глибоко. У бібліотеці зазвичай цього варто. Але в застосуванні рефакторинг краще, ніж передчасне узагальнення.
Errorsatz

1

Інтерфейси та абстрактні класи служать різним цілям:

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

У вашому прикладі interface ITypeNameMapperвизначає потреби клієнтів і abstract class TypeNameMapperне додає ніякої цінності.


2
Анотаційні заняття належать і клієнтам. Я не знаю, що ти маєш на увазі під цим. Все, що є, publicналежить клієнту. TypeNameMapperдодає велику цінність, оскільки це рятує реалізатор від написання якоїсь загальної логіки.
Конрад

2
Будь інтерфейс представлений як interfaceчи ні, має значення лише те, що C # забороняє повне багаторазове успадкування.
Дедуплікатор

0

Вся концепція інтерфейсів була створена для підтримки сім'ї класів, що мають спільний API.

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

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

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


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

1
Дітто, @maaartinus. І навіть не "... як би це був інтерфейс". Публічні члени класу - це інтерфейс. Також ditto для "ви можете це зробити будь-коли пізніше"; це стосується основи дизайну 101.
radarbob

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

1
@supercat Я можу погодитися, якщо припустити, що ви пишете бібліотеку. Якщо це програма, то я не можу побачити жодних проблем із заміною всіх подій одночасно. Мій Eclipse може це зробити (Java, а не C #), але якщо цього не вдалося, це просто як find ... -exec perl -pi -e ...і, можливо, виправлення тривіальних помилок компіляції. Моя думка полягає в тому, що перевага відсутності (на даний момент) марної речі набагато більша, ніж можливі майбутні заощадження.
maaartinus

Перше речення було б правильним, якби ви позначили його interfaceяк код (ви говорите про C # - interfaces, а не абстрактний інтерфейс, як будь-який набір класів / функцій / тощо), і закінчили його "... але все-таки поза законом повне кратне спадщину ». Немає потреби в interfaces, якщо у вас ІМ.
Дедуплікатор

0

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

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

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

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


-1

На це досить складно відповісти, не знаючи решти програми.

Якщо припустити, що ви використовуєте якусь модель DI і створили ваш код максимально розширеним (щоб ви могли TypeNameMapperлегко додавати новий ), я б сказав так.

Причини:

  • Ви фактично отримуєте його безкоштовно, оскільки базовий клас реалізує interfaceвам не потрібно про це турбуватися в будь-яких дочірніх реалізаціях
  • Більшість фреймворків IoC та Mocking бібліотек очікують interfaces. Хоча багато хто з них працюють з абстрактними класами, це не завжди є заданим, і я дуже вірю в той же шлях, як і інші 99% користувачів бібліотеки, де це можливо.

Причини проти:

  • Власне кажучи (як ви вже вказували), ВАМ НЕ СПОСІБНО
  • Якщо class/ interfaceзміни, є невеликі додаткові накладні витрати

Я б сказав, що ви повинні створити interface. Причини проти досить мізерні, але, хоча можливо використовувати тези в глузуванні та IoC, з інтерфейсами часто набагато простіше. Зрештою, я б не критикував код колеги, якби вони пішли іншим шляхом.

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