Как объектно-ориентированный программист может разобраться в программировании, управляемом базами данных?

Я программировал на C # и Java чуть больше года и неплохо разбираюсь в объектно-ориентированном программировании, но мой новый побочный проект требует модели, управляемой базой данных. Я использую C # и Linq, которые кажутся очень мощным инструментом, но у меня проблемы с проектированием базы данных на основе моего объектно-ориентированного подхода.

Мои два основных вопроса:

Что делать с наследованием в моей базе данных? Допустим, я создаю приложение для составления списка сотрудников и у меня есть абстрактный класс Event. От Event я получаю абстрактные классы ShiftEvent и StaffEvent. Затем у меня есть конкретные классы Shift (производные от ShiftEvent) и StaffTimeOff (производные от StaffEvent). Существуют и другие производные классы, но этого достаточно.

Должен ли я иметь отдельную таблицу для ShiftEvents и StaffEvents? Может быть, у меня для каждого конкретного класса должны быть отдельные таблицы? Оба этих подхода кажутся мне проблемными при взаимодействии с базой данных. Другой подход может заключаться в наличии одной таблицы событий, и в этой таблице будут столбцы, допускающие значение NULL, для каждого типа данных в любом из моих конкретных классов. Кажется, что все эти подходы могут помешать расширяемости в будущем. Скорее всего, есть третий подход, который я не рассматривал.

Мой второй вопрос:

Как работать с коллекциями и отношениями "один ко многим" объектно-ориентированным способом?

Скажем, у меня есть класс продуктов и класс категорий. Каждый экземпляр категорий будет содержать один или несколько продуктов, но сами продукты не должны знать категорий. Если я хочу реализовать это в базе данных, то каждому продукту потребуется идентификатор категории, который сопоставляется с таблицей категорий. Но это приводит к большей взаимосвязи, чем я бы предпочел с точки зрения объектно-ориентированного программирования. Продукты не должны даже знать, что категории существуют, не говоря уже о том, чтобы иметь поле данных, содержащее идентификатор категории! Есть ли способ лучше?


person Martin Doms    schedule 26.01.2009    source источник
comment
Это такой отличный вопрос. Вы получите отличные ответы, но мало голосов. Вот как мы катимся здесь на SO.   -  person Dan Rosenstark    schedule 27.01.2009
comment
Он прав ... более 4 лет спустя и всего 22 голоса. Этот вопрос заслуживает лучшего.   -  person Inversus    schedule 21.05.2013


Ответы (11)


Linq to SQL с использованием таблицы для решения класса:

http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/01/linq-to-sql-inheritance.aspx.

Другие решения (например, мое любимое LLBLGen) позволяют использовать другие модели. Лично мне нравится решение с одной таблицей со столбцом дискриминатора, но это, вероятно, связано с тем, что мы часто запрашиваем по иерархии наследования и, таким образом, рассматриваем его как обычный запрос, тогда как для запроса определенного типа требуется только изменение «где».

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

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

person Godeke    schedule 26.01.2009

У меня была противоположная проблема: как разобраться в объектно-ориентированном дизайне после многих лет проектирования баз данных. Кстати, десятью годами ранее у меня возникла проблема с головой окунуться в SQL после многих лет «структурированного» программирования с плоскими файлами. Между декомпозицией классов и сущностей данных достаточно сходства, чтобы заставить вас думать, что они эквивалентны. Это не так.

Я склонен согласиться с мнением о том, что как только вы выберете реляционную базу данных для хранения, вам следует разработать нормализованную модель и скомпрометировать свою объектную модель там, где это неизбежно. Это связано с тем, что СУБД больше ограничивает вас, чем собственный код - создание скомпрометированной модели данных с большей вероятностью причинит вам боль.

Тем не менее, в приведенных примерах у вас есть выбор: если ShiftEvent и StaffEvent в основном похожи с точки зрения атрибутов и часто обрабатываются вместе как события, тогда я был бы склонен реализовать единую таблицу событий со столбцом типа. Представления с одной таблицей могут быть эффективным способом разделения подклассов, и на большинстве платформ db их можно обновлять. Если классы более различаются по атрибутам, то таблица для каждого из них может быть более подходящей. Я не думаю, что мне нравится идея трех таблиц: отношения «имеет один или нет» редко необходимы в реляционном дизайне. В любом случае вы всегда можете создать представление событий как объединение двух таблиц.

