Интерфейс vs. протокол.
От: 0x7be СССР  
Дата: 13.04.11 18:07
Оценка: 14 (4)
В современных мэйнстримовых языках интерфейс представляет собой набор функций (сигнатур), которые комонент должен реализовывать. Однако контракт компонента часто подразумевает нечто большее чем просто номенклатура возможных вызовов (ну или сообщений, так тоже можно сказать). В частности, компоненты могут иметь протокол, то есть легальные последовательности вызовов.

Самый простой пример:
interface ISomething
{
  void Open(...);

  void Read(...);
  void Write(...);
 
  void Close(...);
}

Хотя интерфейс позволяет вызывать методы в любом порядке, здесь первым вызовом необходимо делать Open, последним Close, а остальные между ними. К сожалению, протокол тут выражен лишь в комментариях со всеми недостатками такого подхода.
Можно ли выразить протокол в коде с имеющимися средствами языка C#?

Да, есть некоторые варианты. Например, разбить этот интерфейс на два:

interface ISomethingProvider
{
  ISomethingOperator Open(...);
}

interface ISomethingOperator
{
  void Read(...);
  void Write(...);
 
  void Close(...);
}

Имея первый интерфейс программист физически не может вызвать "не те функции" до вызова Open. Впрочем, он может вызвать
Read/Write после Close, что тоже есть нарушение протокола. Более радикальный вариант переработки имеет следующий вариант:
interface ISomethingProvider
{
  ISomethingOperator Open(...);
}

interface ISomethingOperator
{
  ISomethingOperator Read(...);
  ISomethingOperator Write(...);
 
  void Close(...);
}

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

Фактически это означает, что интерфейсы должны быть линейными типами, то есть допускать лишь одно использование. C# не позволяет наложить такое ограничение на тип, но его можно контролировать в run-time, бросая исключение. Методы, в зависимости от исхода своей работы, могут возвращать разные интерфейсы, пуская пользовательский код по разным путям в графе состояний протокола. Здесь бы очень пригодились алгебраические типы и pattern-matching, которого в C# тоже нет, но кое-что из палок и веревок соорудить тоже можно

В каком-то смысле этот подход — это доведение принципа ISP до крайности, включающей выделение интерфейса не только для отдельного клиента компонента, но и для отдельного протокольного состояния.

В чем преимущество такого подхода? Протокол становится выражен в коде и статически контролируется компилятором. Программист во многом лишается возможности его нарушить из-за небрежности. Что, пожалуй, ещё важнее, изменение такого протокола приведет к поломке собираемости кода, протокол нарушающего, что очень важно при поддержке большого проекта. Можно сказать, что это будет отловлено и юнит-тестами. Но тесты — это, так сказать, экспериментальная проверка на конечном множестве прецедентов, а такое формальное выражение протокола даст возможность через систему типов статически доказать , что протокол соблюден.

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

Предлагаю обсудить эту идею и её жизнеспособность в реальных проектах.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.