Какова цель интерфейса маркера? - PullRequest
82 голосов
/ 21 июня 2009

Для чего нужен интерфейс маркера?

Ответы [ 10 ]

72 голосов
/ 21 июня 2009

Это немного касательная, основанная на ответе "Митч Уит".

Как правило, каждый раз, когда я вижу, что люди цитируют руководящие принципы проектирования фреймворка, я всегда хочу упомянуть следующее:

В большинстве случаев вы должны игнорировать рекомендации по проектированию каркаса.

Это не из-за каких-либо проблем с рекомендациями по разработке структуры. Я думаю, что .NET Framework - это фантастическая библиотека классов. Большая часть этой фантастики вытекает из руководящих принципов проектирования фреймворка.

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

Множество предложений в нем могут помочь вам сделать то, что:

  1. Может быть, не самый простой способ реализации чего-либо
  2. Может привести к дополнительному дублированию кода
  3. Может иметь дополнительные накладные расходы времени выполнения

.NET Framework большой, действительно большой. Он настолько велик, что было бы абсолютно неразумно предполагать, что кто-то обладает детальными знаниями о каждом его аспекте. На самом деле гораздо безопаснее предположить, что большинство программистов часто сталкиваются с частями инфраструктуры, которые они никогда не использовали ранее.

В этом случае основными целями дизайнера API являются:

  1. Держите вещи в соответствии с остальной частью структуры
  2. Устранить ненужные сложности в области поверхности API

Руководящие указания по проектированию инфраструктуры подталкивают разработчиков к созданию кода для достижения этих целей.

Это означает выполнение таких действий, как обход слоев наследования, даже если это означает дублирование кода или выталкивание всего кода, генерирующего исключения, в «точки входа» вместо использования общих помощников (чтобы трассировка стека имела больше смысла в отладчике), и много других подобных вещей.

Основная причина, по которой эти рекомендации предлагают использовать атрибуты вместо интерфейсов маркеров, заключается в том, что удаление интерфейсов маркеров делает структуру наследования библиотеки классов гораздо более доступной. Диаграмма классов с 30 типами и 6 уровнями иерархии наследования очень сложна по сравнению с диаграммой с 15 типами и 2 уровнями иерархии.

Если ваши API действительно используют миллионы разработчиков, или ваша кодовая база действительно велика (скажем, более 100K LOC), то выполнение этих рекомендаций может помочь.

Если 5 миллионов разработчиков тратят 15 минут на изучение API, а не на 60 минут на его изучение, то получается чистая экономия в 428 человеко-лет. Это много времени.

Однако в большинстве проектов не участвуют миллионы разработчиков или 100K + LOC. В типичном проекте, скажем, с 4 разработчиками и около 50 тыс. Лок, набор предположений сильно отличается. Разработчики в команде будут гораздо лучше понимать, как работает код. Это означает, что имеет больше смысла оптимизировать процесс быстрого создания высококачественного кода и уменьшения количества ошибок и усилий, необходимых для внесения изменений.

Трата 1 недели на разработку кода, соответствующего .net Framework, против 8 часов написания кода, который легко изменить и содержит меньше ошибок, может привести к:

  1. Поздние проекты
  2. Нижние бонусы
  3. Увеличено количество ошибок
  4. Больше времени проводите в офисе и меньше времени на пляже и пейте маргариту.

Без 4,999,999 других разработчиков, чтобы покрыть затраты, это обычно не стоит.

Например, тестирование интерфейсов маркеров сводится к одному выражению "is" и приводит к меньшему количеству кода, который ищет атрибуты.

Так что мой совет:

  1. Строго следуйте руководящим принципам структуры, если вы разрабатываете библиотеки классов (или виджеты пользовательского интерфейса), предназначенные для широкого распространения.
  2. Подумайте о принятии некоторых из них, если у вас более 100K LOC в вашем проекте
  3. В противном случае игнорируйте их полностью.
43 голосов
/ 21 июня 2009

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

Дизайн интерфейса и .NET Руководство по проектированию типов - Дизайн интерфейса не поощряет использование интерфейсов маркеров в пользу использования атрибутов в C #, но, как указывает @Jay Bazuzi, легче проверить интерфейсы маркеров, чем атрибутов: o is I

Итак, вместо этого:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

Рекомендации .NET рекомендуют вам сделать это:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 
20 голосов
/ 02 октября 2012

Поскольку в каждом другом ответе говорилось: «Их следует избегать», было бы полезно получить объяснение, почему.

Во-первых, почему используются маркерные интерфейсы: они существуют, чтобы позволить коду, использующему объект, который его реализует, проверить, реализуют ли они указанный интерфейс и по-разному ли он относится к объекту.

Проблема этого подхода заключается в том, что он нарушает инкапсуляцию. Сам объект теперь имеет косвенный контроль над тем, как он будет использоваться снаружи. Более того, он знает систему, в которой он будет использоваться. Применяя интерфейс маркера, определение класса предполагает, что он будет использоваться где-то, что проверяет наличие маркера. Он неявно знает среду, в которой он используется, и пытается определить, как его следует использовать. Это идет вразрез с идеей инкапсуляции, потому что он знает о реализации части системы, которая существует полностью за пределами своей области видимости.

