Я поддерживаю программное обеспечение CAD/CAM для металлорежущего станка. Поэтому у меня есть некоторый опыт в этом вопросе.
Когда мы впервые конвертировали наше программное обеспечение (оно было впервые выпущено в 1985 году!) Объектно-ориентированному дизайну, я сделал именно то, что вам не нравится. Объекты и интерфейсы имели Draw, WriteToFile и т. Д. Обнаружение и чтение о шаблонах проектирования в середине конверсии помогло много, но по-прежнему было много неприятных запахов кода.
В конце концов я понял, что ни один из этих типов операций не был предметом озабоченности объекта. Но, скорее, различные подсистемы, необходимые для выполнения различных операций. Я обработал это, используя то, что теперь называется командным объектом Passive View, и четко определенный интерфейс между слоями программного обеспечения.
Наша программа построена в основном так
- Формы, реализующих различные формы интерфейса. Эти формы - это предмет, передающий события на уровень пользовательского интерфейса.
- Пользовательский интерфейс, который получает события и управляет формами через интерфейс формы.
- UI Layer выполнит команды, которые все реализуют интерфейс команд
- Объект пользовательского интерфейса имеет собственные интерфейсы, с которыми может взаимодействовать команда.
- Команды получают необходимую информацию, обрабатывают ее, манипулируют моделью и затем сообщают об объектах пользовательского интерфейса, которые затем делают что-то нужное с формами.
- И, наконец, модели, которые содержат различные объекты нашей системы. Подобно форматным программам, линиям резки, режущему столу и металлическим листам.
Так что чертеж обрабатывается в слое пользовательского интерфейса. У нас есть разные программы для разных машин. Поэтому, хотя все наше программное обеспечение использует одну и ту же модель и повторно используют многие из тех же команд. Они обрабатывают такие вещи, как рисование очень разных. Например, стол для резки является разным для машины маршрутизатора по сравнению с машиной с использованием плазменного факела, несмотря на то, что оба они представляют собой гигантский плоский стол X-Y. Это потому, что, как и машины, две машины построены по-разному, так что есть визуальная разница с клиентом.
Что касается форм, что мы делаем это следующим образом
У нас есть программа, которые производят формы режущих пути через введенные параметры. Путь резания знает, какая программа формы создана. Однако путь резания не является формой. Это просто информация, необходимая для рисования на экране и для резки фигуры. Одной из причин такого дизайна является то, что пути резания могут быть созданы без программы формы, когда они импортируются из внешнего приложения.
Эта конструкция позволяет отделить дизайн пути резания от конструкции формы, которая не всегда одно и то же. В вашем случае, вероятно, все, что вам нужно для упаковки, - это информация, необходимая для рисования фигуры.
Каждая программа формы имеет несколько видов, реализующих интерфейс IShapeView. Через интерфейс IShapeView формальная программа может рассказать об общей форме формы, которую мы имеем, чтобы настроить себя, чтобы показать параметры этой формы. Форма формы формы реализует интерфейс IShapeForm и регистрируется с объектом ShapeScreen. Объект ShapeScreen регистрируется с нашим объектом приложения. Представления формы используют любой вид экрана, который регистрируется с приложением.
Причина появления нескольких видов, которые у нас есть у клиентов, которые любят вводить фигуры по-разному. Наша клиентская база разделяется пополам между теми, кто любит вводить параметры формы в виде таблицы, и тем, кто хочет войти с графическим представлением формы перед ними. Нам также нужно получить доступ к параметрам порой через минимальный диалог, а не весь экран ввода полной формы. Следовательно, множественные взгляды.
Команды, управляющие фигурами, попадают в одну из двух категорий. Либо они манипулируют траекторией резания, либо манипулируют параметрами формы. Чтобы манипулировать параметрами формы, мы либо возвращаем их обратно на экран ввода фигуры, либо показываем минимальный диалог. Пересчитайте фигуру и отобразите ее в том же месте.
Для пути резания мы объединили каждую операцию в отдельный объект команды. Например у нас есть объекты команд
ResizePath RotatePath MovePath SplitPath и так далее.
Когда нам нужно добавить новую функциональность, мы добавим еще один объект команды, найдем меню, клавиатуру или слот кнопки панели инструментов на правом экране пользовательского интерфейса и настроим объект пользовательского интерфейса, чтобы выполнить эту команду.
Например
CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath
или
CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath
В обоих случаях объект MirrorPath Команда в настоящее время, связанных с требуемым элементом пользовательского интерфейса. В методе выполнения MirrorPath - это весь код, необходимый для зеркального отображения пути на определенной оси. Вероятно, команда будет иметь собственный диалог или использовать один из элементов пользовательского интерфейса, чтобы спросить пользователя, какая ось должна зеркалировать. Ничто из этого не делает посетителя или не добавляет метод пути.
Вы обнаружите, что многие могут быть обработаны посредством связывания действий с командами. Однако я предупреждаю, что это не черная или белая ситуация. Вы по-прежнему обнаружите, что некоторые вещи работают лучше, чем методы исходного объекта. В опыте я обнаружил, что, возможно, 80% того, что я использовал в методах, могли быть перемещены в команду. Последние 20% просто работают лучше на объекте.
Теперь некоторым может не понравиться это, потому что он, кажется, нарушает инкапсуляции. От поддержания нашего программного обеспечения как объектно-ориентированной системы за последнее десятилетие я должен сказать, что важная долгосрочная вещь, которую вы можете сделать, - это четко документировать взаимодействия между различными уровнями вашего программного обеспечения и между различными объектами.
Объединение действий в объекты Command помогает с этой целью лучше, чем рабская преданность идеалам инкапсуляции.Все, что нужно сделать для Mirror the Path, связано с объектом Command Mirror Path.
Просто примечание, так как маловероятно, что вы можете изменить свой язык: существуют языки, которые поддерживают несколько общих функций отправки. – Svante
Большой вопрос. Я просто хотел предоставить контрапункт. Иногда ваша проблема с (5) может быть хорошей. Я использую шаблон посетителя, когда у меня есть некоторые функции, которые необходимо обновить при определении нового подтипа IShape. У меня есть интерфейс IShapeVisitor, который определяет, какие методы необходимы. Пока этот интерфейс обновляется новым подтипом, мой код не создается до тех пор, пока не будет обновлена критическая функциональность. В некоторых ситуациях это может быть очень полезно. – oillio
Я согласен с @oillio, но тогда вы также можете применить его как абстрактный метод на IShape. То, что шаблон Visitor покупает вас на чистом языке OO, - это локальность функции (по сравнению с локальностью класса) и тем самым разделение проблем.В любом случае, использование шаблона посетителя должно явно нарушаться во время компиляции, когда вы хотите принудительно добавить новые типы, которые необходимо тщательно пересмотреть! –