Как правильно реализовать Domain Model?
От: seas  
Дата: 16.10.07 21:39
Оценка:
hi!

Проектирую корпоративные приложения на связке Java + Hibernate + Spring + DWR + Dojo.
Первым делом берусь за Domain Model — POJO + SQL Scheme + Hibernate Mapping.
Цели которым задаюсь:
1. дать достаточную функциональную гибкость этой модели,
2. исключить мешающую мне излишнюю гибкость и возможные ошибки на этапе занесения данных в базу.
3. обеспечить расширяемость и изменяемость системы под новые требования, если они остаются в рамках общей концепции приложения.

В результате получаю package из POJO, который:
1. Содержит несколько деревьев наслодования, которые практически повторяют друг друга в названиях и структуре наследования (но не в данных и время жизни объектов в них разное),
2. Классы в разных деревьях имеют связи между собой, причем связи не между базовыми классами, а между потомками.

Соответственно, ради достижения Цели 2, мы жертвуем объектно-ориентированностью, ведь классы связаны не через интерфейсы (коими являются базовые классы), а на нижнем уровне (Результат 2) — но там связи строго типизированные и база данных получается соответствующая (использую режим Hibernate — Table per Class). Т.е. за нас сначала проверяет компилятор не напортачили-ли мы где-нибудь и не положим-ли шлак в базу, а потом проверит Hibernate когда мы ему подсунем шлак чтобы положить в базу. Система получает достаточно расширяемая за счет добавления новых подклассов уже существующих базовых классов, полиморфная логика зачастую реализуется снаружи, а не внутри классов данных — во вспомогательных Visitorах.

Но так или иначе, мы жертвуем ООП и меня в это ткнули носом. Но я считаю Domain Model — моделью данных, а не логики — ведь все-же она состоит из POJO.

Так вот прав-ли я? Действитель-но ли можно считать такой не объектно ориентированный дизайн — вполне приемлемым для модели данных? Что можно почитать из теории по этому поводу?
Re: Как правильно реализовать Domain Model?
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 17.10.07 06:21
Оценка: +1
Здравствуйте, seas, Вы писали:

S>В результате получаю package из POJO, который:

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

S>2. Классы в разных деревьях имеют связи между собой, причем связи не между базовыми классами, а между потомками.

Приведите, пожалуйста, примеры таких классов.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[2]: Как правильно реализовать Domain Model?
От: seas  
Дата: 17.10.07 07:00
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Здравствуйте, seas, Вы писали:


S>>В результате получаю package из POJO, который:

S>>1. Содержит несколько деревьев наслодования, которые практически повторяют друг друга в названиях и структуре наследования (но не в данных и время жизни объектов в них разное),
КЛ>Явный признак того, что в архитектуре заложена серьезная ошибка.
Опять-же обращаю внимание, что я разрабатываю в конечном итоге модель данных, а не логики, хотя модель данных и выражена в терминах классов и для общности группируется в деревья наследования. На самом деле это преимущественно POJO без полиморфной логики и хранятся в базе данных с помощью Hibernate.

S>>2. Классы в разных деревьях имеют связи между собой, причем связи не между базовыми классами, а между потомками.

КЛ>Приведите, пожалуйста, примеры таких классов.
Первое дерево, с самыми долгоживущими объектами (буквально задаются до старта системы):
1. abstract class Role;
2. class RolePersonal extends Role;
3. class RoleOverGroup extends Role;

Второе дерево (объекты создаются во время работы, но не слишком часто, могут удаляться):
1. abstract class Assignment { private User user; }
2. class AssignmentPersonal extends Assignment { private RolePersonal rolePersonal;}
3. class AssignmentOverGroup extends Assignment { private Group group; private RoleOverGroup roleOverGroup; }

Третье дерево (объекты создаются динамически и очень часто, не удаляются, хранятся в качестве истории):
1. abstract class ApprovalPath { private Document document; }
2. class ApprovalPathPerson { private User dueApprover; }
3. class ApprovalPathOverGroup { private Group group; private RoleOverGroup roleOverGroup; }

таким образом мы имеем три параллельно идущих дерева, как тока добавляем подкласс в дерево Role, придется расширять все остальные.
но за счет типизации связей между подклассами (ApprovalpathOverGroup -> RoleOverGroup, AssignmentPersonal -> RolePersonal) компилятор заругает нас за ошибки и Hibernate потом не даст положить туфту в базу.
Re[3]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 17.10.07 07:50
Оценка:
Здравствуйте, seas, Вы писали:

