C# - 对象组合 - 删除样板代码 [英] C# - Object Composition - Removing Boilerplate Code

查看:209
本文介绍了C# - 对象组合 - 删除样板代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我就已经需要留存数据,并常常最终使用的格局。有谁知道一个很好的策略,不牺牲代码库的可扩展性去除尽可能多的样板代码?

I've worked on numerous .NET projects that have been required to persist data and have usually ended up using a Repository pattern. Does anyone know of a good strategy for removing as much boilerplate code without sacrificing code base scalability?

由于这么多的库代码是锅炉板,需要重复我通常创建一个基类覆盖像异常处理,记录和事务支持的基础知识,以及一些基本的CRUD方法:

Because so much of the Repository code is boiler plate and needs to be repeated I normally create a base class to cover the basics like exception handling, logging and transaction support as well as a few basic CRUD methods:

public abstract class BaseRepository<T> where T : IEntity
{
    protected void ExecuteQuery(Action query)
    {
        //Do Transaction Support / Error Handling / Logging
        query();
    }       

    //CRUD Methods:
    public virtual T GetByID(int id){}
    public virtual IEnumerable<T> GetAll(int id){}
    public virtual void Add (T Entity){}
    public virtual void Update(T Entity){}
    public virtual void Delete(T Entity){}
}

所以这个效果很好,当我有一个简单的域名,我可以快速创建一个DRY仓储类为每个实体。然而,这开始分解时域变得更加复杂。比方说,一个新的实体介绍,不允许更新。我可以分解基类和移动Update方法分成不同的类:

So this works well when I have a simple domain, I can quickly create a DRY repository class for each entity. However, this starts to break down when the domain gets more complex. Lets say a new entity is introduced that does not allow updates. I can break up base classes and move the Update method into a different class:

public abstract class BaseRepositorySimple<T> where T : IEntity
{
    protected void ExecuteQuery(Action query);

    public virtual T GetByID(int id){}
    public virtual IEnumerable<T> GetAll(int id){}
    public virtual void Add (T entity){}
    public void Delete(T entity){}
}

public abstract class BaseRepositoryWithUpdate<T> :
    BaseRepositorySimple<T> where T : IEntity
{
     public virtual void Update(T entity){}
}

这解决方案不能很好地扩展。比方说,我有一个有一个共同的方法,几个实体:
公共虚拟无效存档(T实体){}

This solution does not scale well. Let's say I have several Entities that have a common method: public virtual void Archive(T entity){}

但一些实体可以还可以存档而其他人不能被更新。所以,我继承的解决方案打破了,我不得不创建两个新的基类来处理这种情况。

but some Entities that can be Archived can also be Updated while others can't. So my Inheritance solution breaks down, I'd have to create two new base classes to deal with this scenario.

我已经探讨了Compositon格局,但这似乎留下了很多锅炉板代码:

I've explored the Compositon pattern, but this seems to leave a lot of boiler plate code:

public class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
    private Archiver<MyEntity> _archiveWrapper;      
    private GetByIDRetriever<MyEntity> _getByIDWrapper;

    public MyEntityRepository()
    {
         //initialize wrappers (or pull them in
         //using Constructor Injection and DI)
    }

    public MyEntity GetByID(int id)
    {
         return _getByIDWrapper(id).GetByID(id);
    }

    public void Archive(MyEntity entity)
    {
         _archiveWrapper.Archive(entity)'
    }
} 

该MyEntityRepository现在加载样板代码。有没有我可以用它来自动生成这个工具/模式

The MyEntityRepository is now loaded with boilerplate code. Is there a tool / pattern that I can use to automatically generate this?

如果我能打开MyEntityRepository弄成这个样子,我认为这将是迄今为止是理想的?

If I could turn the MyEntityRepository into something like this, I think that would by far be ideal:

[Implement(Interface=typeof(IGetByID<MyEntity>), 
    Using = GetByIDRetriever<MyEntity>)]      
