Работая над последним проектом, возникла проблема разделения абстрактных и конкретных сущностей. Вырву кусок ТЗ из контекста: необходимо была возможность легкой расширяемости служб инкассации. Например, существует на данный момент 3 вида инкассации: инкассация Сбербанка, внутренняя и фельдьегерская (вроде бы, инкассация НБУ, во всяком случае государственная) инкассации. Задача была в безболезненном добавлении новых служб.
В теории необходимо указать абстрактный класс, который будет содержать общие для всех видов инкассации свойства и методы, а в конкретных моделях указать специфические. Например, у каждой службы инкассации собственная формула расчета стоимости перевозки наличности, у какой-то ограничены дистанция и сумма перевозимой наличности и т.д.
Итак, изначально для построения сущностей была взята ОРМ ActiveRecord. Посему сперва напишем наш абстрактный класс, который будет содержать общие для всех служб инкассации методы и свойства. Листинг кода некоторых классов выкладывать не буду, потому что из названия можно легко догадаться о его примерном назначении (в целях экономии места комментарии и некоторые методы были опущены):
[JoinedBase] [ActiveRecord("ENCASHMENT_SERVICE", Where = "Enabled = 1")] public abstract class IEncashmentService : ActiveRecordBase { #region Properties [PrimaryKey(Generator = PrimaryKeyType.Increment, Column = "ID")] public int Id { get; protected set; } [Property("NAME")] public string Name { get; protected set; } [Property("ENABLED")] public bool Enabled { get; protected set; } [Property("AGREEMENT")] public string Agreement { get; protected set; } [HasMany(ColumnKey = "SERVICE_ID", Lazy = false)] public ISet<EncashmentRoute> Routes { get; protected set; } [HasMany(ColumnKey = "SERVICE_ID", Lazy = false)] public ISet<EncashmentServiceParameter> Parameters { get; protected set; } [HasMany(ColumnKey = "SERVICE_ID", Lazy = true)] internal ISet<Deal> Deals { get; set; } #endregion #region Methods public abstract IEnumerable<PotentialDeal> Create(CashRequest buyer, CashRequest seller); #endregion }
В данном листинге ключ к статье - атрибут "JoinedBase". Имеено он определяет, что данный класс будет абстрактным родителем для всех служб инкассации.
Теперь необходимо описать конкретные службы инкассации. В данной статье напишу пример "Внутренней инкассации", а остальные делаются по аналогии. Итак, листинг кода внутренней службы инкассации будет выглядеть следующим образом (комментарии также были опущены):
[EncashmentServiceCode("Internal")] [ActiveRecord("ENCASHMENT_SERVICE_INTERNAL")] public class InternalEncashmentService : IEncashmentService { #region Properties [JoinedKey("SERVICE_ID")] protected internal int ServiceId { get; set; } #endregion #region Methods public override IEnumerable<PotentialDeal> Create(CashRequest buyer, CashRequest seller) { /* Функциональность метода была опущена, т.к. для статьи он неважен. */ } #endregion }
В последнем листинге нас интересует атрибут свойства ServiceId - JoinedKey. Именно он показывает о том, что данный класс является "прикрепленным" наследником от класса IEncashmentService.
Заполните базу данными о службах инкассации. Теперь, чтобы проверить правильно ли вы все сделали, добавьте проект юнит-тестов и в одном из тестов напишите следующее:
IEncashmentService[] encashmentServices = ActiveRecordBase<IEncashmentService>.FindAll(); Assert.IsTrue(encashmentServices.Length > 0);
Учтите, что связка между абстрактной и конкретной моделью осуществляется через уникальный идентификатор службы инкассации. Если nHibernate, пробегая по "абстрактной" таблице не найдет ни в одной из "конкретной" таблицы, то произойдет исключение, так что аккуратнее. =)
Также учтите, что таблицы необходимо заводить как для абстрактной модели, так и для каждой конкретной.
Будут вопросы - пишите.
Комментариев нет:
Отправить комментарий