Чи додавання типу повернення до методу оновлення порушує "Принцип єдиної відповідальності"?


37

У мене є метод, який оновлює дані співробітників у базі даних. EmployeeКлас незмінний, так що «оновлення» кошти об'єкта на насправді для створення екземпляра нового об'єкта.

Я хочу, щоб Updateметод повертав новий екземпляр Employeeіз оновленими даними, але оскільки тепер я можу сказати, що відповідальність методу полягає вEmployee тому, щоб оновити дані працівника та отримати з бази даних новий об’єкт , чи порушує він єдиний принцип відповідальності ?

Сам запис БД оновлюється. Потім новий об'єкт призначений для представлення цього запису.


5
Маленький псевдо-код може пройти довгий шлях: чи справді створена нова запис БД, чи сама оновлена ​​запис БД, але в коді "клієнт" створюється новий об'єкт, тому що клас моделюється як незмінний?
Мартін Ба

Крім того, що запитав Мартін Бра, чому ви повертаєте екземпляр співробітника, коли ви оновлюєте базу даних? Значення екземпляра Employee не змінилися в методі оновлення, тому навіщо його повертати (абоненти вже мають доступ до екземпляра ...). Або метод оновлення також отримує (потенційно різні) значення з бази даних?
Торсал

1
@Thorsal: якщо ваші суб'єкти даних незмінні, повернення екземпляра з оновленими даними є значною мірою SOP, оскільки в іншому випадку вам доведеться робити інстанціювання змінених даних самостійно.
mikołak

21
Compare-and-swapі test-and-setє фундаментальними операціями в теорії багатопотокового програмування. Вони обидва є методами оновлення з типом повернення, і не можуть працювати інакше. Це порушує такі поняття, як розділення команд-запитів або принцип єдиної відповідальності? Так, і в цьому вся суть . SRP - це не загальнодобрана річ, і насправді може бути активно шкідливою.
MSalters

3
@MSalters: Саме так. Розділення команд / запитів говорить про те, що повинно бути можливим видавати запити, які можна визнати ідентичними, і видавати команди без необхідності чекати відповіді, але атомне читання-зміна-запис має бути визнане третьою категорією операції.
supercat

Відповіді:


16

Як і будь-яке правило, я думаю, що тут важливо враховувати мету правила, дух і не заплутуватися в аналізі того, як саме це слово було сформульовано в деяких підручниках, і як застосувати це до цього випадку. Нам не потрібно підходити до цього, як до юристів. Мета правил - допомогти нам написати кращі програми. Це не так, як метою написання програм є дотримання правил.

Мета правила про єдину відповідальність - полегшити розуміння та підтримку програм, змусивши кожну функцію виконувати одну самостійну, цілісну річ.

Наприклад, я одного разу написав функцію, яку я назвав чимось на кшталт "checkOrderStatus", який визначав, чи є замовлення відкладеним, відправленим, зворотним замовленням і будь-яким іншим, і повертає код із зазначенням якого. Потім прийшов інший програміст і модифікував цю функцію, щоб також оновити кількість на руках, коли замовлення було відправлено. Це суттєво порушило принцип єдиної відповідальності. Інший програміст, який читає цей код пізніше, побачить ім'я функції, побачить, як використовується повернене значення, і, можливо, ніколи не підозрює, що він здійснив оновлення бази даних. Хтось, кому потрібно було отримати статус замовлення без оновлення кількості на руках, опинився б у незручному положенні: чи повинен він написати нову функцію, що дублює частину статусу замовлення? Додайте прапор, щоб сказати, чи потрібно робити оновлення db? І т.д.

З іншого боку, я б не зрозумів, що становить "дві речі". Я нещодавно написав функцію, яка надсилає інформацію про клієнтів з нашої системи в систему нашого клієнта. Ця функція виконує певне переформатування даних, щоб відповідати їх вимогам. Наприклад, у нас є деякі поля, які можуть бути нульовими в нашій базі даних, але вони не дозволяють нулю, тому нам доведеться заповнити якийсь манекен текст, "не вказаний", або я забуваю точні слова. Можливо, ця функція виконує дві речі: переформатувати дані та надіслати їх. Але я дуже свідомо ставлю це в єдину функцію, а не в тому, щоб "переформатувати" і "відправити", тому що я не хочу ніколи, ніколи надсилати без переформатування. Я не хочу, щоб хтось писав новий дзвінок і не усвідомлював, що він повинен зателефонувати переформатувати і потім відправити.

У вашому випадку оновлення бази даних та повернення зображення записаної записи здаються двома речами, які цілком можуть поєднуватися логічно та неминуче. Я не знаю деталей вашої заявки, тому я не можу остаточно сказати, хороша це ідея чи ні, але це звучить правдоподібно.