S>Здравствуйте, Кирилл Лебедев, Вы писали:


КЛ>>Здравствуйте, seas, Вы писали:


S>Первое дерево, с самыми долгоживущими объектами (буквально задаются до старта системы):

S>1. abstract class Role;
S>2. class RolePersonal extends Role;
S>3. class RoleOverGroup extends Role;

S>Второе дерево (объекты создаются во время работы, но не слишком часто, могут удаляться):

S>1. abstract class Assignment { private User user; }
S>2. class AssignmentPersonal extends Assignment { private RolePersonal rolePersonal;}
S>3. class AssignmentOverGroup extends Assignment { private Group group; private RoleOverGroup roleOverGroup; }

S>Третье дерево (объекты создаются динамически и очень часто, не удаляются, хранятся в качестве истории):

S>1. abstract class ApprovalPath { private Document document; }
S>2. class ApprovalPathPerson { private User dueApprover; }
S>3. class ApprovalPathOverGroup { private Group group; private RoleOverGroup roleOverGroup; }

ваще это вроде не противоречит объектному ориентированию я читал книжку, так там вообще базовый класс знает про потомков.

может как-то выделить разделение на Personal и OverGroup в отдельную иерарахию и сделать ссылки на ее объект ,если у например ApprovalPathPerson и ApprovalPathOverGroup одинаковые интерфейсы.,. если бы видеть весь код и его назначение...
Re[4]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 17.10.07 09:28
Оценка:
Здравствуйте, kejroot, Вы писали:

K>ваще это вроде не противоречит объектному ориентированию я читал книжку, так там вообще базовый класс знает про потомков.

Значит плохую книжку читал.
Re[5]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 17.10.07 09:43
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, kejroot, Вы писали:


K>>ваще это вроде не противоречит объектному ориентированию я читал книжку, так там вообще базовый класс знает про потомков.

GZ>Значит плохую книжку читал.
вам лучше знать "Рефакторинг с использованием шаблонов", правда базовый класс был фабрикой для потомков..
Re[6]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 17.10.07 14:39
Оценка:
Здравствуйте, kejroot, Вы писали:

K>Здравствуйте, GlebZ, Вы писали:


GZ>>Здравствуйте, kejroot, Вы писали:


K>>>ваще это вроде не противоречит объектному ориентированию я читал книжку, так там вообще базовый класс знает про потомков.

GZ>>Значит плохую книжку читал.
K>вам лучше знать "Рефакторинг с использованием шаблонов", правда базовый класс был фабрикой для потомков..
То есть, он был полиморфным, или же знал о поведении своих потомков?
Re[7]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 17.10.07 14:54
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, kejroot, Вы писали:


K>>Здравствуйте, GlebZ, Вы писали:


GZ>>>Здравствуйте, kejroot, Вы писали:


K>>>>ваще это вроде не противоречит объектному ориентированию я читал книжку, так там вообще базовый класс знает про потомков.

GZ>>>Значит плохую книжку читал.
K>>вам лучше знать "Рефакторинг с использованием шаблонов", правда базовый класс был фабрикой для потомков..
GZ>То есть, он был полиморфным, или же знал о поведении своих потомков?
в примере у AttributeDescriptor создавались методы forBoolean, forClass, forInteger, возвращавщие разные типы потомков BooleanDescriptor, DefaultDescriptor, ReferenceDescriptor. то есть он знал о существовании своих потомков — вызывал конструктор. Факт!
Re[8]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 17.10.07 15:08
Оценка:
Здравствуйте, kejroot, Вы писали:

K>в примере у AttributeDescriptor создавались методы forBoolean, forClass, forInteger, возвращавщие разные типы потомков BooleanDescriptor, DefaultDescriptor, ReferenceDescriptor. то есть он знал о существовании своих потомков — вызывал конструктор. Факт!

То есть, это фактически набор некоторых функций которые генерили новые инстансы классов, но ни в коем случае не лезли в поведение своего инстанса кроме как через полиморфные вызовы?
Re[9]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 17.10.07 15:22
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, kejroot, Вы писали:


K>>в примере у AttributeDescriptor создавались методы forBoolean, forClass, forInteger, возвращавщие разные типы потомков BooleanDescriptor, DefaultDescriptor, ReferenceDescriptor. то есть он знал о существовании своих потомков — вызывал конструктор. Факт!

