Подтвердить что ты не робот

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

У меня была противоположная проблема: как получить голову вокруг OO после нескольких лет проектирования базы данных. Приходите к этому, десять лет назад я столкнулся с проблемой получить голову вокруг SQL после многолетнего "структурированного" программирования с плоскими файлами. Есть достаточно сходства между разложением класса и данных, чтобы ввести вас в заблуждение, думая, что они эквивалентны. Это не так.

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

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

Моделирование реляционных данных преодолевает эффект ряби в основном за счет независимости физических данных и независимости логических данных.

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

Ответ 6

Я угадываю голову:

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

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

Ответ 7

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

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

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, намного лучше справляется с трением между реляционными структурами и структурами OO.

Ответ 8

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

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

Ответ 10

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

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

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

...

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

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

Product.ParentCategory

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

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

Ответ 11

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

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

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

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

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

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