Чи погана практика використовувати інтерфейс лише для категоризації?


12

Наприклад:

Скажімо , у мене є класи A, B, C. У мене два інтерфейси, давайте можемо їх називати IAnimalі IDog. IDogуспадковує від IAnimal. Aі Bє IDogs, хоча Cце не так, але це IAnimal.

Важлива частина полягає в тому, що вона IDogне забезпечує додаткової функціональності. Він використовується лише для того, щоб дозволити Aі B, але ні C, бути переданим як аргумент певним методам.

Це погана практика?



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

2
@JarrodRoberson IMHO MyProject.Data.IAnimalі MyProject.Data.Animalкращі за MyProject.Data.Interfaces.Animalта MyProject.Data.Implementations.Animal
Майк Кодер

1
Справа в тому, що не повинно бути жодних причин навіть турбуватися, чи це Interfaceабо Implementation, чи є в повторюваному префіксі чи просторі імен, це тавтологія в будь-якому випадку і порушує DRY. Dogце все, про що ти повинен дбати. PitBull extends Dogтакож не потрібно надмірності впровадження, слово extendsпідказує мені все, що мені потрібно знати, прочитайте посилання, яке я розмістив у своєму первісному коментарі.

1
@Jarrod: Перестань скаржитися на мене. Поскаржитися на команду розробки .NET. Якби я пішов проти стандарту, мої товариші-біси ненавиділи б мої кишки.
Кендалл Фрей

Відповіді:


13

Інтерфейс, який не має жодного учасника або має абсолютно однакові члени з іншим інтерфейсом, який успадковується від того ж інтерфейсу, який називається Marker Interface, і ви використовуєте його як маркер.

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

Я з упевненістю можу сказати, що це не погана практика, тому що я бачив схему "Маркерний інтерфейс" в Orchard Project.

Ось зразок проекту «Орчард».

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

Будь ласка, зверніться до інтерфейсу маркера .


Чи підтримують атрибути / примітки перевірку часу компіляції (використовуючи C # як основну мову)? Я знаю, що я міг би викинути виняток, якщо об’єкт не має атрибута, але шаблон інтерфейсу виявляє помилки під час компіляції.
Кендалл Фрей

Якщо ви використовуєте Маркерний інтерфейс, щоб у вас взагалі не було переваги перевірки компіляції. Ви в основному робите перевірку типу за інтерфейсом.
Пріснокровний

Я збентежений. Цей шаблон інтерфейсу маркера забезпечує перевірку часу компіляції, оскільки ви не можете передати Cметод очікуванню IDog.
Кендалл Фрей

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

Дозвольте описати це, як я це робив у коментарі до відповіді Теластина. наприклад, у мене є Kennelклас, який зберігає список IDogs.
Кендалл Фрей

4

Так, це погана практика майже кожної мови.

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

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

[редагувати: уточнення]

Я маю на увазі те, що ви керуєтесь певною логікою на основі інтерфейсу. Чому одні методи можуть працювати лише з собаками, а інші - з будь-якою твариною? Ця різниця є контрактом, що мається на увазі, оскільки немає фактичного члена, який забезпечує різницю; відсутні фактичні дані в системі. Єдина відмінність полягає в інтерфейсі (звідси коментар "без набору тексту"), і те, що це означає, буде відрізнятися між програмістами. [/ редагувати]

Деякі мови надають механізми ознак, де це не дуже погано, але вони, як правило, орієнтуються на можливості, а не на вдосконалення. Я з ними мало досвіду, тому не можу говорити про найкращі практики там. У C ++ / Java / C # хоч ... не добре.


Мене бентежить твій середній два абзаци. Що ви маєте на увазі під підтипом? Я використовую інтерфейси, які є контрактами, а не реалізаціями, і я не впевнений, як застосовуються ваші коментарі. Що ви маєте на увазі під "не вводити текст"? Що ви маєте на увазі під «мається на увазі»? IDog може зробити все, що може IAnimal, але не гарантовано зможе зробити більше.
Кендалл Фрей

Дякуємо за роз’яснення. У моєму конкретному випадку я точно не використовую інтерфейси по-різному. Це, мабуть, найкраще описати, сказавши, що у мене є Kennelклас, який зберігає список IDogs.
Кендалл Фрей

@KendallFrey: Або викидаєте інтерфейс (погано), або створюєте штучне відмінність, яке пахне неправильною абстракцією.
Теластин

2
@Telastyn - Метою інтерфейсу є те, що надання метаданих
Freshblood

1
@Telastyn: То як атрибути застосовуються до перевірки часу компіляції? AFAIK, в C #, атрибути можна використовувати лише під час виконання.
Кендалл Фрей

2

Я добре знаю лише C #, тому я не можу сказати про це для програмування OO взагалі, але як приклад C #, якщо вам потрібно класифікувати класи, очевидними параметрами є інтерфейси, властивості enum або спеціальні атрибути. Зазвичай я б вибирав інтерфейс незалежно від того, що думають інші.

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}

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

У мене є подібний варіант використання, але більшість .net розробників настійно проти цього, але я не збираюсь використовувати атрибути
GorillaApe

-1

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

Наприклад, припустимо, що у мене є інтерфейси:

інтерфейс IMaybeMutableFoo {
  Річ бар {get; set;} // та інші властивості
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
інтерфейс IImmutableFoo: IMaybeMutableFoo {};

Очікується, що реалізація IImmutableFoo.AsImmutable()повернеться сама; IMaybeMutableFoo.AsImmutable()очікується, що реалізація змінного класу поверне новий глибоко незмінний об'єкт, що містить знімок даних. Запропонований режим, який хоче зберегти знімок даних, що зберігаються в, IMaybeMutableFooможе запропонувати перевантаження:

public void StoreData (IImmutableFoo ThingToStore)
{
  // Збереження посилання буде достатньо, оскільки, як відомо, ThingToStore є незмінним
}
публічна недійсна StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Виклик більш конкретного перевантаження
}

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


Downvoter: Хочете коментувати? Часи успадковувати інтерфейс, не додаючи нічого нового, є нерозумним, але це не означає, що не буває випадків, коли це хороша (і IMHO недостатньо використовувана) техніка.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.