GZ>То есть, это фактически набор некоторых функций которые генерили новые инстансы классов, но ни в коем случае не лезли в поведение своего инстанса кроме как через полиморфные вызовы?
да! фабрика..
а базовый класс был абстрактным и эти функции возвращали базовый же тип. полиморфные вызовы, это, если я правильно понял, даже щитай и не лезли, это ж стандартные средства языка! в базовом эти методы были абстрактные
Re[3]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 17.10.07 15:54
Оценка:
Здравствуйте, seas, Вы писали:

S>Опять-же обращаю внимание, что я разрабатываю в конечном итоге модель данных, а не логики, хотя модель данных и выражена в терминах классов и для общности группируется в деревья наследования. На самом деле это преимущественно POJO без полиморфной логики и хранятся в базе данных с помощью Hibernate.

Все таки на логику поведение посмотреть всегда стоит. Посмотрите на прецеденты использования. Есть у меня смутное подозрение что при использовании единая таблица ролей, или Approval будет эффективней. Эффективность БД очень важная вещь а рефакторить ее сложнее чем остальную логику(поскольку за этим может потянуться большой хвост в изменениях в бизнес-логике). Поэтому желательно предполагать какие будут транзакции.
Re[10]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 17.10.07 16:18
Оценка:
Здравствуйте, kejroot, Вы писали:

K>да! фабрика..

K>а базовый класс был абстрактным и эти функции возвращали базовый же тип. полиморфные вызовы, это, если я правильно понял, даже щитай и не лезли, это ж стандартные средства языка! в базовом эти методы были абстрактные
Вопрос не в стандартных средствах языка. Вредны цикличные зависимости поведения. От них всегда неприятности. В случае наследования, наследуемый класс предоставляет некоторый интерфейс поведения который должен быть реализован/дополнен его наследниками. Как только наследуемый класс начинает понимать поведение наследника, мы получаем бардак в виде того, что все наследники должны определять данное поведение. Что немедленно приводит к сипуке в случае расширение модели.

зы. Параллельные иерархии не так уж плохо, но и не так уж хорошо. Хороший пример паттерн Visitor(обсуждался в форуме неоднократно). Главное чтобы был смысл в реализации оного.
Re[11]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 18.10.07 04:45
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, kejroot, Вы писали:


K>>да! фабрика..

K>>а базовый класс был абстрактным и эти функции возвращали базовый же тип. полиморфные вызовы, это, если я правильно понял, даже щитай и не лезли, это ж стандартные средства языка! в базовом эти методы были абстрактные
GZ>Вопрос не в стандартных средствах языка. Вредны цикличные зависимости поведения. От них всегда неприятности.
Это я все понимаю. и с этим не спорю (см. ниже *). я не говорю, что всегда можно создавать такие зависимости, как было описано. из-за них при добавлении наслединика, нужно расширять интерфейс или менять реализацию методов наследуемого.
но всё же это связь из паттерна фабрика, в нее тоже добавляются сведения о новых видах классов.

GZ>В случае наследования, наследуемый класс предоставляет некоторый интерфейс поведения который должен быть реализован/дополнен его наследниками. Как только наследуемый класс начинает понимать поведение наследника, ...

Что вы понимаете под "наследуемый класс понимает поведение наследника"? Знает сигнатуру методов, которые наследник доопределил? Это, согласен, плохо. Правда, если он использует конкретного наследника, он знает его тип и к другим наследникам это знание сигнатры доопределённых методов не относится. * Но всё равно вреден факт знания о наследнике, т.к. нарушается порядок в иерархии.
В примере это сделано для компактности и простоты, чтобы совместить в одном классе конкретную фабрику и абстрактный продукт. Я думаю это оправдано, если учесть, что сокращается количество классов, о которых нужно знать клиенту. Кроме того при необходимости всегда можно сделать Extract Class, ибо код не забетонирован!)

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

почему вы называете бардаком стандартный механизм абстрактных методов? примеры использования — Template Method, Factory Method. его можно не использовать, если уж есть наследники, которым эти методы не нужны, можно же взять виртуальные методы наконец

GZ>зы. Параллельные иерархии не так уж плохо, но и не так уж хорошо. Хороший пример паттерн Visitor(обсуждался в форуме неоднократно). Главное чтобы был смысл в реализации оного.

