04-05-2023
Интерфейс-маркер, маркер (англ. marker interface pattern) — это шаблон проектирования, применяемый в языках программирования с проверкой типов во время выполнения. Шаблон предоставляет возможность связать метаданные (интерфейс) с классом даже при отсутствии в языке явной поддержки для метаданных.
Чтобы использовать эту модель, класс реализует интерфейс[1] («помечается интерфейсом»), а взаимодействующие с классом методы проверяют наличие интерфейса. В отличие от обычного интерфейса, который определяет функциональность (в виде объявлений методов и свойств), которой должен обладать реализуемый класс объектов, важен сам факт обладания класса маркером. Маркер лишь является признаком наличия определённого поведения у объектов класса, помеченного маркером. Разумеется, возможны и «смешанные» интерфейсы, однако при неаккуратном использовании они могут создавать путаницу.
Пример применения маркеров-интерфейсов в языке программирования Serializable интерфейс. Класс реализует этот интерфейс, чтобы показать, что его экземпляры могут быть записаны в ObjectOutputStream
. Класс ObjectOutputStream
имеет приватный метод writeObject()
, который содержит ряд instanceof
проверок возможности записи, одной из которых является интерфейс Serializable
. Если вся серия проверок оканчивается неудачей, метод выбрасывает исключение NotSerializableException
.
Другим примером является интерфейс INamingContainer, который определен в .NET_Framework. INamingContainer
определяет элемент управления контейнером, который создает новый идентификатор пространства имен в иерархии элементов управления объекта Page
.[2]. Любой элемент управления, который реализует этот интерфейс, создает новое пространство имен, в котором обеспечивается уникальность всех идентификаторов атрибутов дочерних элементов управления в пределах всего приложения. При разработке шаблонных элементов управления необходимо реализовывать этот интерфейс, чтобы избежать конфликтов именования на странице.
Класс Repeater
является элементом управления и представляет собой список с привязкой к данным, который определен в .NET_Framework (ASP.net). Данный элемент позволяет создавать разметку путем повторения указанного шаблона для каждого элемента списка. Во избежание конфликтов имен класс помечается интерфейсом INamingContainer
.
public interface INamingContainer { }
public class Control : IComponent, ... { ... internal bool IsBindingContainer { get { return ((this is INamingContainer) && !(this is INonBindingContainer)); } } ... }
public class Repeater : Control, INamingContainer { ... }
Исходя из свойства IsBindingContainer
исполняющая среда во время генерации страницы дополняет новым пространством имен идентификаторы элементов управления, находящихся в элементе управления Repeater
.
Преимуществом использования маркера является возможность внести в языки, не поддерживающие метаданные, дополнительную информацию об особенностях поведения класса. При этом в некоторых языках, например чтобы извлечь информацию из метаданных требуется больше времени, что при частом использовании отразится на производительности. Так в языке C#, по производительности можно сравнивать конструкцию (для проверки поддержки некоторого поведения или особенностей), когда используется интерфейс-маркер:
control is INamingContainer
и конструкцию с использованием атрибутов и механизма отражения:
control.GetType().IsDefined(typeof(NamingContainerAttrubute), false)
Кроме этого, некоторые языки[какие?] позволяют создавать или генерировать классы (и интерфейсы) и помечать классы интерфейсом-маркером динамически во времени выполнения. Атрибуты или метаданные обычно связываются с классами уже во время компиляции, что приводит к невозможности изменить их поведение в дальнейшем.
Одной из основных проблем с маркером является то, что интерфейс определяет контракт на реализацию классов, и что контракт наследуется всеми подклассами. Это означает, что вы не можете «отменить лишние реализации» маркером. В приведённом примере, если вы создаете подкласс, и не хотите его сериализовать (возможно, потому что это зависит от частичной реализации), вы должны явно бросать исключение NotSerializableException
(согласно документации ObjectOutputStream
).
Решением описанной проблемы является поддержка метаданных непосредственно в синтаксисе языка:
Интерфейс-маркер (шаблон проектирования).