Что касается Продукта и Категории, если одна Категория может иметь много Продуктов, но не наоборот, то обычный реляционный способ представления этого продукта состоит в том, чтобы продукт содержал идентификатор категории. Да, это связь, но это всего лишь связь данных, и это не смертный грех. Столбец, вероятно, должен быть проиндексирован, чтобы было эффективно извлекать все продукты для категории. Если вы действительно в ужасе от этого понятия, представьте, что это отношение «многие ко многим», и используйте отдельную таблицу ProductCategorisation. Это не так уж важно, хотя подразумевает потенциальные отношения, которых на самом деле не существует и может ввести в заблуждение кого-то, кто обратится к приложению в будущем.

person Mike Woodhouse    schedule 26.01.2009

На мой взгляд, эти парадигмы (реляционная модель и ООП) применимы к разным доменам, что затрудняет (и делает бессмысленным) попытки создать сопоставление между ними.

Реляционная модель предназначена для представления фактов (например, «A - человек»), то есть нематериальных вещей, обладающих свойством «уникальности». Нет смысла говорить о нескольких «экземплярах» одного и того же факта - есть просто факт.

Объектно-ориентированное программирование - это парадигма программирования, в которой подробно описывается способ создания компьютерных программ для выполнения определенных критериев (повторное использование, полиморфизм, сокрытие информации ...). объект обычно является метафорой для какой-то материальной вещи - автомобиля, двигателя, менеджера или человека и т. Д. Материальные вещи не являются фактами - могут быть два разных объекта с одинаковым состоянием, но они не являются тот же объект (отсюда и разница между equals и == в Java, например).

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

TL; версия DR (устранено несоответствие объектно-реляционного импеданса):

Факты = рецепт на вашем холодильнике. Объекты = содержимое вашего холодильника.

person Eek    schedule 26.01.2009
comment
Все, что Eek написал здесь, верно, но я также отмечу, что некоторые базы данных, такие как PostGre, позволяют определять пользовательские типы, позволяя вам хранить объекты как существительные в этих фактах. - person Breton; 27.01.2009
comment
У фактов может не быть примеров, но у таблиц есть. - person Superbest; 16.12.2014

Фреймворки, такие как

  1. Hibernate http://www.hibernate.org/
  2. JPA http://java.sun.com/developer/technicalArticles/J2EE/jpa/

может помочь вам беспрепятственно решить эту проблему наследования. например http://www.java-tips.org/java-ee-tips/enterprise-java-beans/inheritance-and-the-java-persistenc.html

person Pierre    schedule 26.01.2009

Я также должен был разбираться в проектировании баз данных, SQL и, в частности, в мировоззрении, ориентированном на данные, прежде чем приступить к объектно-ориентированному подходу. Несоответствие объектно-реляционного импеданса все еще сбивает меня с толку.

Самое близкое, что я нашел, чтобы справиться с этим, - это смотреть на объекты не с точки зрения объектно-ориентированного программирования или даже с точки зрения объектно-ориентированного дизайна, а с точки зрения объектно-ориентированного анализа. Лучшая книга по OOA, которую я получил, была написана в начале 90-х Питером Коудом.

На стороне базы данных лучшая модель для сравнения с OOA - это не реляционная модель данных, а модель Entity-Relationship (ER). Модель ER на самом деле не является реляционной и не определяет логический дизайн. Многие апологеты отношений считают, что это слабость ER, но на самом деле это ее сильная сторона. ER лучше всего использовать не для проектирования базы данных, а для анализа требований к базе данных, также известного как анализ данных.

Анализ данных ER и OOA на удивление совместимы друг с другом. ER, в свою очередь, вполне совместима с моделированием реляционных данных и, следовательно, с проектированием базы данных SQL. OOA, конечно, совместим с OOD и, следовательно, с OOP.

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

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

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

ООП содержит эффект пульсации в основном за счет инкапсуляции и сокрытия информации.

Реляционное моделирование данных преодолевает эффект пульсации, прежде всего, за счет независимости физических данных и логической независимости данных.

На первый взгляд, эти два способа мышления кажутся принципиально противоречащими друг другу. В конце концов, вы научитесь использовать их оба с пользой.

person Walter Mitty    schedule 27.01.2009

Мое предположение пришло мне в голову:

Что касается наследования, я бы предложил иметь 3 таблицы: Event, ShiftEvent и StaffEvent. Событие имеет общие элементы данных, вроде того, как оно было изначально определено.

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

person Community    schedule 26.01.2009

Большой вопрос: как это понять? Просто нужна практика. Вы пытаетесь реализовать проект базы данных, сталкиваетесь с проблемами в дизайне, проводите рефакторинг и в следующий раз вспоминаете, что сработало, а что нет.

Чтобы ответить на ваши конкретные вопросы ... это небольшое мнение, например, «как я бы это сделал», не принимая во внимание потребности в производительности и тому подобное. Я всегда начинаю полностью нормализовать и перехожу к этому, основываясь на реальных тестах:

