【发布时间】:2016-09-27 09:00:46
【问题描述】:
背景
这是一个重构问题。我有一堆方法,它们或多或少具有完全相同的代码,但它们作用于不同的类型。每种类型基本上只有一种方法,我想将它们全部组合成一个可以使用泛型类型的方法。
当前代码
也许下面的代码将有助于解释我正在尝试什么 -
以下方法的主要区别在于 DbSet 实体参数。在方法代码中,它们使用的属性大多完全相同,但在一两行中,它们可能会使用实体类型不共享的属性。例如,AccountId(来自 Account 实体)和 CustomerId(来自 Customer 实体)。
int? MethodToRefactor(DbSet<Account> entity, List someCollection, string[] moreParams)
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
//Only the below two lines differ in all MethodToRefactor because they use entity's properties that are not shared by all entities
if (entity.Count(a => a.Name == refText) > 0)
keyValue = entity.Where(a => a.Name == refText).First().AccountId;
if (...some conditional code...)
break;
}
return keyValue;
}
int? MethodToRefactor(DbSet<Customer> entity, List someCollection, string[] moreParams)
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
//Only the below two lines differ in all MethodToRefactor because they use entity's properties that are not shared by all entities
if (entity.Count(c => c.CustomerName == refText) > 0)
keyValue = entity.Where(c => c.CustomerName == refText).First().CustomerId;
if (...some conditional code...)
break;
}
return keyValue;
}
下面是调用上述方法的代码-
void Caller()
{
foreach (var entity in EntityCollection)
{
if (entity.Name == "Account")
{
id = MethodToRefactor(db.Accounts,...);
}
else if (entity.Name == "Customer")
{
id = MethodToRefactor(db.Customers,...);
}
}
}
问题
这在一方面是不可扩展的,因为它需要为每个新添加的实体复制/粘贴一个新的 MethodToRefactor。也很难维护。我也许可以在一个单独的方法中重构所有 MethodToRefactors 共有的代码,并在每个实体中执行一个 ifelse,但是我基本上会将调用者与 MethodToRefactor 合并。我正在寻找一种更简洁的解决方案,对 Caller 方法的更改最少,如下所述。
理想/期望的重构代码
这是泛型/模板类型的绝佳候选者。如下所示,我可以将实际实体更改为通用 T 并将不使用实体之间公共属性的两行作为表达式/方法传递。
以下是演示理想解决方案的 C# 类型的伪代码,但我不知道如何在 C# 中实际执行。
int? MethodToRefactor<T>(DbSet<T> entity, Expression<Func<T, T> filterMethod,
Expression<Func<T, T> getIdMethod, List someCollection, string[] moreParams) where T : Account, Customer //This will fail
{
int? keyValue = null;
foreach (var itemDetail in someCollection)
{
string refText = GetRefTextBySource(itemDetail, moreParams);
if (filterMethod(entity) == true)
keyValue = getIdMethod(entity);
if (...some conditional code...)
break;
}
return keyValue;
}
void Caller()
{
foreach (var entity in EntityCollection)
{
if (entity.Name == "Account")
{
id = MethodToRefactor<Account>(db.Accounts, () => {entity.Count(a => a.Name == refText) > 0}, () => {entity.Where(a => a.Name == refText).First().AccountId},...);
}
else if (entity.Name == "Customer")
{
id = MethodToRefactor<Customer>(db.Customer, () => {entity.Count(c => c.CustomerName == refText) > 0}, () => {entity.Where(c => c.CustomerName == refText).First().CustomerId},...);
}
}
}
实现的好处/目标 1. 我们将所有 MethodToRefactors 合二为一,消除了所有重复代码。 2. 我们将实体特定的操作抽象给调用者。这一点很重要,因为该逻辑被移动到一个逻辑位置,该位置知道不同实体之间的差异(调用者在开始时每个实体都有一个)以及如何使用这些差异。 2. 通过将实体特定代码委托给调用者,我们还使其更加灵活,这样我们就不必为每个实体特定逻辑创建一个 MethodToRefactor。
注意:我不是适配器、策略等的忠实粉丝,我更喜欢可以使用 C# 语言功能实现这些目标的解决方案。这并不意味着我反对经典设计模式,只是我不喜欢通过重构几个方法来创建一堆新类的想法。
【问题讨论】:
标签: c# generics expression-trees