На практическом уровне это уменьшает мобильность и возможность повторного использования. Если класс повторно используется в другом приложении, интерфейс также необходимо скопировать, и он может не иметь никакого значения в новой среде, что делает его полностью избыточным.

Таким образом, «маркер» - это метаданные о классе. Эти метаданные не используются самим классом и имеют смысл только для (некоторых!) Внешнего клиентского кода, чтобы он мог обрабатывать объект определенным образом. Поскольку это имеет значение только для клиентского кода, метаданные должны быть в клиентском коде, а не в классе API.

Разница между «интерфейсом маркера» и обычным интерфейсом заключается в том, что интерфейс с методами сообщает внешнему миру, как его можно использовать , тогда как пустой интерфейс подразумевает, что он сообщает внешнему миру, как он следует использовать .

7 голосов
/ 10 июля 2013

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

Предположим, вы хотите определить метод, который ожидает аргумент, тип которого должен быть точно одним из A, B или C. Во многих функционально-первых языках (например, F # ) такой тип может быть четко определено как:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

Тем не менее, на первых языках ОО, таких как C #, это невозможно. Единственный способ добиться чего-то подобного здесь - это определить интерфейс IArg и «пометить» A, B и C этим.

Конечно, вы можете избежать использования интерфейса маркера, просто принимая тип «объект» в качестве аргумента, но тогда вы потеряете выразительность и некоторую степень безопасности типов.

Дискриминационные профсоюзные типы чрезвычайно полезны и существуют на функциональных языках не менее 30 лет. Странно, что до сегодняшнего дня все основные языки OO игнорировали эту функцию - хотя на самом деле она не имеет ничего общего с функциональным программированием, но относится к системе типов.

6 голосов
/ 21 июня 2009

Маркерный интерфейс - это просто пустой интерфейс. Класс будет реализовывать этот интерфейс как метаданные, которые будут использоваться по какой-то причине. В C # вы чаще используете атрибуты для разметки класса по тем же причинам, по которым вы используете интерфейс маркеров на других языках.

4 голосов
/ 14 июня 2016

Эти два метода расширения решат большинство проблем, которые, по утверждению Скотта, предпочитают интерфейсы маркеров над атрибутами:

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

Теперь у вас есть:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

против

if (o is IFooAssignable)
{
    //...
}

Я не вижу, как построение API займет 5 раз с первым шаблоном по сравнению со вторым, как утверждает Скотт.

4 голосов
/ 24 января 2011

Интерфейс маркера позволяет пометить класс таким образом, который будет применяться ко всем классам-потомкам. «Чистый» интерфейс маркера не определит и не унаследует ничего; более полезным типом маркерных интерфейсов может быть тот, который «наследует» другой интерфейс, но не определяет новых членов. Например, если есть интерфейс «IReadableFoo», можно также определить интерфейс «IImmutableFoo», который будет вести себя как «Foo», но пообещает любому, кто его использует, ничего не изменит его значение. Подпрограмма, которая принимает IImmutableFoo, сможет использовать его так же, как и IReadableFoo, но подпрограмма будет принимать только те классы, которые были объявлены как реализующие IImmutableFoo.

Я не могу придумать много вариантов использования «чистых» маркерных интерфейсов. Единственное, о чем я могу думать, было бы, если бы EqualityComparer (из T) .Default возвращал Object.Equals для любого типа, который реализовал IDoNotUseEqualityComparer, даже если этот тип также реализовал IEqualityComparer. Это позволило бы иметь незапечатанный неизменяемый тип без нарушения принципа подстановки Лискова: если тип закрывает все методы, связанные с проверкой на равенство, производный тип может добавить дополнительные поля и иметь их изменяемость, но мутация таких полей не будет быть видимым, используя любые методы базового типа. Может быть не страшно иметь незапечатанный неизменяемый класс и избегать любого использования EqualityComparer.Default или доверенных производных классов, чтобы не реализовывать IEqualityComparer, но производный класс, который действительно реализовывал IEqualityComparer, мог бы выглядеть как изменяемый класс, даже если рассматривать его как base объект класса.

1 голос
/ 09 июля 2018

Интерфейс маркера - это просто процедурное программирование на языке ОО. Интерфейс определяет контракт между исполнителями и потребителями, за исключением интерфейса маркера, поскольку интерфейс маркера определяет только себя. Таким образом, прямо из шлюза интерфейс маркера терпит неудачу с основной целью быть интерфейсом.

0 голосов
/ 08 марта 2018

Интерфейс маркера - это полностью пустой интерфейс, который не имеет тела / элементов данных / реализации.
Класс реализует интерфейс маркера , когда это необходимо, это просто " mark "; означает, что он сообщает JVM, что определенный класс предназначен для клонирования, поэтому разрешите его клонировать. Этот конкретный класс предназначен для сериализации своих объектов, поэтому, пожалуйста, разрешите сериализовать его объекты.

0 голосов
/ 10 февраля 2017

Маркеры - это пустые интерфейсы. Маркер либо есть, либо его нет.

класс Foo: IConfidential

Здесь мы отмечаем Фу как конфиденциального. Никаких фактических дополнительных свойств или атрибутов не требуется.

...