Шаблон команды и сложные операции в C#

Я пишу программу на С#, которая должна поддерживать отмену/повтор. Для этой цели я остановился на шаблоне Command; tldr, каждая операция, которая манипулирует состоянием документа, должна выполняться объектом Command, который знает о предыдущем состоянии документа, а также об изменениях, которые необходимо внести, и способен выполнить/отменить себя.

Он отлично работает для простых операций, но теперь у меня есть операция, которая затрагивает сразу несколько частей документа. Точно так же объект Command должен быть достаточно умным, чтобы знать все старое состояние, которое ему необходимо сохранить на случай, если его потребуется отменить.

Проблема заключается в раскрытии всего этого состояния с использованием общедоступных интерфейсов, которое может быть использовано не по назначению, если кто-то попытается вызвать интерфейс напрямую, что может привести к повреждению состояния. Моя интуиция подсказывает мне, что наиболее ООП-способ сделать это — предоставить специализированные классы Command — вместо того, чтобы позволять вам напрямую манипулировать состоянием документа, все, что вы можете сделать, это попросить документ создать объект Command, который имеет доступ к его внутреннее состояние и гарантированно знает достаточно, чтобы правильно поддерживать отмену/возврат.

К сожалению, C# не поддерживает концепцию друзей, поэтому я не могу создать класс Command, имеющий доступ к внутренним компонентам документа. Есть ли способ предоставить закрытые члены класса документа другому классу или есть какой-то другой способ сделать то, что мне нужно, без необходимости раскрывать множество внутренних документов?


person pjohansson    schedule 25.12.2011    source источник
comment
Обычно вы будете использовать шаблон Memento при работе с операциями отмены/возврата, а не шаблон команды. Шаблон команды больше подходит, когда вам нужно что-то выполнить, например, при программировании пульта дистанционного управления каждая кнопка получит команду.   -  person Patrick    schedule 26.12.2011
comment
«К сожалению, C# не поддерживает концепцию друзей» следует читать как «К счастью». Если язык поможет вам выстрелить себе в ногу, это не лучше :)   -  person Boris Yankov    schedule 26.12.2011


Ответы (4)


Это зависит от того, если вы развертываете библиотеку, ваш документ может объявить «внутренние» методы для взаимодействия с его внутренним состоянием, эти методы будут использоваться вашим классом Command, внутренние методы ограничены сборкой, в которой они скомпилированы.

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

person Caian    schedule 25.12.2011
comment
Оба являются полностью нерасширяемыми решениями. - person Ondrej Tucny; 26.12.2011
comment
@OndrejTucny Верно, но не обязательно проблема, если он хочет полной изоляции от пользовательского интерфейса, он не может расширить класс Command из пользовательского кода, теперь, если он хочет расширить шаблон Command на другие невыполнимые классы, повторно используя «Command», тогда я согласен, что ему нужно предоставить интерфейс, который все классы могут использовать для отмены изменений самостоятельно, независимо от типа данных, но это отодвинет действие отмены от команды. - person Caian; 26.12.2011

Во-первых, в C# есть ключевое слово internal, которое объявляет доступность «для друзей», что разрешает публичный доступ из всей сборки.

Во-вторых, доступность «друга» может быть расширена до секунды. сборка с атрибутом сборки InternalsVisibleTo, чтобы вы могли создать второй проект для своих команд, но внутреннее содержимое документа останется внутренним.

В качестве альтернативы, если ваши объекты команд вложены внутри класса документа, они будут иметь доступ ко всем его закрытым членам.

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

person Scott Rippey    schedule 25.12.2011

Вы всегда можете получить доступ к полям и свойствам, частным или нет, посредством отражения (Type.GetField(string, BindingFlags.Private) и друзья).

Может быть, с настраиваемым атрибутом класса (или поля/свойства), чтобы автоматизировать процесс захвата достаточного состояния для каждой команды?

person AKX    schedule 25.12.2011
comment
Вы можете что-то сделать != Вы должны сделать это. - person Boris Yankov; 26.12.2011

Вместо команды, вносящей изменения в разные места документа, можно использовать две фиктивные команды, отмечающие начало и конец многоэтапных операций. Назовем их BeginCommand и EndCommand. Сначала вы помещаете команду BeginCommand в стек отмены, а затем выполняете различные шаги как отдельные команды, каждая из которых вносит изменения только в одном месте документа. Конечно, вы также помещаете их в стек отмены. Наконец, вы нажимаете EndCommand на стек отмены.

При отмене вы проверяете, является ли команда, извлеченная из стека отмены, EndCommand. Если это так, вы продолжаете отмену до тех пор, пока не будет достигнута команда BeginCommand.

Это превращает многошаговую команду в макрокоманду, делегирующую работу другим командам. Сама эта макрокоманда не помещается в стек отмены.

person Olivier Jacot-Descombes    schedule 25.12.2011