这是我经常遇到的一个问题。让我们听听你的解决方案。我将以员工管理应用程序为例:
我们有一些实体类,其中一些实现了一个特定的接口。
public interface IEmployee { ... }
public interface IRecievesBonus { int Amount { get; } }
public class Manager : IEmployee, IRecievesBonus { ... }
public class Grunt : IEmployee /* This company sucks! */ { ... }
我们有一组员工可以重复访问。我们需要抓取所有实现iRecievesBonus的对象并支付奖金。
幼稚的实现遵循以下原则:
foreach(Employee employee in employees)
{
IRecievesBonus bonusReciever = employee as IRecievesBonus;
if(bonusReciever != null)
{
PayBonus(bonusReciever);
}
}
或者交替使用c:-
foreach(IRecievesBonus bonusReciever in employees.OfType<IRecievesBonus>())
{
PayBonus(bonusReciever);
}
-
我们不能修改IEemployee接口来包含子类型的详细信息,因为我们不想用只有子类型关心的详细信息污染父类型。
-
我们没有仅包含子类型的现有集合。
-
我们不能使用
接见者模式
因为元素类型不稳定。另外,我们可能有一个实现IRecievesBonus和IDrinkstea的类型。它的accept方法将包含对visitor.visit(this)的不明确调用。
我们经常被迫走这条路,因为我们
不能
修改超级类型或集合,例如在.NET中,我们可能需要通过子控件集合查找此表单上的所有按钮。我们可能需要对依赖于子类型某些方面的子类型做一些事情(例如上面示例中的奖金金额)。
我觉得奇怪的是,没有一种“可接受”的方式来做到这一点,因为它经常出现。
1)类型转换是否值得避免?
2)有没有我没想到的选择?
编辑
P_ter t_¶r_¶k建议组成员工并将类型转换进一步推下对象树:
public interface IEmployee
{
public IList<IEmployeeProperty> Properties { get; }
}
public interface IEmployeeProperty { ... }
public class DrinksTeaProperty : IEmployeeProperty
{
int Sugars { get; set; }
bool Milk { get; set; }
}
foreach (IEmployee employee in employees)
{
foreach (IEmployeeProperty property in employee.Propeties)
{
// Handle duplicate properties if you need to.
// Since this is just an example, we'll just
// let the greedy ones have two cups of tea.
DrinksTeaProperty tea = property as DrinksTeaProperty;
if (tea != null)
{
MakeTea(tea.Sugers, tea.Milk);
}
}
}
在这个例子中,将这些特性从雇员类型中剔除是绝对值得的——特别是因为有些经理可能喝茶,而有些经理可能不喝茶——但是我们仍然有相同的类型转换的潜在问题。
只要我们在正确的水平上做,这是“可以”的情况吗?或者我们只是在转移问题?
圣杯将是访客模式的一个变体,其中:
-
可以添加元素成员而不修改所有访问者
-
访问者可以基于接口类型访问成员
-
不涉及铸造或反射
但我很感激这可能不切实际。