首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >重构以避免重复代码

重构以避免重复代码
EN

Stack Overflow用户
提问于 2010-12-16 03:00:12
回答 5查看 2.1K关注 0票数 2

我试图排除一些重复的代码,但它现在开始闻起来很时髦。假设我从这个开始并不是很正确,但你明白我的意思:

代码语言:javascript
运行
复制
    public virtual OrganisationEntity Get(int id)
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id";
        command.Parameters.Add("@id", SqlDbType.Int).Value = id;

        List<OrganisationEntity> entities = new List<OrganisationEntity>();

        SqlDataReader reader = Database.ExecuteQuery(command, ConnectionName.Dev);

        while (reader.Read())
        {
            OrganisationEntityMapper mapper = Mapper;
            entities = mapper.MapAll(reader);
        }

        return entities.First<OrganisationEntity>();
    }

很明显,除了查询之外,每个其他Get(int id)方法都有相同的形式,所以我的下一步是创建一个基类,RepositoryBase如下所示:

代码语言:javascript
运行
复制
public abstract class RepositoryBase<T> where T : new()
{
    /// <summary>
    /// 
    /// </summary>
    public abstract EntityMapperBase<T> Mapper { get; }

    public virtual T Get(int id)
    {

        List<T> entities = new List<T>();

        SqlDataReader reader = Database.ExecuteQuery(Command, ConnectionName);

        while (reader.Read())
        {
            EntityMapperBase<T> mapper = Mapper;
            entities = mapper.MapAll(reader);
        }

        return entities.First<T>();
    }
}

以增加一些通用的趣味性,但这也是它变得丑陋的地方。首先,Database.ExecuteQuery需要一个SqlCommand和一个枚举,所以我想,好吧,然后我将添加两个属性,我将使用一些东西来启动它们。我意识到我不再需要这里的int id参数了,因为我在一个子类中构造了查询,所以我可能会将命令和connectionName作为参数传递,我希望connectionName无论如何都要依赖于OrganisationRepository (其他人需要另一个字符串):

代码语言:javascript
运行
复制
public class OrganisationRepository : RepositoryBase<OrganisationEntity>
{
    protected override EntityMapperBase<OrganisationEntity> Mapper
    {
        get
        {
            return new OrganisationMapper();
        }            
    }

    public override OrganisationEntity Get(int id)
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id";
        command.Parameters.Add("@id", SqlDbType.Int).Value = id;
        return base.Get(command, ConnectionName.Dev);
    }
}

但是,哦,当然,现在方法签名不再同步了……糟了!所以,基本上我想知道。只是感觉很难受,但不知道具体原因。一方面,我想尽可能多地剔除重复代码,但现在它留给我的是!

如何将其重构为(更)适当的OO?我是不是应该忘记分解查询字符串,写很多重复的东西呢?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2010-12-16 03:17:50

你的“下一步”不会和我的一样。

我的下一步将是找到您正在尝试重构的这种“通用代码”的另一个示例。也许是一个"`CustomerEntity.Get(int id)'“方法。

现在,让我们假设CustomerEntity和OrganisationEntity版本之间的唯一区别是查询字符串和将术语“组织”替换为“客户”。我的下一步是尝试使这两种方法越来越相同。假设此方法是OrganisationEntityRepository类的一部分,我会将其重构为EntityRepository1类,并将CustomerEntityRepository重构为EntityRepository2。

步骤1是为实体类型引入一个泛型参数。您必须对OrganisationEntityMapper和CustomerEntityMapper类执行相同的操作。

接下来,回过头来看看还有什么不同。我发现它们使用不同的映射器类,所以让我们将映射器类型设为泛型。为了做到这一点并仍然引用MapAll方法,我将引入一个带有MapAll方法的IMapper接口,并让我的两个具体的映射器类实现该接口。

现在,下一个最大的区别是查询。我将把它放到一个虚拟的"CommandText“属性中。

现在我想我已经为基类做好了准备,也许是EntityRepositoryBase<TEntity,TMapper>。在适当的假设下,我得出了以下结论:

代码语言:javascript
运行
复制
public abstract class EntityRepositoryBase<TEntity, TMapper>
    where TMapper : IMapper<TEntity>
{
    public virtual TEntity Get(int id)
    {
        List<TEntity> entities;
        using (var command = new SqlCommand {CommandText = CommandText})
        {
            command.Parameters.Add("@id", SqlDbType.Int).Value = id;

            entities = new List<TEntity>();

            using (var reader = Database.ExecuteQuery(command, ConnectionName.Dev))
            {
                while (reader.Read())
                {
                    var mapper = Mapper;
                    entities = mapper.MapAll(reader);
                }
            }
        }

        return entities.First();
    }

    protected abstract string CommandText { get; }
    protected abstract TMapper Mapper { get; }
}

public class OrganisationEntityRepository :
    EntityRepositoryBase<OrganisationEntity, OrganisationEntityMapper<OrganisationEntity>>
{
    protected override string CommandText
    {
        get { return @"SELECT t.Id, t.Description FROM Organisation t Where t.Id = @Id"; }
    }

    protected override OrganisationEntityMapper<OrganisationEntity> Mapper
    {
        get { throw new NotImplementedException(); }
    }
}

public class CustomerEntityRepository : EntityRepositoryBase<CustomerEntity, CustomerEntityMapper<CustomerEntity>>
{
    protected override string CommandText
    {
        get { return @"SELECT t.Id, t.Description FROM Customer t Where t.Id = @Id"; }
    }

    protected override CustomerEntityMapper<CustomerEntity> Mapper
    {
        get { throw new NotImplementedException(); }
    }
}

而且,不用说,尽管我还是要说:支持JetBrains ReSharper 5.1,因为它做了所有移动的事情,所以我不必这么做。

票数 1
EN

Stack Overflow用户

发布于 2010-12-16 03:17:19

我相信像Entity FrameworknHibernate这样的ORM会更适合解决这种情况。他们可以照顾所有的管道,你正在试图建立自己。

票数 0
EN

Stack Overflow用户

发布于 2010-12-16 03:25:44

如果您可以在项目中使用ORM框架,我认为这会比手工编写所有这些内容更可取。

但是,可以进行这种重构的一种方法是在RepositoryBase中使用一个具有以下签名的抽象方法:

代码语言:javascript
运行
复制
public abstract T Get(int id);

还有一个带有这个签名的受保护的方法:

代码语言:javascript
运行
复制
protected T Get(SqlCommand command, SqlConnection connection)

它的代码与您之前在RepositoryBase中显示的代码相同。

这样,在派生类中,您只需实现构造命令的类,并从执行实际数据库调用的基类调用命令。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4453746

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档