Недавно я взглянул на реализацию Kazi Manzur Kigg MVC (Kazi пород) и заметил некоторый код, который, казалось, побеждал принцип DRY / SOC. Я хотел бы, чтобы у всех были мысли о возможном рефакторе для разделения проблем.
Kigg реализует методы Add
и Remove
в каждом классе репозитория ( Примечание : BaseRepository
имеет виртуальные методы, которые могут быть перегружены каждой конкретной реализацией.)
Реализации для Kigg.Repository.LinqToSql.CategoryRepository
и Kigg.Repository.LinqToSql.StoryRepository
каскадного удаления с помощью их методов Remove
для удаления дочерних объектов. ( Примечание : Категория имеет родительское отношение (один ко многим) к Story, поэтому они имеют одинаковые дочерние отношения от Story до графа объектов) см. Диаграмму . Оскорбительный код - это способ, которым оба хранилища удаляют друг друга дочерние объекты:
CategoryRepository
</p>
<pre><code>namespace Kigg.Repository.LinqToSql.CategoryRepository{
//using statements omitted
public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
{
//code omitted
public override void Remove(ICategory entity)
{
Check.Argument.IsNotNull(entity, "entity");
Category category = (Category) entity;
Database.DeleteAll(Database.StoryViewDataSource.Where(v => v.Story.CategoryId == category.Id));
Database.DeleteAll(Database.CommentSubscribtionDataSource.Where(cs => cs.Story.CategoryId == category.Id));
Database.DeleteAll(Database.CommentDataSource.Where(c => c.Story.CategoryId == category.Id));
Database.DeleteAll(Database.VoteDataSource.Where(v => v.Story.CategoryId == category.Id));
Database.DeleteAll(Database.MarkAsSpamDataSource.Where(sp => sp.Story.CategoryId == category.Id));
Database.DeleteAll(Database.StoryTagDataSource.Where(st => st.Story.CategoryId == category.Id));
Database.DeleteAll(Database.StoryDataSource.Where(s => s.CategoryId == category.Id));
base.Remove(category);
}
}
}
StoryRepository
</p>
<pre><code>namespace Kigg.Repository.LinqToSql
{
//using statements omitted
public class StoryRepository : BaseRepository<IStory, Story>, IStoryRepository
{
//code omitted
public override void Remove(IStory entity)
{
Check.Argument.IsNotNull(entity, "entity");
Story story = (Story) entity;
Database.DeleteAll(Database.StoryViewDataSource.Where(sv => sv.StoryId == story.Id));
Database.DeleteAll(Database.CommentSubscribtionDataSource.Where(cs => cs.StoryId == story.Id));
Database.DeleteAll(Database.CommentDataSource.Where(c => c.StoryId == story.Id));
Database.DeleteAll(Database.VoteDataSource.Where(v => v.StoryId == story.Id));
Database.DeleteAll(Database.MarkAsSpamDataSource.Where(sp => sp.StoryId == story.Id));
Database.DeleteAll(Database.StoryTagDataSource.Where(st => st.StoryId == story.Id));
base.Remove(story);
}
}
}
Буду ли я прав, предполагая, что в более совершенном проекте CategoryRepository
будет вызываться метод Remove
для StoryRepository
, что делегирует заботу об удалении дочернего объекта Story StoryRepository
, к которому он принадлежит? С точки зрения техобслуживания любые добавления к дочерним элементам истории потребуют добавления DeleteAll
вызовов как к CategoryRepository
, так и к StoryRepository
.
.
Какая реализация будет лучше?
Следует ли изменить рефакторинг CategoryRepository
для непосредственного использования StoryRepository
:
Категория Репозиторий (рефакторинг)
</p>
<pre><code>namespace Kigg.Repository.LinqToSql.CategoryRepository{
//using statements omitted
public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
{
//code omitted
public override void Remove(ICategory entity)
{
Check.Argument.IsNotNull(entity, "entity");
Category category = (Category) entity;
// refactor - start
StoryRepository _storyRepository = new StoryRepository( Database );
category.Stories.ForEach( story => _storyRepository.Remove( story ) );
// refactor - end
base.Remove(category);
}
}
}
Этот рефакторинг позволит CategoryRepository
повторно использовать логику удаления в StoryRepository
, а также должен повторно использовать тот же LinqToSql DataContext
, на который ссылается аргумент Database
, данный конструктору StoryRepository
. Но когда дело доходит до юнит-тестирования, у него начинает появляться запах.
Будет ли лучший рефакторинг включать использование IoC (Kigg использует Unity в качестве контейнера Ioc) для внедрения экземпляра PerWebRequest
scoped IStoryRepository
в конструктор CategoryRepository
?
Категория Репозиторий (рефакторинг 2)
</p>
<pre><code>namespace Kigg.Repository.LinqToSql.CategoryRepository{
//using statements omitted
public class CategoryRepository : BaseRepository<ICategory, Category>, ICategoryRepository
{
private readonly IStoryRepository _storyRepository;
public CategoryRepository(IDatabase database, IStoryRepository storyRepository) : base(database)
{
Check.Argument.IsNotNull(storyRepository, "storyRepository");
_storyRepository = storyRepository;
}
public CategoryRepository(IDatabaseFactory factory, IStoryRepository storyRepository) : base(factory)
{
Check.Argument.IsNotNull(storyRepository, "storyRepository");
_storyRepository = storyRepository;
}
//code omitted
public override void Remove(ICategory entity)
{
{
Check.Argument.IsNotNull(entity, "entity");
Category category = (Category) entity;
// refactor - start
category.Stories.ForEach( story => _storyRepository.Remove( story ) );
// refactor - end
base.Remove(category);
}
}
}
С помощью этого второго рефакторинга мы теперь можем внедрить экземпляр IStoryRepository
в CategoryRepository
во время модульного тестирования и через Unity Ioc. Конечно, мы должны были бы распространить этот рефакторинг на каждый класс репозитория, чтобы они могли заботиться о своих детях.
Что думают все?