Проблема принципа проектирования CQS: реализация очереди - PullRequest
0 голосов
/ 10 ноября 2018

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

@ Ката указал, что шаблон, который интересовал OP, называется Разделение команд и запросов и заявил, что это хорошая модель для структурирования вашего кода.

Из Википедия :

Разделение команд и запросов (CQS) - это принцип императивного компьютерного программирования. Он был разработан Бертраном Мейером как часть его новаторской работы над языком программирования Eiffel.

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

Я поставил под сомнение правильность этого принципа разработки, поскольку в целом он может сделать ваш код гораздо более утомительным. Например: вы не можете выполнить простой оператор, такой как next = Queue.Dequeue (); Вам потребуются две инструкции: одна для изменения структуры данных и одна для чтения результата.

@ Kata нашла альтернативную реализацию стека, которая, на первый взгляд, удовлетворяет лучшее из обоих миров: взяв страницу из функционального программирования, мы определяем наш стек как неизменную структуру данных. Всякий раз, когда мы нажимаем (x), мы создаем новый узел Stack, который содержит значение x и поддерживает указатель на старый экземпляр Stack. Всякий раз, когда мы выполняем pop (), мы просто возвращаем указатель на следующий экземпляр Stack. Таким образом, мы можем придерживаться принципа разделения команд и запросов.

Пример реализации стека: https://fsharpforfunandprofit.com/posts/stack-based-calculator/

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

РЕДАКТИРОВАТЬ: Вот пример проблемы:

s = new Stack();
s2 = s
...
s = s.push(x);
assert(s == s2); // this will fail

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

В стиле функционального программирования (FP) мы часто разрабатываем наши функции, чтобы нам не нужно было синхронизировать эти ссылки.

Рассмотрим этот сценарий: вы создаете стек s, вставляете его в объект Client, затем перемещаете элемент в s и получаете новый стек s2:

s = new Stack()
client = new Client(s)
s2 = s.push(...)

Поскольку s и s2 не синхронизированы (т. Е. Они представляют собой разные стеки), внутри объекта client он по-прежнему видит старую версию стека (s), которую вы не используете. не хочу Вот код Client:

class Client {
    private Stack stack;
    // other properties
    public Client(Stack stack) { this.stack = stack; }
    public SomeType foo(/*some parameters*/) {
        // access this.stack
    }
}

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

class Client {
    // some properties
    public SomeType foo(Stack stack, /*some parameters*/) {
        // access stack
    }
}

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

Но у FP есть концепция, которая может смягчить эту боль: так называемое частичное применение . Если у вас уже есть стек s, вы можете написать client.foo(s), чтобы получить «обновленную» версию foo, для которой не требуется стек, а требуется только другой some parameters. Затем вы можете передать эту обновленную функцию foo получателю, который не поддерживает ни одного стека.

Тем не менее, стоит упомянуть, что есть люди, которые уверяют, что эта боль может быть действительно полезной. Например, Скотт Влашин в своей статье Функциональные подходы к внедрению зависимости :

Недостатком, конечно, является то, что теперь есть пять дополнительных параметров для функции, что выглядит болезненно. (Конечно, эквивалентный метод в версии OO также имел эти пять зависимостей, но они были неявными).

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

Также у Марка Симанна - автора книги Внедрение зависимостей - была интересная серия по Отклонение зависимостей .

Если вы не можете испытать эту боль, просто сломайте CQS и вернитесь к традиционной реализации стека. В конце концов, если функция (например, pop / dequeue) хорошо известна и знает, что она одновременно что-то возвращает и изменяет свои внутренние данные, нарушение CQS не так уж и плохо.

Даже в этом случае некоторые языки FP предлагают механизм передачи сообщений, позволяющий реализовать изменяемый стек таким образом, чтобы вы не писали данные, изменяющие код (например, код, использующий символ назначения). MailboxProcessor в F # - такой механизм.

Надеюсь, это поможет:)

0 голосов
/ 10 ноября 2018

Именно из-за дизайна функции вам необходимо вернуть состояние, которое отражает контекст.

Это позволяет вам различать успех и неудачу, а также потенциально другую информацию, если вы добавите bool с DequeueResult в следующем минимальном коде участника.

Let Dequeue = функция bool (Результат)

Если голова == Нулевой возврат ... Вернуть true

Что-то более подходящее для CQS:

Let Dequeue = function Node () Возврат головы

Но потребовалось бы, чтобы у Head было специальное значение Node.Null, чтобы попытаться различить неудачи и раздоры.

Возвращение DequeueResult может быть лучше, если вы можете указать в результате больше о сбое.

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