[Implement(Interface=typeof(IArchive<MyEntity>), 
    Using = Archiver<MyEntity>)
public class MyEntityRepository
{
    public MyEntityRepository()
    {
         //initialize wrappers (or pull them in
         //using Constructor Injection and DI)
    }
}



面向方面编程



我看着使用一个这样的AOP框架,具体的 PostSharp 和他们的组成方面,它看起来像它应该做的伎俩,但为了使用存储库我会要叫Post.Cast<>(),它增加了一个非常奇怪的气味的代码。任何人都知道,如果有使用AOP来帮助摆脱排字样板代码的一个更好的办法?

Aspect Oriented Programming

I looked into using an AOP framework for this, specifically PostSharp and their Composition Aspect, which looks like it should do the trick, but in order to use a Repository I'll have to call Post.Cast<>(), which adds a very odd smell to the code. Anyone know if there's a better way to use AOP to help get rid of the compositor boilerplate code?

如果一切都失败了,我想我可以在,创建自定义代码生成的Visual Studio插件可以生成样板代码到部分代码文件的工作。是否已有一个工具,在那里,将做到这一点。

If all else fails, I suppose I could work at creating a Custom Code Generator Visual Studio plug in that could generate the boiler plate code into a partial code file. Is there already a tool out there that would do this?

[Implement(Interface=typeof(IGetByID<MyEntity>), 
    Using = GetByIDRetriever<MyEntity>)]      
[Implement(Interface=typeof(IArchive<MyEntity>), 
    Using = Archiver<MyEntity>)
public partial class MyEntityRepository
{
    public MyEntityRepository()
    {
         //initialize wrappers (or pull them in
         //using Constructor Injection and DI)
    }
} 

//Generated Class file
public partial class MyEntityRepository : IGetByID<MyEntity>, IArchive<MyEntity>
{
    private Archiver<MyEntity> _archiveWrapper;      
    private GetByIDRetriever<MyEntity> _getByIDWrapper;

    public MyEntity GetByID(int id)
    {
         return _getByIDWrapper(id).GetByID(id);
    }

    public void Archive(MyEntity entity)
    {
         _archiveWrapper.Archive(entity)'
    }
} 



扩展方法



忘了补充这个当我最初写问题(对不起)。我也试图与扩展方法进行实验:

Extension Methods

Forgot to add this when I initially wrote the question (sorry). I also tried experimenting with extension methods:

public static class GetByIDExtenions
{
     public T GetByID<T>(this IGetByID<T> repository, int id){ }        
}

不过,这有两个问题,一个)我必须记住扩展方法类的命名空间,到处添加和b)扩展方法不能满足接口的依赖关系:

However, this has two problems, a) I'd have to remember the namespace of the extension methods class and add it everywhere and b) the extension methods can't satisfy interface dependencies:

public interface IMyEntityRepository : IGetByID<MyEntity>{}
public class MyEntityRepository : IMyEntityRepository{}

更新的:现在的 T4模板是一个可能的解决方案?

Update: Would T4 Templates be a possible solution?

推荐答案

我有一个通用库接口,这是对于一个特定的数据存储实现一次。在这里,它是:

I have a single generic repository interface, which is implemented only once for a particular data storage. Here it is:

public interface IRepository<T> where T : class
{
    IQueryable<T> GetAll();
    T Get(object id);
    void Save(T item);
    void Delete(T item);
}



我对的EntityFramework,NHibernate的,RavenDB储量它的实现。 。我也有一个内存中执行单元测试

I have implementations of it for EntityFramework, NHibernate, RavenDB storages. Also I have an in-memory implementation for unit testing.

例如,这里是内存中的集合为基础的资源库的一部分:

For example, here is a part of the in-memory collection-based repository:

public class InMemoryRepository<T> : IRepository<T> where T : class
{
    protected readonly List<T> _list = new List<T>();

    public virtual IQueryable<T> GetAll()
    {
        return _list.AsReadOnly().AsQueryable();
    }

    public virtual T Get(object id)
    {
        return _list.FirstOrDefault(x => GetId(x).Equals(id));
    }

