Ответ 1
A предыдущий мой вопрос раскрыл некоторые полезные ссылки, которые, как я подозреваю, уместны на ваш вопрос, в частности обсуждение Джимми Богарда Перечисление классов.
У меня есть следующая модель DB:
**Person table**
ID | Name | StateId
------------------------------
1 Joe 1
2 Peter 1
3 John 2
**State table**
ID | Desc
------------------------------
1 Working
2 Vacation
и модель домена была бы (упрощена):
public class Person
{
public int Id { get; }
public string Name { get; set; }
public State State { get; set; }
}
public class State
{
private int id;
public string Name { get; set; }
}
Состояние может использоваться в логике домена, например:
if(person.State == State.Working)
// some logic
Итак, из моего понимания, государство действует как объект ценности, который используется для проверки логики домена. Но он также должен присутствовать в модели БД для представления чистой ERM.
Таким образом, состояние может быть расширено:
public class State
{
private int id;
public string Name { get; set; }
public static State New {get {return new State([hardCodedIdHere?], [hardCodeNameHere?]);}}
}
Но используя этот подход, имя состояния будет жестко закодировано в домене.
Вы знаете, что я имею в виду? Существует ли стандартный подход к подобным вещам? С моей точки зрения, что я пытаюсь сделать, это использовать объект (который сохраняется с точки зрения дизайна ERM) как своего рода объект ценности в моем домене. Как вы думаете?
Обновление вопроса: Вероятно, мой вопрос был недостаточно ясным.
Мне нужно знать, как бы я использовал объект (например, пример состояния), который хранится в базе данных в моей логике домена. Чтобы избежать таких вещей, как:
if(person.State.Id == State.Working.Id)
// some logic
или
if(person.State.Id == WORKING_ID)
// some logic
A предыдущий мой вопрос раскрыл некоторые полезные ссылки, которые, как я подозреваю, уместны на ваш вопрос, в частности обсуждение Джимми Богарда Перечисление классов.
Ваша предлагаемая структура кажется прекрасной. (Терминологическое отступление: поскольку State
имеет идентификатор, это не объект значения, а скорее объект.)
Перечисления - это запах кода, поэтому не пытайтесь идти по этому маршруту. Это гораздо более объектно-ориентированное перемещение поведения в объект State с использованием шаблона State.
Вместо того, чтобы писать
if (person.State == State.Working)
// do something...
по всему вашему коду, это позволит вам писать
person.State.DoSomething();
Это намного чище и позволит вам добавлять новые государства, если это необходимо.
Общепринятой практикой является включение в "перечисление" элемента "Неизвестный" со значением 0. Вы можете сделать это и использовать его для нового состояния, если вы действительно этого хотите.
Но то, что вы описываете, это бизнес-логика... настройка состояния после создания нового объекта должна произойти на уровне бизнес-логики, а не внутри самого класса.
Вы хотите создать метод factory, который будет создавать соответствующий необходимый класс состояний на основе сохраненного значения.
что-то вроде
public static State GetStateByID( StateEnum value)
{
if(value.Invalid)
throw new Exception();
switch(value)
case State.Working
return new WorkingState();
case State.somethingelse
return new somethingelseState();
case State.something
return new somethingState();
case State.whatever
return new whateverState();
}
При использовании перечислений всегда старайтесь использовать 0 как Invalid. Под капотом перечисление представляет собой тип значения, а неназначенный int всегда равен 0.
Обычно используется factory, например, в сочетании с шаблоном состояния.
Итак, когда вы читаете сохраненное целочисленное значение из базы данных, вы можете передать int в перечисление и вызвать factory с ним, чтобы получить соответствующий объект State.
Я лично считаю ошибкой программировать против идентификаторов. Вместо этого я бы поменял вашу таблицу на следующее:
**State table**
ID | Desc | IsWorking | IsVacation
-----------------------------------------------------------
1 Working True False
2 Vacation False True
Я бы использовал эти атрибуты для принятия бизнес-решений, например:
public void MakeDecisionOnState(State state)
{
if (state.IsVacation)
DoSomething();
if (state.IsWorking)
DoSomethingElse();
}
Или, будучи еще более умным, используйте шаблон factory, чтобы создать правильный экземпляр на основе этих атрибутов:
public abstract class State
{
public Guid Id { get; set; }
public string Description { get; set; }
public abstract void DoSomething();
}
public class WorkingState : State
{
public override void DoSomething()
{
//Do something specific for the working state
}
}
public class VacationState : State
{
public override void DoSomething()
{
//Do something specific for the vacation state
}
}
public class StateFactory
{
public static State CreateState(IDataRecord record)
{
if (record.GetBoolean(2))
return new WorkingState { Id = record.GetGuid(0), Description = record.GetString(1) };
if (record.GetBoolean(3))
return new VacationState { Id = record.GetGuid(0), Description = record.GetString(1) };
throw new Exception("Data is screwed");
}
}
Теперь вы исключили оператор if/switch, и ваш код мог бы быть просто:
state.DoSomething();
Причина, по которой я делаю это, заключается в том, что зачастую эти типы объектов (или, скорее всего, объекты ценности в DDD) могут быть сконфигурированы клиентом, то есть они могут не хотеть, чтобы некоторые из состояний были активными в системе, или они может пожелать назвать их чем-то другим. Путем программирования против атрибутов клиент может удалять/редактировать записи по своему усмотрению, и даже если этот процесс генерирует новый идентификатор, он не влияет на систему, им просто нужно установить атрибуты.
В моем документе доменный слой должен быть отделен от структуры модели БД /ERM. Мне было трудно понять ваше окончательное предложение для государственного класса. ИМХО это не очень хорошо для установления общего языка, который является одной из основных целей DDD.
Я бы пошел на более простой дизайн. Государство принадлежит классу Person. Я бы включил его в класс.
public class Person
{
public int Id { get; }
public string Name { get; set; }
public PersonState State { get; set; }
}
Кажется, что состояние имеет определенные значения (я предполагаю, что человек является сотрудником в вашем контексте), который не меняется очень часто. Поэтому я бы смоделировал его как перечисление и рассматривал его как тип данных.
enum Days {Working, Vacation};
Это, на мой взгляд, прост в понимании дизайна. Отображение в проект ERM принадлежит IMHO в слое persistence. Там перечисление должно быть сопоставлено с ключом таблицы состояний. Это можно сделать, используя аспект, чтобы сохранить исходную модель домена чистой.