Вцелом согласен, только Visitor это не очень хороший пример параллельной иерархии. больше подходит Iterator. хороший пример факта "допустимости" параллельной иерархии, правда не понятно как он поможет в решении проблемы автора топика.. а Визитор похож на Builder, там для каждого подкласса сделан метод, и иерархия для разных целей вИзита или билда.
Re[3]: Как правильно реализовать Domain Model?
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 18.10.07 10:27
Оценка:
Здравствуйте, seas, Вы писали:

S>Опять-же обращаю внимание, что я разрабатываю в конечном итоге модель данных, а не логики, хотя модель данных и выражена в терминах классов и для общности группируется в деревья наследования. На самом деле это преимущественно POJO без полиморфной логики и хранятся в базе данных с помощью Hibernate.

Понимаю, но, на мой взгляд, в этом и заключается причина ошибки. Разработать модель данных в отрыве от логики работы программы может и можно, но бесперспективно, т.к. такая модель будет неуклюжа, громоздка и неудобна в использовании. Любая структура — в том числе и структура данных! — разрабатывается под решаемые задачи. Если Вам нужен последовательный доступ к элементам коллекции и неограниченная возможность по добавлению элементов, Вы предпочтете список. Если Вам требуется произвольный доступ (по индексу), Вы предпочтете массив. Какую структуру (модель) данных выбрать — зависит от задач.
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[3]: Как правильно реализовать Domain Model?
От: Кирилл Лебедев Россия http://askofen.blogspot.com/
Дата: 18.10.07 11:13
Оценка:
Здравствуйте, seas, Вы писали:

S>Первое дерево, с самыми долгоживущими объектами (буквально задаются до старта системы):

А какие обязанности у Ваших классов? За что отвечает группа классов Role? За что отвечают группы Assignment и Approval?

S>1. abstract class Role;

S>2. class RolePersonal extends Role;
S>3. class RoleOverGroup extends Role;
Есть предложения реорганизовать Вашу иерархию таким образом:

class Role {...};
class PersonalRole : public Role {...};
class GroupRole : public Role {...};

class Assignment
{
   User * m_pUser;
   Group * m_pGroup;
   Role * n_pRole;
};

class Approval
{
   Document * m_pDocument;
   Role * m_pRole;
   Group * m_pGroup;
};
С уважением,
Кирилл Лебедев
Software Design blog — http://askofen.blogspot.ru/
Re[12]: Как правильно реализовать Domain Model?
От: Larvef Германия  
Дата: 18.10.07 12:04
Оценка:
Здравствуйте, kejroot, Вы писали:

K>Что вы понимаете под "наследуемый класс понимает поведение наследника"? Знает сигнатуру методов, которые наследник доопределил? Это, согласен, плохо.


Прошу прощения, что встреваю в дискуссию. Я правильно понимаю, что исходя из вышесказанного использование абстрактных методов — плохо в архитектурном смысле?
Re[13]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 18.10.07 12:43
Оценка:
Здравствуйте, Larvef, Вы писали:

L>Здравствуйте, kejroot, Вы писали:


K>>Что вы понимаете под "наследуемый класс понимает поведение наследника"? Знает сигнатуру методов, которые наследник доопределил? Это, согласен, плохо.


L>Прошу прощения, что встреваю в дискуссию. Я правильно понимаю, что исходя из вышесказанного использование абстрактных методов — плохо в архитектурном смысле?


я именно, пишу что не плохо, когда оправдано — хорошо.
вот так я имел ввиду плохо:

class Node {
    private ParentNode parentField;
    public Operation() {
        parentField.AddChild(...);
    }
}

class ParentNode extends Node {
    private List<Node> children;
    ...
    public void AddChild(Node arg) {
        children.Add(arg);
    }
    ...
}

если бы AddChild был абстрактный метод, поле parentField можно было бы сделать типом Node и тогда всё нормально
Re[12]: Как правильно реализовать Domain Model?
От: GlebZ Россия  
Дата: 18.10.07 17:03
Оценка:
Здравствуйте, kejroot, Вы писали:

GZ>>В случае наследования, наследуемый класс предоставляет некоторый интерфейс поведения который должен быть реализован/дополнен его наследниками. Как только наследуемый класс начинает понимать поведение наследника, ...