Table Event
EventID
Title
StartDateTime
EndDateTime

Table ShiftEvent
ShiftEventID
EventID
ShiftSpecificProperty1

...

Table Product
ProductID
Name

Table Category
CategoryID
Name

Table CategoryProduct
CategoryID
ProductID

Также повторяя то, что сказал Пьер - инструмент ORM, такой как Hibernate, намного лучше справляется с трением между реляционными структурами и объектно-ориентированными структурами.

person Rex M    schedule 26.01.2009

Существует несколько возможностей сопоставить дерево наследования с реляционной моделью. NHibernate, например, поддерживает стратегии «таблица на класс иерархии», таблица на подкласс и таблица на конкретный класс: http://www.hibernate.org/hib_docs/nhibernate/html/inheritance.html

По вашему второму вопросу: вы можете создать отношение 1: n в своей БД, где таблица Products имеет внешний ключ к таблице категорий. Однако это не означает, что ваш класс продукта должен иметь ссылку на экземпляр категории, к которому он принадлежит. Вы можете создать класс Category, который содержит набор или список продуктов, и вы можете создать класс продукта, который не имеет понятия о Категории, к которой он принадлежит. Опять же, вы можете легко сделать это, используя (N) Hibernate; http://www.hibernate.org/hib_docs/reference/en/html/collections.html

person Frederik Gheysels    schedule 26.01.2009

Похоже, вы открыли для себя объектно-реляционный импеданс Несоответствие.

person moffdub    schedule 26.01.2009
comment
А вот и врачи, которые еще раз опишут вашу болезнь! - person Dan Rosenstark; 27.01.2009

Продукты не должны даже знать, что категории существуют, не говоря уже о том, чтобы иметь поле данных, содержащее идентификатор категории!

Я не согласен с этим, я бы подумал, что вместо того, чтобы указывать идентификатор категории, вы позволяете своей организации делать это за вас. Тогда в коде у вас будет что-то вроде (заимствовано из ActiveRecord NHib и Castle):

class Category
  [HasMany]
  IList<Product> Products {get;set;}

...

class Product
  [BelongsTo]
  Category ParentCategory {get;set;}

Затем, если вы хотите узнать, к какой категории относится ваш продукт, вы просто сделаете что-нибудь простое, например:

Product.ParentCategory

Я думаю, что вы можете настроить орм по-другому, но в любом случае я спрашиваю о наследовании ... почему вас это волнует? Либо сделайте это с объектами и забудьте о базе данных, либо сделайте это по-другому. Это может показаться глупым, но если вы действительно не можете иметь кучу таблиц или по какой-то причине не хотите одну таблицу, зачем вам заботиться о базе данных? Например, у меня такая же установка с несколькими объектами-наследниками, и я просто занимаюсь своими делами. Я еще не смотрел саму базу данных, потому что меня это не касается. Меня беспокоит лежащий в основе SQL, и возвращаются правильные данные.

Если вам нужно заботиться о базе данных, вам нужно будет либо изменить свои объекты, либо придумать собственный способ работы.

person rball    schedule 26.01.2009
comment
Меня беспокоит лежащий в основе SQL, и возвращаются правильные данные. Что вы имеете в виду? Вы смотрите на SQL, сгенерированный ORM? Разве он не генерируется автоматически? - person RussellH; 27.01.2009

Думаю, здесь не помешало бы немного прагматизма. Сопоставления между объектами и таблицами всегда кое-где странно. Вот что я делаю:

Я использую Ibatis для общения с моей базой данных (от Java до Oracle). Всякий раз, когда у меня есть структура наследования, в которой я хочу, чтобы подкласс сохранялся в базе данных, я использую «дискриминатор». Это уловка, когда у вас есть одна таблица для всех классов (типов) и все поля, которые вы, возможно, захотите сохранить. В таблице есть один дополнительный столбец, содержащий строку, которая используется Ибатисом, чтобы увидеть, какой тип объекта необходимо вернуть.

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

Что касается ваших отношений между категорией и продуктом, я бы добавил к продукту столбец categoryId, потому что это упростило бы жизнь как с точки зрения SQL, так и с точки зрения сопоставления. Если вы действительно застряли на «теоретически правильной вещи», вы можете рассмотреть дополнительную таблицу, в которой всего 2 столбца, соединяющих категории и их продукты. Это будет работать, но обычно эта конструкция используется только тогда, когда вам нужны отношения «многие ко многим».

Постарайтесь сделать это как можно проще. «Академическое решение» - это хорошо, но, как правило, это немного излишне, и его труднее рефакторировать, потому что оно слишком абстрактное (например, скрытие отношений между категорией и продуктом).

Надеюсь, это поможет.

person Rolf    schedule 26.01.2009