Якщо ви створюєте об'єкт в пам'яті, який зберігає всі дані для запису, виконуючи дзвінки бази даних, щоб записати це, а потім повернути об'єкт, це має багато сенсу. У вас предмет в руках. Чому б просто не повернути його назад? Якщо ви не повернули об'єкт, як би його викликав? Чи доведеться йому читати базу даних, щоб отримати об’єкт, який ви тільки що написали? Це здається досить неефективним. Як він знайде запис? Чи знаєте ви первинний ключ? Якщо хтось заявляє, що "законно", щоб функція запису повертала первинний ключ, щоб ви могли перечитати запис, чому б просто не повернути весь запис, щоб цього не потрібно? Яка різниця?

З іншого боку, якщо створення об'єкта - це велика робота, що відрізняється від запису бази даних, і абонент може захотіти зробити запис, але не створити об'єкт, то це може бути марним. Якщо абонент може захотіти об'єкт, але не зробити запис, то вам доведеться надати інший спосіб отримати об'єкт, що може означати написання зайвого коду.

Але я думаю, що сценарій 1 є більш імовірним, тому я б сказав, мабуть, не проблема.


Дякую за всі відповіді, але ця справді мені найбільше допомогла.
Сіпо

Якщо ви не хочете відправляти без переформатування, і немає можливості використовувати для переформатування поряд із надсиланням даних пізніше, то це одне, а не два.
Joker_vD

public function sendDataInClientFormat() { formatDataForClient(); sendDataToClient(); } private function formatDataForClient() {...} private function sendDataToClient() {...}
CJ Dennis

@CJDennis Sure. Насправді так я і зробив: Функція для форматування, функція для фактичного надсилання, і були дві інші функції, до яких я сюди не потрапляю. Потім одна функція верхнього рівня, щоб викликати їх у відповідному порядку. Ви можете сказати, що "форматувати та надсилати" - це одна логічна операція, і таким чином належним чином можна поєднувати одну функцію. Якщо ви наполягаєте на тому, що це два, добре, все-таки раціональна річ - це одна функція верхнього рівня, яка все це виконує.
Джей

67

Концепція SRP полягає у тому, щоб зупинити модулі, які роблять 2 різні речі, коли робити їх окремо, сприяє кращому обслуговуванню та меншій кількості спагетті в часі. Як говорить СРП, "одна причина для змін".

У вашому випадку, якщо у вас була звичайна програма, яка "оновлює і повертає оновлений об'єкт", ви все одно змінюєте об'єкт один раз - даючи йому 1 причину для зміни. Якщо ви повернете об’єкт прямо назад, ні тут, ні там, ви все ще працюєте над цим єдиним об'єктом. Кодекс несе відповідальність за одну, і лише одну річ.

SRP насправді не в тому, щоб намагатися скоротити всі операції до одного виклику, а щоб зменшити те, що ви працюєте, і як ви працюєте над ним. Отже, єдине оновлення, яке повертає оновлений об’єкт, є нормальним.


7
Крім того, справа не в тому, щоб "намагатися скоротити всі операції до одного дзвінка", а щоб зменшити кількість різних випадків, про які ви повинні задуматися, використовуючи відповідний предмет.
jpmc26

46

Як завжди, це питання ступеня. SRP повинен заважати вам писати метод, який витягує запис із зовнішньої бази даних, здійснює швидку трансформацію Фур'є на ній та оновлює глобальний реєстр статистики з результатом. Я думаю, що майже кожен погодився б, що це слід робити різними методами. Постулювання єдиної відповідальності за кожен метод - це найекономічніший і незабутній спосіб зробити це.

На іншому кінці спектру - це методи, які дають інформацію про стан об'єкта. Як правило isActive, надання цієї інформації є єдиною відповідальністю. Напевно, всі згодні, що це нормально.

Зараз деякі поширюють принцип настільки, що вважають повернення прапора успіху іншою відповідальністю від виконання дії, про успіх якої повідомляється. За надзвичайно суворої інтерпретації це правда, але оскільки альтернативою буде необхідність викликати другий метод отримання статусу успіху, що ускладнює виклику, багато програмістів прекрасно повертаються коди успіху з методу з побічними ефектами.

Повернення нового об’єкта - це крок далі на шляху до численних обов'язків. Вимагати того, щоб абонент здійснив другий дзвінок по всьому об'єкту, дещо розумніший, ніж вимагати другого виклику, просто щоб побачити, чи вдався перший чи ні. Тим не менш, багато програмістів вважають повернення результату оновлення абсолютно чудовим. Хоча це може бути розтлумачено як дві дещо різні обов'язки, це, безумовно, не одне з кричущих зловживань, які надихнули цей принцип на початок.


15
Якщо вам доведеться зателефонувати за другим методом, щоб побачити, чи вдався перший, чи не потрібно вам закликати третій метод, щоб отримати результат другого? Тоді якщо ви насправді бажаєте результату, ну ...

3
Можу додати, що надто ревне застосування СРП призводить до безлічі малих класів, що є тягарем саме по собі. Наскільки велике навантаження залежить від вашого оточення, компілятора, IDE / допоміжних інструментів тощо
Erik Alapää

8

Чи порушує це принцип єдиної відповідальності?

Не обов'язково. Якщо що, це порушує принцип розділення команд-запитів .

Відповідальність така

оновити дані працівника

