Збереження об'єкта за допомогою власного методу чи через інший клас?


38

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

Що рекомендується відповідно до парадигми OOD?

Наприклад

Class Student
{
    public string Name {set; get;}
    ....
    public bool Save()
    {
        SqlConnection con = ...
        // Save the class in the db
    }
    public bool Retrieve()
    {
         // search the db for the student and fill the attributes
    }
    public List<Student> RetrieveAllStudents()
    {
         // this is such a method I have most problem with it 
         // that an object returns an array of objects of its own class!
    }
}

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

Class Student { /* */ }
Class DB {
  public bool AddStudent(Student s)
  {

  }
  public Student RetrieveStudent(Criteria)
  {
  } 
  public List<Student> RetrieveAllStudents()
  {
  }
}

Як щодо їх змішування?

   Class Student
    {
        public string Name {set; get;}
        ....
        public bool Save()
        {
            /// do some business logic!
            db.AddStudent(this);
        }
        public bool Retrieve()
        {
             // build the criteria 
             db.RetrieveStudent(criteria);
             // fill the attributes
        }
    }

3
Ви повинні провести кілька досліджень щодо схеми активних записів, таких як це питання чи це
Ерік Кінг

@gnat, я думав, що запитати, що краще, на основі думки, потім додав "плюси і мінуси", і не маю жодних проблем, щоб їх усунути, але насправді я маю на увазі парадигму OOD, яка рекомендується
Ахмад

3
Правильної відповіді немає. Це залежить від ваших потреб, ваших уподобань, способу використання вашого класу та де ви бачите свій проект. Єдина правильна річ OO - це те, що ви можете зберігати та отримувати їх як об’єкти.
Данк

Відповіді:


34

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

Проста причина відокремлення класу Studentвід "DB" (або StudentRepositoryдотримання більш популярних конвенцій) полягає в тому, що ви можете змінити свої "бізнес-правила", присутні в Studentкласі, не впливаючи на код, що відповідає за стійкість, і навпаки.

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

Об'єднавши правила бізнесу та наполегливість разом, або один клас, як у вашому першому прикладі, або DBзалежність Student, ви поєднуєте дві дуже різні проблеми. Це може виглядати так, як вони належать разом; вони здаються згуртованими, оскільки використовують ті самі дані. Але ось що: згуртованість не може вимірюватися лише тими даними, якими вони поділяються між процедурами, ви також повинні враховувати рівень абстракції, на якому вони існують. Насправді ідеальний тип згуртованості описується як:

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

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

Я рекомендую читати про « Чиста архітектура» , дивитись цю розповідь про Принцип єдиної відповідальності (де використовується дуже подібний приклад), а також переглядати цю розмову про «Чисту архітектуру» . Ці концепції окреслюють причини таких поділів.


11
Ах, але Studentклас слід правильно капсулювати, так? Як тоді зовнішній клас може керувати стійкістю приватної держави? Інкапсуляція має бути збалансована проти SoC та SRP, але просто обирати інший без ретельного зважування компромісів, мабуть, неправильно. Можливим рішенням цієї проблеми є використання приватних пакетів доступу для використання постійного коду.
амон

1
@amon Я погоджуюся, що це не так просто, але я вважаю, що це питання сполучення, а не інкапсуляції. Наприклад, ви можете зробити клас "Студент" абстрактним, з абстрактними методами для будь-яких даних, необхідних бізнесу, які потім реалізуються репозиторієм. Таким чином, структура даних БД повністю прихована за реалізацією сховища, тому вона навіть інкапсульована від класу Student. Але, в той же час, сховище глибоко пов'язане з класом Стьюдента - якщо будь-які абстрактні методи будуть змінені, реалізація також повинна змінитися. Вся справа в компромісах. :)
MichelHenrich

4
Твердження, що SRP робить програми легшими в обслуговуванні, в кращому випадку сумнівне. Проблема, яку SRP створює, полягає в тому, що замість того, щоб мати колекцію добре названих класів, де назва розповідає все, що клас може і чого не може зробити (іншими словами, легко зрозуміти, що призводить до простоти в обслуговуванні), ви в кінцевому підсумку стаєте сотнями / тисячі класів, яким людям потрібно було використати тезаурус, щоб вибрати унікальне ім'я, багато імен схожі, але не зовсім, і сортування всього того, що плітка є справою. IOW, зовсім не піддається ремонту, IMO.
Данк

3
@ Будь ласка, ви говорите так, ніби класи були єдиним доступним модулем модуляризації. Я бачив і писав багато проектів, що слідують за SRP, і я погоджуюся, що ваш приклад трапляється, але тільки в тому випадку, якщо ви не правильно використовуєте пакунки та бібліотеки. Якщо ви розділите обов'язки на пакети, оскільки контекст мається на увазі під ним, ви можете знову називати класи знову. Тоді для підтримки такої системи потрібно лише просвердлити правильний пакет, перш ніж шукати клас, який потрібно змінити. :)
MichelHenrich

5
Принципи @Dunk OOD можна констатувати так: "В принципі, робити це краще через X, Y і Z". Це не означає, що його слід завжди застосовувати; люди повинні бути прагматичними і зважувати компроміси. У великих проектах переваги такі, що зазвичай переважає крива навчання, про яку ви говорите, - але, звичайно, це не реальність для більшості людей, і тому її не слід застосовувати так сильно. Однак, навіть у більш легких програмах SRP, я думаю, що ми можемо погодитися, що відокремлення наполегливості від правил ведення бізнесу завжди приносить користь, що перевищує його вартість (за винятком, можливо, коду викидання).
МішельГенріх

