Плохо ли использовать интерфейс только для категоризации?

12

Например:

Скажем , у меня есть классы A, B, C. У меня есть два интерфейса, давайте называть их IAnimalи IDog. IDogнаследует от IAnimal. Aи Bесть IDog, а Cнет, но это IAnimal.

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

Это плохая практика?

Кендалл Фрей
источник
5
@JarrodRoberson, хотя я склонен согласиться, если вы работаете в мире .Net, вы как бы застряли с ним (или будете идти против установленного соглашения, если вы сделаете это иначе).
Даниэль Б
2
@JarrodRoberson ИМХО 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

Интерфейс, который не имеет ни одного члена или имеет точно такие же члены с другим интерфейсом, который унаследован от того же интерфейса, называемого интерфейсом маркера, и вы используете его в качестве маркера.

Это неплохая практика, но интерфейс можно заменить атрибутами (аннотациями), если язык, который вы используете, поддерживает это.

Я могу с уверенностью сказать, что это неплохая практика, потому что я видел интенсивное использование шаблона «Маркерный интерфейс» в Orchard Project.

Вот пример из проекта Orchard.

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 # в качестве эталонного языка)? Я знаю, что мог бы выдать исключение, если объект не имеет атрибута, но шаблон интерфейса ловит ошибки во время компиляции.
Кендалл Фрей
Если вы используете Marker Interface, то у вас вообще нет преимущества проверки компиляции. Вы в основном делаете проверку типа интерфейсом.
Freshblood
Я не совсем понимаю. Этот шаблон 'Marker interface' обеспечивает проверки во время компиляции, поскольку вы не можете передать Cметод, ожидающий IDog.
Кендалл Фрей
Если вы используете этот шаблон, значит, вы выполняете рефлексивную работу. Я имею в виду, что вы динамически или статически делаете проверки типов. Я уверен, что у вас что-то не так в вашем случае использования, но я не видел, как вы используете это.
Freshblood
Позвольте мне описать это так, как я это сделал в комментарии к ответу Теластина. например, у меня есть Kennelкласс, который хранит список IDogс.
Кендалл Фрей
4

Да, это плохая практика почти на каждом языке.

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

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

[править: уточнение]

Я имею в виду, что вы делаете некоторую логику на основе интерфейса. Почему некоторые методы могут работать только с собаками, а другие - с животными? Эта разница является подразумеваемым договором, поскольку нет фактического члена, который обеспечивает разницу; нет фактических данных в системе. Единственное отличие - это интерфейс (отсюда и комментарий «не печатать»), и то, что это значит, будет отличаться у разных программистов. [/редактировать]

Некоторые языки предоставляют механизмы черт, где это не так уж и плохо, но они, как правило, фокусируются на возможностях, а не на совершенствовании. У меня мало опыта с ними, поэтому я не могу говорить о лучших практиках там. В C ++ / Java / C # хотя ... не хорошо.

Telastyn
источник
Я смущен твоими средними двумя параграфами. Что вы подразумеваете под «подтипом»? Я использую интерфейсы, которые являются контрактами, а не реализациями, и я не уверен, как применяются ваши комментарии. Что вы подразумеваете под «не печатать»? Что вы подразумеваете под «подразумеваемым»? 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.");
    }
}
Майк Кодер
источник
Последний раздел кода в основном то, для чего я использую это. Я вижу, что версия интерфейса явно короче, а также проверяется во время компиляции.
Кендалл Фрей
У меня есть пример использования simillar, но большинство разработчиков .net настоятельно не советуют, но я не собираюсь использовать атрибуты
GorillaApe
-1

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

Например, предположим, у меня есть интерфейсы:

интерфейс IMaybeMutableFoo {
  Bar Thing {get; set;} // И другие свойства
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
интерфейс IImmutableFoo: IMaybeMutableFoo {};

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

public void StoreData (IImmutableFoo ThingToStore)
{
  // Достаточно сохранить ссылку, поскольку известно, что ThingToStore является неизменным
}
public void StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Вызываем более конкретную перегрузку
}

В тех случаях, когда компилятор не знает, что нужно сохранить IImmutableFoo, он будет вызывать AsImmutable(). Функция должна быстро вернуться, если элемент уже неизменен, но вызов функции через интерфейс все еще занимает время. Если компилятор знает, что элемент уже есть IImmutableFoo, он может пропустить ненужный вызов функции.

Supercat
источник
Downvoter: Хотите прокомментировать? Временами наследовать интерфейс без добавления чего-либо нового глупо, но это не значит, что не бывает хороших (и имхо, недоиспользуемых) техник.
суперкат