Допустим, у меня есть процедура, которая делает вещи :
void doStuff(initalParams) {
...
}
Теперь я обнаружил, что «делать вещи» - довольно сложная операция. Процедура становится большой, я делю ее на несколько более мелких процедур, и вскоре я понимаю, что наличие некоторого состояния было бы полезно при выполнении каких-либо задач, поэтому мне нужно передавать меньше параметров между небольшими процедурами. Итак, я выделяю его в свой класс:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
И тогда я называю это так:
new StuffDoer().Start(initialParams);
или вот так:
new StuffDoer(initialParams).Start();
И это то, что кажется неправильным. При использовании .NET или Java API я никогда не звоню new SomeApiClass().Start(...);
, что заставляет меня подозревать, что я делаю это неправильно. Конечно, я мог бы сделать конструктор StuffDoer закрытым и добавить статический вспомогательный метод:
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
Но тогда у меня был бы класс, внешний интерфейс которого состоит только из одного статического метода, который также кажется странным.
Отсюда мой вопрос: есть ли устоявшаяся модель для этого типа классов,
- иметь только одну точку входа и
- не имеют «внешне распознаваемого» состояния, т. е. состояние экземпляра требуется только во время выполнения этой одной точки входа?
bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");
когда все, о чем я заботился, - это конкретная часть информации, а методы расширения LINQ недоступны. Работает нормально, и вписывается вif()
условия без необходимости прыгать через обручи. Конечно, вам нужен язык для сбора мусора, если вы пишете такой код.Ответы:
Существует шаблон с именем Method Object, в котором вы выделяете один большой метод с большим количеством временных переменных / аргументов в отдельный класс. Вы делаете это, а не просто извлекаете части метода в отдельные методы, потому что им нужен доступ к локальному состоянию (параметры и временные переменные), и вы не можете совместно использовать локальное состояние, используя переменные экземпляра, потому что они будут локальными для этого метода (и методы, извлеченные из него) только и не будут использоваться остальной частью объекта.
Таким образом, вместо этого метод становится своим собственным классом, параметры и временные переменные становятся переменными экземпляра этого нового класса, а затем метод разбивается на более мелкие методы нового класса. Результирующий класс обычно имеет только один публичный метод экземпляра, который выполняет задачу, которую класс инкапсулирует.
источник
Я бы сказал, что рассматриваемый класс (с использованием единой точки входа) хорошо спроектирован. Это легко использовать и расширять. Довольно твердый.
То же самое для
no "externally recognizable" state
. Это хорошая инкапсуляция.Я не вижу никаких проблем с кодом, как:
источник
doer
снова, это сводится кvar result = new StuffDoer(initialParams).Calculate(extraParams);
.StringComparer
класс , который вы используете , какnew StringComparer().Compare( "bleh", "blah" )
хорошо продуманы? А как насчет создания государства, когда в этом нет нужды? Хорошо, поведение хорошо инкапсулировано (по крайней мере, для пользователя класса, подробнее об этом в моем ответе ), но это не делает его «хорошим дизайном» по умолчанию. Что касается вашего примера «результат деятельности». Это имеет смысл, только если вы когда-либо будете использоватьdoer
отдельно отresult
.one reason to change
. Различие может показаться неуловимым. Вы должны понять, что это значит. Он никогда не создаст один метод классовС точки зрения твердых принципов ответ jgauffin имеет смысл. Однако не стоит забывать об общих принципах проектирования, например, сокрытии информации .
Я вижу несколько проблем с данным подходом:
Некоторые связанные ссылки
Возможно, главный аргумент заключается в том, как далеко простирается принцип единой ответственности . «Если вы доведите это до крайности и соберете классы, у которых есть одна причина существования, у вас может получиться только один метод на класс. Это приведет к большому разрастанию классов даже для самых простых процессов, в результате чего система будет трудно понять и трудно изменить ".
Другая важная ссылка, относящаяся к этой теме: «Разделите ваши программы на методы, которые выполняют одну идентифицируемую задачу. Сохраните все операции в методе на одном уровне абстракции » - Кент Бек Кей здесь "тот же уровень абстракции". Это не означает «одна вещь», как это часто интерпретируется. Этот уровень абстракции полностью зависит от контекста, для которого вы разрабатываете.
Так каков правильный подход?
Не зная вашего конкретного варианта использования, трудно сказать. Есть сценарий, в котором я иногда (не часто) использую подобный подход. Когда я хочу обработать набор данных, не желая делать эту функциональность доступной для всей области видимости класса. Я написал в блоге об этом, как лямбда может еще больше улучшить инкапсуляцию . Я также начал вопрос по теме здесь на программистов . Ниже приведен последний пример использования этой техники.
Поскольку сам «локальный» код внутри
ForEach
не используется повторно нигде, я могу просто сохранить его в точном месте, где он имеет значение. Изложение кода таким образом, что код, опирающийся друг на друга, сильно сгруппирован, делает его более читабельным, на мой взгляд.Возможные альтернативы
источник
Я бы сказал, что это довольно хорошо, но ваш API должен быть статическим методом для статического класса, поскольку этого ожидает пользователь. Тот факт, что статический метод использует
new
для создания вашего вспомогательного объекта и выполнения работы, является деталью реализации, которая должна быть скрыта от того, кто ее вызывает.источник
Существует проблема с невежественными разработчиками, которые могут использовать общий экземпляр и использовать его
Таким образом, для этого требуется явная документация, что он не является поточно-ориентированным и если он легкий (его создание быстрое, не связывает ни внешние ресурсы, ни много памяти), то можно создать экземпляр на лету.
Сокрытие этого в статическом методе помогает в этом, потому что тогда повторное использование экземпляра никогда не произойдет.
Если он имеет дорогостоящую инициализацию, может быть (не всегда) полезно подготовить его инициализацию один раз и использовать его несколько раз, создав другой класс, который будет содержать только состояние и сделает его клонируемым. Инициализация создаст состояние, которое будет храниться в
doer
.start()
будет клонировать его и передать его внутренним методам.Это также учитывает другие вещи, такие как сохранение состояния частичного выполнения. (Если на это уйдет много времени, а внешние факторы, например, сбой в электроснабжении, могут прервать выполнение.) Но эти дополнительные причудливые вещи обычно не нужны, поэтому не стоят этого.
источник
Помимо моего более детального, индивидуального ответа , я считаю, что следующее наблюдение заслуживает отдельного ответа.
Есть признаки того, что вы можете следовать анти-паттерну Полтергейста .
источник
Есть несколько шаблонов, которые можно найти в каталоге Мартина Фаулера из EAA ;
источник