10

Обидва підходи порушують єдиний принцип відповідальності. Перша версія надає Studentкласу багато обов'язків і пов'язує його з певною технологією доступу до БД. Другий призводить до величезного DBкласу, який буде нести відповідальність не тільки за студентів, а й за будь-який інший об’єкт даних у вашій програмі. EDIT: ваш третій підхід є найгіршим, оскільки він створює циклічну залежність між класом DB та Studentкласом.

Тож якщо ви не збираєтесь писати програму іграшок, не використовуйте жодну з них. Натомість використовуйте інший клас, наприклад, StudentRepositoryдля надання API для завантаження та збереження, припускаючи, що ви збираєтесь реалізувати код CRUD самостійно. Ви також можете скористатися рамкою ORM , яка може зробити для вас важку роботу (і рамки, як правило, виконуватимуть деякі рішення, де потрібно розмістити операції Load and Save).


Дякую, я змінив свою відповідь, як щодо третього підходу? все-таки я думаю, тут справа в тому, щоб зробити чіткі шари
Ахмад

Щось також пропонує мені використовувати StudentRepository замість БД (не знаю, чому! Можливо, знову одна відповідальність), але я все одно не можу отримати, чому для кожного класу різні сховища? якщо це просто рівень доступу до даних, то є ще маса функцій статичної утиліти, і вони можуть бути разом, чи не так? просто, можливо, тоді він став би таким брудним і переповненим класом (вибачте за мою англійську)
Ахмад

1
@Ahmad: є кілька причин, і кілька плюсів і мінусів, які слід врахувати. Це могло б заповнити цілу книгу. Обґрунтування цього зводиться до передбачуваності, ремонтопридатності та довгострокової еволюційності ваших програм, особливо коли вони мають тривалий життєвий цикл.
Doc Brown

Хтось працював над проектом, де відбулася значна зміна технології БД? (без масового переписування?) У мене немає. Якщо так, чи дотримуватися SRP, як тут пропонується, щоб не прив'язуватися до цієї БД, допомогло суттєво?
user949300

@ user949300: Я думаю, я не висловився чітко. Клас студента не прив'язується до конкретної технології БД, він прив'язується до певної технології доступу до БД, тому очікує, що щось подібне до БД SQL для постійності. Повністю не знаючи клас "Студент" про механізм стійкості, це дозволяє значно простіше використовувати клас у різних контекстах. Наприклад, у контексті тестування або в контексті, де об'єкти зберігаються у файлі. І це те, що я робив у минулому дуже часто. Для отримання вигоди від цього не потрібно змінювати весь стек БД вашої системи.
Doc Brown

3

Існує досить багато моделей, які можна використовувати для збереження даних. Існує модель « Одиниця роботи» , є « Репозиторій» , є деякі додаткові шаблони, які можна використовувати, наприклад, віддалений фасад тощо, і так далі.

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

Як бічна примітка: у вашому прикладі RetrieveStudent AddStudent має бути статичним методом (оскільки вони не залежать від примірників).

Ще один спосіб використання в класі методів збереження / завантаження:

class Animal{
    public string Name {get; set;}
    public void Save(){...}
    public static Animal Load(string name){....}
    public static string[] GetAllNames(){...} // if you just want to list them
    public static Animal[] GetAllAnimals(){...} // if you actually need to retrieve them all
}

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

Також особисто дивіться схему одиниці роботи. Коли ви це знайомите, це дійсно добре як у малих, так і у великих випадках. І це підтримується безліччю фреймворків / apis, наприклад, щоб назвати EntityFramework або RavenDB.


2
Про вашу сторону зауваження: Хоча, концептуально, існує лише одна БД під час роботи програми, це не означає, що ви повинні робити методи RetriveStudent і AddStudent статичними. Репозиторії зазвичай робляться з інтерфейсами, щоб дозволити розробникам перемикати реалізацію, не впливаючи на решту програми. Це навіть дозволяє вам, наприклад, поширювати свою програму з підтримкою різних баз даних. Ця ж логіка, звичайно, стосується багатьох інших областей, крім наполегливості, як, наприклад, інтерфейс користувача.
MichelHenrich

Ви, безумовно, праві, я зробив багато припущень, грунтуючись на оригінальному питанні.
Gerino

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

1

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

Але я думаю, що це було б досить винятково.

Швидше, зазвичай, краще дозволити класу керувати своїми даними та його функціональністю (як хороший громадянин ОО) та розширити механізм стійкості до іншого класу чи набору класів.

Іншим варіантом є використання структури стійкості, яка визначає стійкість декларативно (як і примітки), але це все ще екстерналізує стійкість.


-1
  • Визначте метод цього класу, який серіалізує об’єкт, і повертає послідовність байтів, які ви можете помістити в базу даних або файл чи будь-що інше
  • Майте конструктор для цього об'єкта, який приймає таку послідовність байтів як вхід

Щодо RetrieveAllStudents()методу, ваше почуття правильне, воно, ймовірно, неправильне, тому що у вас може бути кілька чітких списків учнів. Чому б просто не залишити список (-ів) поза Studentкласом?


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