K>Что вы понимаете под "наследуемый класс понимает поведение наследника"? Знает сигнатуру методов, которые наследник доопределил? Это, согласен, плохо. Правда, если он использует конкретного наследника, он знает его тип и к другим наследникам это знание сигнатры доопределённых методов не относится. * Но всё равно вреден факт знания о наследнике, т.к. нарушается порядок в иерархии.
Плохо если типа
void Method()
{
    if (this is Obj1)
       ((Obj1).Method2();
    else
       ((Obj2).Method3();
}

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

K>В примере это сделано для компактности и простоты, чтобы совместить в одном классе конкретную фабрику и абстрактный продукт. Я думаю это оправдано, если учесть, что сокращается количество классов, о которых нужно знать клиенту. Кроме того при необходимости всегда можно сделать Extract Class, ибо код не забетонирован!)

+1 Смысловая часть в самом паттерне хорошо прописана. (в вашей книге не знаю, но в Гамма это все прекрасно описано)

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

K>почему вы называете бардаком стандартный механизм абстрактных методов? примеры использования — Template Method, Factory Method. его можно не использовать, если уж есть наследники, которым эти методы не нужны, можно же взять виртуальные методы наконец
Я не говорил об абстактных методах. Я говорил о поведении наследников. Наследники знают о поведении своего базового. Они могут это выявить через контракт. Базовый же не должен привязываться к наследникам сверх интерфейса который он предоставляет.

K>Вцелом согласен, только Visitor это не очень хороший пример параллельной иерархии. больше подходит Iterator. хороший пример факта "допустимости" параллельной иерархии, правда не понятно как он поможет в решении проблемы автора топика.. а Визитор похож на Builder, там для каждого подкласса сделан метод, и иерархия для разных целей вИзита или билда.

У автора топика не проблема ООП. Это проблема модели данных. Там другие законы.
Re[13]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 19.10.07 04:25
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Плохо если типа

GZ>
GZ>void Method()
GZ>{
GZ>    if (this is Obj1)
GZ>       ((Obj1).Method2();
GZ>    else
GZ>       ((Obj2).Method3();
GZ>}
GZ>

GZ>Если добавляется новый наследник, то мы получаем проблемы неявного поведения.
даа, плохо.. только опять недопёр, что значит неявное поведение?..
правда что может заставить писать так, если можно написать так:

class Obj
{
    virtual void Method()
    { 
    }
}

class Obj1 : Obj
{
    override void Method()
    { 
        // тело Method2
    }
}

class Obj2 : Obj
{
    override void Method()
    { 
        // тело Method3
    }
}

.. или там чтото ещё?..

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

K>>почему вы называете бардаком стандартный механизм абстрактных методов? примеры использования — Template Method, Factory Method. его можно не использовать, если уж есть наследники, которым эти методы не нужны, можно же взять виртуальные методы наконец
GZ>Я не говорил об абстактных методах. Я говорил о поведении наследников. Наследники знают о поведении своего базового. Они могут это выявить через контракт. Базовый же не должен привязываться к наследникам сверх интерфейса который он предоставляет.
просто я так понял, что если сказано "наследники должны определять данное поведение", то эта обязанность закреплена контрактом(абстрактным методом)..

GZ>У автора топика не проблема ООП. Это проблема модели данных. Там другие законы.

Тогда, с точки зрения этих законов, его решение признается нормальным, а те кто его "ткнули носом" — несведующими. или нет? а какие тогда проблемы в этом решении?
1. параллельные иерархии, здесь канают. или нет?
2. узко типизированные связи между потомками тоже. я кстати, вообще не понимаю, почему seas считает это не кошерным?
может есть ещё неохваченные аспекты?
Re[4]: Как правильно реализовать Domain Model?
От: kejroot  
Дата: 19.10.07 04:37
Оценка:
Здравствуйте, Кирилл Лебедев, Вы писали:

КЛ>Есть предложения реорганизовать Вашу иерархию таким образом:


КЛ>
КЛ>class Role {...};
КЛ>class PersonalRole : public Role {...};
КЛ>class GroupRole : public Role {...};

КЛ>class Assignment
КЛ>{
КЛ>   User * m_pUser;
КЛ>   Group * m_pGroup;
КЛ>   Role * n_pRole;
КЛ>};

КЛ>class Approval
КЛ>{
КЛ>   Document * m_pDocument;
КЛ>   Role * m_pRole;
КЛ>   Group * m_pGroup;
КЛ>};
КЛ>


на мой взгляд, это могло бы нарушить Single Responsibility Principle, что обычно приводит к смешению ответственности и разрастанию классов (засчёт поведения).. ну ладно, раз это модель данных, то не важно. но другая проблема! — ослабляется автоматическая проверка типов компилятором и Hibernate'ом! Мы же боимся закона Мэрфи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.