з неявним розумінням того, що ця відповідальність включає статус операції; наприклад, якщо операція не вдалася, викидається виняток, якщо вона успішна, працівник оновлення повертається тощо.

Знову ж таки, це все питання ступеня і суб'єктивного судження.


То як щодо розділення команд-запитів?

Ну, цей принцип існує, але повернення результату оновлення насправді є дуже поширеним явищем.

(1) Java Set#add(E)додає елемент і повертає його попереднє включення до набору.

if (visited.add(nodeToVisit)) {
    // perform operation once
}

Це більш ефективно, ніж альтернатива CQS, яка, можливо, повинна виконати два пошуку.

if (!visited.contains(nodeToVisit)) {
    visited.add(nodeToVisit);
    // perform operation once
}

(2) Порівнювати і замінювати , замінювати, додавати та тестувати і встановлювати - це звичайні примітиви, які дозволяють існувати одночасне програмування. Ці зразки з'являються часто, від інструкцій з низьким рівнем процесора до одночасних колекцій високого рівня.


2

Єдина відповідальність полягає в тому, що клас не потребує змін з декількох причин.

Як приклад, працівник має список номерів телефонів. Коли спосіб обробки телефонних номерів змінюється (ви можете додати коди виклику країн), які взагалі не повинні змінювати клас працівника.

Я б не потребував того, щоб клас працівника знав, як він зберігає себе в базі даних, тому що це зміниться для змін у співробітниках та змін у тому, як зберігаються дані.

Подібним не повинно бути методу CalculateSalary у класі працівника. Майно зарплати нормально, але нарахування податку тощо потрібно робити десь в іншому місці.

Але те, що метод "Оновлення" повертає те, що тільки що оновили, це добре.


2

Wrt конкретний випадок:

Employee Update(Employee, name, password, etc) (фактично використовую Builder, оскільки у мене дуже багато параметрів).

Це здаєтьсяUpdate метод приймає існуючий в Employeeякості першого параметра для ідентифікації (?) Існуючого співробітника і набору параметрів для зміни на цьому співробітника.

Я дійсно думаю , що це може бути зроблено чистіше. Я бачу два випадки:

(а) Employee дійсно містить базу даних / унікальний ідентифікатор, за допомогою якого він завжди може бути ідентифікований у базі даних. (Тобто вам не потрібно цілих значень запису, встановлених, щоб знайти його в БД.

У цьому випадку я віддаю перевагу void Update(Employee obj)методу, який просто знаходить існуючу запис за ідентифікатором і потім оновлює поля від переданого об'єкта. А може, анvoid Update(ID, EmployeeBuilder values)

Варіантом цього, який я вважав корисним, є лише void Put(Employee obj)метод, який вставляє або оновлює, залежно від того, чи існує запис (за ідентифікацією).

(Б) Повна існуюча запис необхідна для DB пошуку, в цьому випадку це може ще більше сенсу мати: void Update(Employee existing, Employee newData).

Наскільки я тут бачу, я б справді сказав, що відповідальність за створення нового об'єкта (або значень суб-об’єкта) для його зберігання та фактичного зберігання є ортогональними, тому я би розділив їх.

Згадані паралельні вимоги в інших відповідях (атомне встановлення та вилучення / порівняння-заміна тощо) не були проблемою в коді БД, над яким я працював до цих пір. Під час розмови з БД, я думаю, що це слід обробляти на рівні транзакцій зазвичай, а не на рівні окремої заяви. (Це не означає, що може бути не дизайн, де я "атомний" Employee[existing data] Update(ID, Employee newData)не міг би мати сенсу, але з доступом до БД це не те, що я зазвичай бачу.)


1

Поки всі тут говорять про заняття. Але подумайте про це з точки зору інтерфейсу.

Якщо метод інтерфейсу оголошує тип повернення та / або неявну обіцянку про значення повернення, то для кожної реалізації потрібно повернути оновлений об'єкт.

Тож ви можете запитати:

  • Чи можете ви придумати можливі реалізації, які не хочуть турбувати повернення нового об’єкта?
  • Чи можете ви придумати компоненти, які залежать від інтерфейсу (ін'єктуючи екземпляр), які не потребують оновленого співробітника в якості повернення?

Також подумайте про макетні об’єкти для одиничних тестів. Очевидно, буде легше знущатися без поверненого значення. І залежні компоненти легше перевірити, якщо їх введені залежності мають простіші інтерфейси.

Виходячи з цих міркувань, ви можете прийняти рішення.

І якщо ви пошкодуєте про це пізніше, ви все одно можете ввести другий інтерфейс з адаптерами між ними. Звичайно, такі додаткові інтерфейси збільшують складну композицію, тому це все є компромісом.


0

Ваш підхід прекрасний. Незмінюваність - сильне твердження. Єдине, що я хотів би запитати: чи є якесь інше місце, де ви будуєте об’єкт. Якщо ваш об’єкт був непорушним, вам доведеться відповісти на додаткові запитання, оскільки введено "Держава". І зміна стану об’єкта може статися з різних причин. Тоді ви повинні знати ваші випадки, і вони не повинні бути зайвими або розділеними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.