Сохранение объекта с помощью собственного метода или другого класса?

38

Если я хочу сохранить и извлечь объект, должен ли я создать другой класс для его обработки или лучше сделать это в самом классе? Или, может быть, смешивая оба?

Что рекомендуется в соответствии с парадигмой ООД?

Например

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
        }
    }
Ahmad
источник
3
Вам следует изучить шаблон Active Record, например, этот вопрос или этот
Eric King
@gnat, я подумал, что лучше спросить на основе мнений, затем добавить «за и против», и у меня нет проблем с его удалением, но на самом деле я имею в виду рекомендованную парадигму OOD
Ахмад,
3
Там нет правильного ответа. Это зависит от ваших потребностей, ваших предпочтений, от того, как будет использоваться ваш класс, и от того, где вы видите свой проект. Единственная правильная вещь ОО - это то, что вы можете сохранять и извлекать как объекты.
Данк

Ответы:

34

Принцип единой ответственности , разделение интересов и функциональная сплоченность . Если вы прочитаете эти концепции, вы получите ответ: разделите их .

Простая причина отделить Studentкласс от «DB» (или StudentRepository, чтобы следовать более популярным соглашениям), состоит в том, чтобы позволить вам изменить свои «бизнес-правила», присутствующие в Studentклассе, не затрагивая код, отвечающий за постоянство, и наоборот.

Этот вид разделения очень важен не только между бизнес-правилами и постоянством, но и между многими проблемами вашей системы, чтобы позволить вам вносить изменения с минимальным воздействием в несвязанных модулях (минимально, потому что иногда это неизбежно). Это помогает создавать более надежные системы, которые легче поддерживать и которые более надежны при постоянных изменениях.

Смешивая бизнес-правила и постоянство, либо с одним классом, как в первом примере, либо с DBзависимостью Student, вы связываете две совершенно разные задачи. Может показаться, что они принадлежат друг другу; они кажутся связными, потому что используют одни и те же данные. Но вот в чем дело: сплоченность не может быть измерена только данными, которые разделяются между процедурами, вы также должны учитывать уровень абстракции, на котором они существуют. Фактически, идеальный тип сцепления описывается как:

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

И, Studentконечно же , выполнение проверок на некоторое время и их сохранение не образуют «единой четко определенной задачи». Итак, опять же, бизнес-правила и механизмы персистентности - это два совершенно разных аспекта системы, которые по многим принципам хорошего объектно-ориентированного проектирования должны быть разделены.

Я рекомендую прочитать о Чистой архитектуре , посмотреть этот доклад о Принципе единой ответственности (где используется очень похожий пример), а также посмотреть этот доклад о Чистой архитектуре . Эти понятия обрисовывают в общих чертах причины такого разделения.

MichelHenrich
источник
11
Ах, но Studentкласс должен быть правильно инкапсулирован, да? Как тогда внешний класс может управлять сохранением частного государства? Инкапсуляция должна быть сбалансирована с SoC и SRP, но простой выбор одного из двух без тщательного взвешивания компромиссов, вероятно, ошибочен. Возможное решение этой загадки - использование частных средств доступа к пакетам для использования кода персистентности.
Амон
1
@ Amon Я согласен, что это не так просто, но я считаю, что это вопрос связи, а не инкапсуляции. Например, вы можете сделать класс Student абстрактным, с абстрактными методами для любых данных, которые нужны бизнесу, которые затем будут реализованы в хранилище. Таким образом, структура данных БД полностью скрыта за реализацией репозитория, поэтому она даже инкапсулирована из класса Student. Но, в то же время, хранилище тесно связано с классом Student - если какие-либо абстрактные методы изменены, реализация также должна измениться. Это все о компромиссах. :)
MichelHenrich
4
Утверждение, что SRP облегчает поддержку программ, в лучшем случае сомнительно. Проблема, которую создает SRP, состоит в том, что вместо того, чтобы иметь коллекцию хорошо названных классов, где имя говорит обо всем, что класс может и не может сделать (другими словами, легко понять, что приводит к простоте обслуживания), вы получите сотни / Тысячи классов, которые люди должны использовать тезаурус, чтобы выбрать уникальное имя, многие имена похожи, но не совсем, и сортировка по всей этой болтовне является рутиной. IOW, вообще не обслуживаемый, IMO.
Данк
3
@ Ты говоришь так, как будто классы были единственной доступной модульной единицей. После SRP я видел и писал много проектов, и я согласен, что ваш пример действительно есть, но только если вы не правильно используете пакеты и библиотеки. Если вы разделяете обязанности на пакеты, так как это подразумевает контекст, вы можете снова свободно именовать классы. Затем, для поддержания системы, подобной этой, все, что нужно, это перейти к нужному пакету, прежде чем искать класс, который нужно изменить. :)
MichelHenrich
5
Принципы @Dunk OOD можно сформулировать так: «В принципе, делать это лучше из-за X, Y и Z». Это не значит, что оно всегда должно применяться; люди должны быть прагматичными и взвешивать компромиссы. В больших проектах преимущества таковы, что обычно перевешивают кривую обучения, о которой вы говорите, - но, конечно, это не реальность для большинства людей, и поэтому ее не следует применять так сильно. Однако даже в более легких приложениях SRP я думаю, что мы оба можем согласиться с тем, что отделение постоянства от бизнес-правил всегда дает преимущества сверх его стоимости (за исключением, может быть, одноразового кода).
Мишель Генрих
10

