这是我遇到的一个常见问题。让我们听听您的解决方案。我将使用Employee-managing应用程序作为示例: -
我们有一些实体类,其中一些实现特定的接口。
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);
}
我们经常被迫沿着这条路线走下去,因为我们不能修改超类型,也不能修改集合,例如在.NET中,我们可能需要通过子控件集合查找此表单上的所有按钮。我们可能需要对孩子类型做一些事情,这取决于孩子类型的某些方面(例如上面例子中的奖金金额)。
让我感到奇怪的是,考虑到它的出现频率,没有“接受”的方法来做到这一点。
1)类型转换是否值得避免?
2)有没有我没有想过的替代方案?
PéterTörök建议编写Employee并在对象树中进一步推进类型转换: -
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);
}
}
}
在这个例子中,绝对值得将这些特征从Employee类型中推出 - 特别是因为有些经理可能会喝茶,有些可能不会 - 但我们仍然有类型转换的相同根本问题。
只要我们在合适的级别进行,它是否“ok”?或者我们只是解决问题?
圣杯将成为访客模式的变体,其中: -
但我很欣赏这可能是不切实际的。
答案 0 :(得分:5)
我肯定会通过将所需的属性/特征与Employee相关联而不是将其子类化来尝试通过组合而不是继承来解决这个问题。
我可以在Java中给出一个示例,我认为它足够接近您的语言(C#)是有用的。
public enum EmployeeProperty {
RECEIVES_BONUS,
DRINKS_TEA,
...
}
public class Employee {
Set<EmployeeProperty> properties;
// methods to add/remove/query properties
...
}
修改后的循环看起来像这样:
foreach(Employee employee in employees) {
if (employee.getProperties().contains(EmployeeProperty.RECEIVES_BONUS)) {
PayBonus(employee);
}
}
此解决方案比子类化更灵活:
在Java中,枚举本身可以有属性或(甚至虚拟)方法 - 我不知道这是否可以在C#中使用,但在最坏的情况下,如果需要更复杂的属性,可以用类实现它们层次结构。 (即使在这种情况下,你也不会回到原点,因为你有一个额外的间接水平,这为你提供了上述的灵活性。)
你是对的,在最一般的情况下(在上面的最后一句中讨论),类型转换问题没有解决,只是在对象图上向下推了一层。
总的来说,我不知道这个问题真正令人满意的解决方案。处理它的典型方法是使用多态:拉出公共接口并通过它操纵对象,从而消除了向下转换的需要。但是,如果有问题的对象没有通用接口,该怎么办?可能有助于认识到,在这些情况下,设计并不能很好地反映现实:实际上,我们创建了一个标记界面,只是为了使我们能够将一堆不同的对象放入一个公共集合中,但这些对象之间没有语义关系。
所以我相信在这些情况下,向下倾斜的尴尬是一个信号,表明我们的设计可能存在更深层次的问题。
答案 1 :(得分:0)
您可以实现一个只迭代IRecievesBonus类型的自定义迭代器。