У меня есть метод, который обновляет данные сотрудников в базе данных. Employee
Класс неизменен, так что «обновление» средства объекта на самом деле для создания экземпляра нового объекта.
Я хочу, чтобы Update
метод возвращал новый экземпляр Employee
с обновленными данными, но теперь я могу сказать, что ответственность за метод заключается в обновлении данных сотрудника и получении из базы данных нового Employee
объекта , нарушает ли он принцип единой ответственности ?
Сама запись БД обновляется. Затем создается новый объект для представления этой записи.
Compare-and-swap
иtest-and-set
являются фундаментальными операциями в теории многопоточного программирования. Оба они являются методами обновления с возвращаемым типом, и не могли работать иначе. Это нарушает такие понятия, как разделение команд и запросов или принцип единой ответственности? Да, и в этом весь смысл . SRP не является универсально хорошей вещью и на самом деле может быть активно вредным.Ответы:
Как и в случае с любым правилом, я думаю, что здесь важно рассмотреть цель правила, его дух, а не разбираться в том, как именно проанализировать, как было сформулировано правило в каком-то учебнике, и как применить его к этому случаю. Нам не нужно подходить к этому как к адвокатам. Цель правил - помочь нам написать лучшие программы. Это не похоже на то, что целью написания программ является соблюдение правил.
Цель правила единственной ответственности состоит в том, чтобы облегчить понимание и поддержку программ, заставляя каждую функцию выполнять одну самодостаточную, согласованную вещь.
Например, я однажды написал функцию, которую я назвал чем-то вроде «checkOrderStatus», которая определяла, находится ли заказ в ожидании, отправлен, был ли он заказан обратно, и возвращала код, указывающий, какой именно. Затем появился другой программист и изменил эту функцию, чтобы также обновлять имеющееся количество при отправке заказа. Это серьезно нарушило принцип единоличной ответственности. Другой программист, читающий этот код позже, увидит имя функции, увидит, как используется возвращаемое значение, и вполне может никогда не подозревать, что он сделал обновление базы данных. Кто-то, кому нужно получить статус заказа без обновления имеющегося количества, окажется в неловком положении: должен ли он написать новую функцию, которая дублирует часть статуса заказа? Добавить флаг, чтобы сказать, нужно ли делать обновление базы данных? И т.п.
С другой стороны, я бы не стал придираться к тому, что составляет «две вещи». Я только недавно написал функцию, которая отправляет информацию о клиентах из нашей системы в систему нашего клиента. Эта функция выполняет некоторое переформатирование данных в соответствии с их требованиями. Например, у нас есть некоторые поля, которые могут быть нулевыми в нашей базе данных, но они не допускают пустых значений, поэтому мы должны заполнить какой-то фиктивный текст, «не указан», или я забыл точные слова. Возможно, эта функция делает две вещи: переформатировать данные и отправить их. Но я очень сознательно поместил это в одну функцию вместо того, чтобы «переформатировать» и «отправить», потому что я не хочу когда-либо отправлять без переформатирования. Я не хочу, чтобы кто-то писал новый звонок и не понимал, что ему нужно переформатировать, а затем отправить.
В вашем случае обновление базы данных и возврат изображения записанной записи кажутся двумя вещами, которые вполне могут логически и неизбежно сочетаться друг с другом. Я не знаю деталей вашего заявления, поэтому не могу однозначно сказать, хорошая это идея или нет, но это звучит правдоподобно.
Если вы создаете объект в памяти, который содержит все данные для записи, делаете вызовы базы данных, чтобы написать это, а затем возвращаете объект, это имеет большой смысл. У вас есть объект в ваших руках. Почему бы просто не вернуть его? Если вы не вернули объект, как вызывающий абонент получит его? Придется ли ему читать базу данных, чтобы получить объект, который вы только что написали? Это кажется довольно неэффективным. Как он найдет запись? Вы знаете первичный ключ? Если кто-то заявляет, что для функции записи «законно» возвращать первичный ключ, чтобы вы могли перечитать запись, почему бы просто не вернуть всю запись, чтобы вам не пришлось это делать? Какая разница?
С другой стороны, если создание объекта - это куча работы, совершенно отличной от написания записи базы данных, и вызывающая сторона может захотеть сделать запись, но не создать объект, тогда это может быть расточительным. Если вызывающая сторона может захотеть объект, но не выполнить запись, то вам придется предоставить другой способ получить объект, что может означать написание избыточного кода.
Но я думаю, что сценарий 1 более вероятен, поэтому я бы сказал, что, вероятно, нет проблем.
источник
public function sendDataInClientFormat() { formatDataForClient(); sendDataToClient(); } private function formatDataForClient() {...} private function sendDataToClient() {...}
Концепция SRP заключается в том, чтобы запретить модулям выполнять две разные вещи, если их выполнение по отдельности способствует лучшему обслуживанию и сокращению времени спагетти. Как говорит СРП, «одна причина для перемен».
В вашем случае, если у вас была подпрограмма, которая «обновляет и возвращает обновленный объект», вы все равно меняете объект один раз, давая ему 1 причину для изменения. То, что вы возвращаете объект обратно, ни здесь, ни там, вы все еще работаете с этим единственным объектом. Кодекс отвечает за одну и только одну вещь.
SRP на самом деле не о том, чтобы пытаться свести все операции к одному вызову, а о том, с чем вы работаете и как с ним работаете. Так что единственное обновление, которое возвращает обновленный объект, подойдет.
источник
Как всегда, это вопрос степени. SRP должен помешать вам написать метод, который извлекает запись из внешней базы данных, выполняет быстрое преобразование Фурье и обновляет регистр глобальной статистики с результатом. Я думаю, что почти все согласились бы, что эти вещи должны быть сделаны разными методами. Постановка единственной ответственности за каждый метод - просто самый экономичный и запоминающийся способ сделать это.
На другом конце спектра находятся методы, которые дают информацию о состоянии объекта. Типичный
isActive
имеет предоставление этой информации в качестве единственной ответственности. Наверное, все согласны с тем, что это нормально.Теперь некоторые настолько расширяют этот принцип, что считают возвращение флага успеха отличной обязанностью от выполнения действия, об успехе которого сообщается. При очень строгой интерпретации это действительно так, но поскольку альтернатива должна была бы вызывать второй метод для получения статуса успеха, что усложняет вызывающего, многие программисты прекрасно справляются с возвратом кодов успеха из метода с побочными эффектами.
Возврат нового объекта - это еще один шаг на пути к множеству обязанностей. Требование от вызывающего абонента сделать второй вызов для всего объекта несколько более разумно, чем требование второго вызова просто для того, чтобы увидеть, был ли первый успешным или нет. Тем не менее, многие программисты считают, что возвращать результат обновления вполне нормально. Хотя это может быть истолковано как две слегка отличающиеся обязанности, это, конечно, не одно из вопиющих злоупотреблений, которые вдохновляли принцип с самого начала.
источник
Нарушает ли это принцип единой ответственности?
Не обязательно. Во всяком случае, это нарушает принцип разделения команд и запросов .
Ответственность
с неявным пониманием того, что в эту ответственность входит статус операции; например, в случае сбоя операции выдается исключение, в случае успеха возвращается сотрудник обновления и т. д.
Опять же, все это зависит от степени и субъективного суждения.
Так что насчет разделения команд и запросов?
Ну, этот принцип существует, но возвращение результата обновления на самом деле очень распространено.
(1) Java
Set#add(E)
добавляет элемент и возвращает его предыдущее включение в набор.Это более эффективно, чем альтернатива CQS, которая может выполнять два поиска.
(2) Сравнение-и-замена , выборка-и-добавление и проверка-и-набор - это общие примитивы, которые позволяют параллельному программированию существовать. Эти шаблоны часто появляются, от низкоуровневых инструкций ЦП до высокоуровневых параллельных коллекций.
источник
Единственная ответственность касается класса, не нуждающегося в изменении по нескольким причинам.
В качестве примера у сотрудника есть список телефонных номеров. Когда меняется способ обработки телефонных номеров (вы можете добавить коды вызова страны), это вообще не должно изменять класс сотрудников.
У меня не было бы класса служащих, которому нужно было бы знать, как он сохраняет себя в базе данных, потому что тогда он изменился бы для изменений в сотрудниках и изменений в том, как хранятся данные.
Похоже, не должно быть метода CalculateSalary в классе сотрудников. С зарплатой все в порядке, но расчет налога и т. Д. Должен выполняться где-то еще.
Но то, что метод Update возвращает то, что он только что обновил, прекрасно.
источник
Wrt. конкретный случай:
Это кажется
Update
метод принимает существующий вEmployee
качестве первого параметра для идентификации (?) Существующего сотрудника и набора параметров для изменения на этом сотруднике.Я действительно думаю , что это может быть сделано чище. Я вижу два случая:
(а)
Employee
действительно содержит базу данных / уникальный идентификатор, по которому его всегда можно идентифицировать в базе данных. (То есть вам не нужно устанавливать все значения записи, чтобы найти ее в БД.В этом случае я бы предпочел
void Update(Employee obj)
метод, который просто находит существующую запись по ID, а затем обновляет поля из переданного объекта. Или может бытьvoid Update(ID, EmployeeBuilder values)
Вариант этого, который я нашел полезным, состоит в том, чтобы иметь только
void Put(Employee obj)
метод, который вставляет или обновляет, в зависимости от того, существует ли запись (по идентификатору).(б) Полная существующая запись необходима для DB поиска, в этом случае это может еще больше смысла иметь:
void Update(Employee existing, Employee newData)
.Насколько я могу видеть здесь, я бы действительно сказал, что ответственность за создание нового объекта (или значения подобъекта) для хранения и фактического его хранения является ортогональной, поэтому я бы разделил их.
Упомянутые параллельные требования в других ответах (атомарный набор-извлечение / сравнение-своп и т. Д.) Не были проблемой в коде БД, над которым я работал до сих пор. Когда я говорю с БД, я думаю, что это следует обрабатывать на уровне транзакций, а не на уровне отдельных операторов. (Это не значит, что не может быть дизайна, в котором «атомарность»
Employee[existing data] Update(ID, Employee newData)
не имеет смысла, но с доступом к БД это не то, что я обычно вижу.)источник
Пока что все здесь говорят о занятиях. Но подумайте об этом с точки зрения интерфейса.
Если интерфейсный метод объявляет тип возвращаемого значения и / или неявное обещание о возвращаемом значении, то каждая реализация должна возвращать обновленный объект.
Так что вы можете спросить:
Также подумайте о фиктивных объектах для юнит-тестов. Очевидно, что будет проще издеваться без возвращаемого значения. А зависимые компоненты легче тестировать, если их внедренные зависимости имеют более простые интерфейсы.
На основании этих соображений вы можете принять решение.
И если вы пожалеете об этом позже, вы все равно можете ввести второй интерфейс с адаптерами между ними. Конечно, такие дополнительные интерфейсы увеличивают сложность композиции, поэтому это все компромисс.
источник
Ваш подход в порядке. Неизменность является сильным утверждением. Единственное, что я хотел бы спросить: есть ли другое место, где вы строите объект. Если бы ваш объект не был неизменным, вам пришлось бы отвечать на дополнительные вопросы, потому что введено «State». И изменение состояния объекта может происходить по разным причинам. Тогда вы должны знать свои дела, и они не должны быть избыточными или разделенными.
источник