Оба подхода нарушают принцип единой ответственности. Первая версия дает Studentклассу множество обязанностей и связывает его с определенной технологией доступа к БД. Второе приводит к огромному DBклассу, который будет отвечать не только за студентов, но и за любой другой тип объекта данных в вашей программе. РЕДАКТИРОВАТЬ: ваш третий подход является худшим, поскольку он создает циклическую зависимость между классом DB и Studentклассом.

Поэтому, если вы не собираетесь писать игрушечную программу, не используйте ни одну из них. Вместо этого используйте другой класс, такой как a, StudentRepositoryдля предоставления API для загрузки и сохранения, предполагая, что вы собираетесь реализовать код CRUD самостоятельно. Вы также можете подумать об использовании платформы ORM , которая может выполнить тяжелую работу за вас (и среда обычно обеспечивает выполнение некоторых решений, в которых должны быть размещены операции загрузки и сохранения).

Док Браун
источник
Спасибо, я изменил свой ответ, а как насчет третьего подхода? во всяком случае, я думаю, что здесь речь идет о создании отдельных слоев
Ахмад
Что-то также предлагает мне использовать StudentRepository вместо БД (не знаю почему! Может быть, опять-таки единая ответственность), но я до сих пор не могу понять, почему разные репозитории для каждого класса? если это просто уровень доступа к данным, то есть больше статических служебных функций, и они могут быть вместе, не так ли? просто может быть тогда это стало бы таким грязным и переполненным классом (извините за мой английский)
Ахмад
1
@ Ахмад: есть несколько причин, и пара плюсов и минусов, чтобы рассмотреть. Это может заполнить целую книгу. Причины этого сводятся к тестируемости, ремонтопригодности и долгосрочному развитию ваших программ, особенно если они имеют длительный жизненный цикл.
Док Браун
Кто-нибудь работал над проектом, в котором произошли значительные изменения в технологии БД? (без массовой переписки?) У меня нет. Если да, то значительно ли помогла следующая SRP, как предлагается здесь, чтобы избежать привязки к этой БД?
user949300
@ user949300: Я думаю, что я не выразил себя ясно. Студенческий класс не привязывается к определенной технологии БД, он привязывается к определенной технологии доступа к БД, поэтому он ожидает что-то вроде БД SQL для сохранения. Сохранение класса Student полностью не осведомленным о механизме персистентности позволяет значительно упростить повторное использование класса в различных контекстах. Например, в контексте тестирования или в контексте, где объекты хранятся в файле. И это то, что я действительно делал в прошлом очень часто. Не нужно менять весь стек БД вашей системы, чтобы получить выгоду от этого.
Док Браун
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.

Gerino
источник
2
О вашем замечании: хотя, с концептуальной точки зрения, во время работы приложения имеется только одна БД, это не означает, что вы должны сделать методы RetriveStudent и AddStudent статическими. Репозитории обычно создаются с интерфейсами, чтобы позволить разработчикам переключать реализации, не влияя на остальную часть приложения. Это может даже позволить вам распространять ваше приложение, например, с поддержкой различных баз данных. Эта та же самая логика, конечно, применима ко многим другим областям помимо постоянства, как, например, UI.
MichelHenrich
Вы определенно правы, я сделал много предположений на основе исходного вопроса.
Джерино
спасибо, я думаю, что лучший подход заключается в использовании слоев, как указано в шаблоне репозитория
Ахмад,
1

Если это очень простое приложение, в котором объект более или менее привязан к хранилищу данных и наоборот (т.е. его можно рассматривать как свойство хранилища данных), то может иметь смысл иметь метод .save () для этого класса.

Но я думаю, что это было бы довольно исключительным.

Скорее, обычно лучше позволить классу управлять своими данными и своей функциональностью (например, хорошим гражданином ОО) и перенести механизм персистентности в другой класс или набор классов.

Другой вариант - использовать постоянную среду, которая определяет постоянство декларативно (как в случае с аннотациями), но все же выводит сохранение из памяти.

обкрадывать
источник
-1
  • Определите метод для этого класса, который сериализует объект и возвращает последовательность байтов, которую вы можете поместить в базу данных или файл или что-то еще
  • Иметь конструктор для этого объекта, который принимает такую ​​последовательность байтов как ввод

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

philfr
источник
Вы знаете, что я видел такую ​​тенденцию в некоторых PHP-фреймворках, например, Laravel, если вы это знаете. Модель привязана к базе данных и может даже возвращать ряд объектов.
Ахмад