«Свободные интерфейсы», которые поддерживают порядок в цепочке вызовов - PullRequest
2 голосов
/ 24 ноября 2010

Существует ли элегантный / удобный способ (не создавая много «пустых» классов или, по крайней мере, они не должны раздражать), чтобы иметь свободные интерфейсы, поддерживающие порядок на уровне компиляции.http://en.wikipedia.org/wiki/Fluent_interface

с идеей разрешить эту компиляцию

var fluentConfig = new ConfigurationFluent().SetColor("blue")
                                           .SetHeight(1)
                                           .SetLength(2)
                                           .SetDepth(3);

и отклонить эту

var fluentConfig = new ConfigurationFluent().SetLength(2)
                                           .SetColor("blue")
                                           .SetHeight(1)
                                           .SetDepth(3);

Ответы [ 5 ]

8 голосов
/ 24 ноября 2010

Каждый шаг в цепочке должен возвращать интерфейс или класс, который включает в себя только те методы, которые допустимо использовать после текущего шага.Другими словами, если SetColor должен стоять первым, ConfigurationFluent должен иметь только метод SetColor.Затем SetColor вернет объект, который имеет только метод SetHeight и т. Д.

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

1 голос
/ 15 сентября 2011

У меня есть три способа сделать это в C ++, используя, по сути, FSM времени компиляции для проверки действий.Вы можете найти код на github .

1 голос
/ 14 сентября 2011

возможно, компилятор может проверить, что методы вызываются в том же порядке, в котором они определены.

это может быть новая функция для компиляторов.

Или, возможно, с помощью аннотаций, что-то вроде:

class ConfigurationFluent {

 @Called-before SetHeight
 SetColor(..)  {}

 @Called-After SetColor
 SetHeight(..) {}

 @Called-After SetHeight
 SetLength(..){ }

 @Called-After SetLength             
 SetDepth(..) {}

}
1 голос
/ 24 ноября 2010

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

Более длинный ответ начинается с игрыАдвокат дьявола.Если бы у меня были зависимые свойства (то есть свойства, которые требовали, чтобы вначале были установлены другие свойства), я мог бы реализовать их примерно так:

method SetLength(int millimeters)
    if color is null throw new ValidationException

    length = millimeters
    return this
end

(ПРИМЕЧАНИЕ: приведенное выше не отображается ни на один реальный язык,это просто psuedocode)

Так что теперь у меня есть исключения для беспокойства.Если я не подчиняюсь правилам, беглый объект выдаст исключение.Теперь предположим, что у меня есть объявление, подобное вашему:

var config = new Fluent().SetLength(2).SetHeight(1).SetDepth(3).SetColor("blue");

Когда я ловлю ValidationException, потому что длина зависит от того, какой цвет задается первым, как я, как пользователь, должен знать, каков правильный порядок?Даже если бы каждый метод SetX находился на отдельной строке, трассировка стека просто выдаст мне строку, где переменная config была объявлена ​​в большинстве языков.Кроме того, как я должен держать правила этого объекта прямо в моей голове по сравнению с другими объектами?Это кокофония противоречивых идеалов.

Такие проверки приоритетов нарушают дух подхода «Свободного интерфейса».Этот подход был разработан для удобной настройки сложных объектов.Вы получаете удобство, когда пытаетесь навести порядок.

Чтобы правильно и элегантно реализовать свободный интерфейс, есть пара рекомендаций, которые лучше всего соблюдать, чтобы поблагодарить потребителей вашего класса:

  • Предоставление значимых значений по умолчанию: сводит к минимуму необходимость изменения значений и сводит к минимуму шансы на создание недопустимого объекта.
  • Не выполняйте проверку конфигурации до тех пор, пока об этом явно не попросят.Это может происходить, когда мы используем конфигурацию для создания нового полностью сконфигурированного объекта или когда потребитель явно вызывает метод Validate().
  • В любых выданных исключениях, убедитесь, что сообщение об ошибке ясно и указывает налюбые несоответствия.
0 голосов
/ 24 ноября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...