Интерфейс-маркер (шаблон проектирования)

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Интерфейс-маркер, маркер (англ. marker interface pattern) — это шаблон проектирования, применяемый в языках программирования с проверкой типов во время выполнения. Шаблон предоставляет возможность связать метаданные (интерфейс) с классом даже при отсутствии в языке явной поддержки для метаданных.

Чтобы использовать эту модель, класс реализует интерфейс[1] («помечается интерфейсом»), а взаимодействующие с классом методы проверяют наличие интерфейса. В отличие от обычного интерфейса, который определяет функциональность (в виде объявлений методов и свойств), которой должен обладать реализуемый класс объектов, важен сам факт обладания класса маркером. Маркер лишь является признаком наличия определённого поведения у объектов класса, помеченного маркером. Разумеется, возможны и «смешанные» интерфейсы, однако при неаккуратном использовании они могут создавать путаницу.

Пример применения маркеров-интерфейсов в языке программирования Java является 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).

Решением описанной проблемы является поддержка метаданных непосредственно в синтаксисе языка:

  • Такие языки как .NET framework и Java (начиная с версии Java 5 (1.5)) имеют поддержку таких метаданных. В .NET, они называются «пользовательскими атрибутами», в Java их называют «аннотациями». Несмотря на разные названия, они концептуально равнозначны, могут быть определены для классов, переменных, методов и параметров методов, а также быть доступны с помощью отражения.
  • В Python термин англ. marker interface применяется в компонентной архитектуре Zope (Zope Component Architecture, ZCA) и построенной на его базе системе управления содержимым Plone. В ZCA маркерами могут отмечаться не только классы, но и отдельные объекты.[3]

См. также[править | править исходный текст]

Примечания[править | править исходный текст]

  1. Bloch Joshua Item 37: Use marker interfaces to define types // Effective Java (Second edition). — Addison-Wesley, 2008. — P. 179. — ISBN 978-0-321-35668-0
  2. INamingContainer - интерфейс (System.Web.UI)
  3. BlueBream v1.0b4 documentation, Tutorial

Литература[править | править исходный текст]

  • Joshua Bloch, "Effective Java (Second edition), " Item 37: Use marker interfaces to define types, page 179.