    public virtual void Save(T item)
    {
        if (_list.Any(x => EqualsById(x, item)))
        {
            Delete(item);
        }

        _list.Add(item);
    }

    public virtual void Delete(T item)
    {
        var itemInRepo = _list.FirstOrDefault(x => EqualsById(x, item));

        if (itemInRepo != null)
        {
            _list.Remove(itemInRepo);
        }
    }
}



通用库接口释放我从创建类似的类的很多的。你只有一个通用的仓库实现,而且自由查询

Generic repository interface frees me from creating lot's of similar classes. You have only one generic repository implementation, but also freedom in querying.

的IQueryable< T> 从<导致code> GETALL()方法可以让我做我想要的数据有任何疑问,并把它们从具体存储代码分开。所有流行的.NET的ORM有自己的LINQ提供者,他们都应该有神奇的 GETALL()法 - 这里,所以没有任何问题。

IQueryable<T> result from GetAll() method allows me to make any queries I want with the data, and separate them from the storage-specific code. All popular .NET ORMs have their own LINQ providers, and they all should have that magic GetAll() method - so no problems here.

我指定使用IoC容器组成根仓库实现:

I specify repository implementation in the composition root using IoC container:

ioc.Bind(typeof (IRepository<>)).To(typeof (RavenDbRepository<>));

在测试中我使用它的内存替换:

In the tests I'm using it's in-memory replacement:

ioc.Bind(typeof (IRepository<>)).To(typeof (InMemoryRepository<>));

如果我想添加更多的具体业务查询存储库,我会添加一个扩展方法(类似的回答您的扩展方法):

If I want to add more business-specific queries for the repository, I will add an extension method (similar to your extension method in the answer):

public static class ShopQueries
{
    public IQueryable<Product> SelectVegetables(this IQueryable<Product> query)
    {
        return query.Where(x => x.Type == "Vegetable");
    }

    public IQueryable<Product> FreshOnly(this IQueryable<Product> query)
    {
        return query.Where(x => x.PackTime >= DateTime.Now.AddDays(-1));
    }
}



所以,你可以使用和混合的业务这些方法逻辑层查询,节省了可测试性和资源库实现的难易程度,如:

So you can use and mix those methods in the business logic layer queries, saving testability and easiness of repository implementations, like:

var freshVegetables = repo.GetAll().SelectVegetables().FreshOnly();

如果你不希望使用这些扩展方法(比如我)不同的命名空间 - 确定,把它们放在同一个命名空间,其中仓库实现驻留(如 MyProject.Data ),或者甚至更好,一些现有业务特定的命名空间(如 MyProject.Products MyProject.Data.Products )。没有必要现在就记住额外的命名空间。

If you don't want to use a different namespace for those extension methods (like me) - ok, put them in the same namespace where repository implementation resides (like MyProject.Data), or, even better, to some existing business specific namespace (like MyProject.Products or MyProject.Data.Products). No need to remember additional namespaces now.

如果你有一些特定的存储库的逻辑某种实体,创建一个派生库类中重写需要的方法。例如,如果产品只能通过 ProductNumber 中找到,而不是编号,不支持删除,您可以创建这个类:

If you have some specific repository logic for some kind of entities, create a derived repository class overriding the method you want. For example, if products can only be found by ProductNumber instead of Id and don't support deleting, you can create this class:

public class ProductRepository : RavenDbRepository<Product>
{
    public override Product Get(object id)
    {
        return GetAll().FirstOrDefault(x => x.ProductNumber == id);
    }

    public override Delete(Product item)
    {
        throw new NotSupportedException("Products can't be deleted from db");
    }
}

和做出的IoC返回产品这个特定的存储库实现:

And make IoC return this specific repository implementation for products:

ioc.Bind(typeof (IRepository<>)).To(typeof (RavenDbRepository<>));
ioc.Bind<IRepository<Product>>().To<ProductRepository>();



这就是我在片中是如何离开我的仓库;)

That's how I leave in piece with my repositories ;)

这篇关于C# - 对象组合 - 删除样板代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