6

我有一系列数据结构,例如:

abstract class Base {...}
class Foo : Base {...}
class Bar : Base {...}

和一个方法,它接受一个 Base 并根据它是哪个子类来转换它:

void Convert(Base b) {
  if (b is Foo) 
    // Do the Foo conversion
  else if (b is Bar) 
    // Do the Bar conversion
...

显然这是很糟糕的面向对象—— Convert 方法必须知道 Base 的每个派生类,并且每次扩展 Base 时都必须更改。解决这个问题的“正常”面向对象方法是让每个派生类 Base 负责转换自身,例如

abstract class Base {
  abstract Converted Convert();
...}
class Foo : Base {
  override Converted Convert(){...}
...}
class Bar : Base {
  override Converted Convert(){...}
...}

但是,在我编写的代码中,Base 是一个纯数据结构(只有 getter 和 setter - 没有逻辑),它位于另一个我无权更改的程序集中。有没有一种更好地构造不强制 Base 的派生类具有逻辑的代码的方法?

谢谢

4

2 回答 2

19

如果你这样做的时候很痛,那就不要那样做。根本问题是:

我有一个方法需要Base...

既然这是根本问题,就把它去掉。完全摆脱这种方法,因为它很糟糕。

将其替换为:

void Convert(Foo foo) {
   // Do the Foo conversion
}
void Convert(Bar bar) {
   // Do the Bar conversion
}

现在没有方法必须撒谎说它可以转换任何 Base东西,而实际上它不能。

于 2013-11-13T16:31:25.557 回答
2

我遇到了与此类似的情况,并提出了一个类似于函数式语言中的模式匹配的解决方案。这是语法:

请注意,在 lambdas 中, foo 和 bar 被强类型化为它们各自的类型;无需投射。

var convertedValue = new TypeSwitch<Base, string>(r)
            .ForType<Foo>(foo => /* do foo conversion */)
            .ForType<Bar>(bar => /* do bar conversion */)
        ).GetValue();

下面是 TypeSwitch 类的实现:

public class TypeSwitch<T, TResult>
{
    bool matched;
    T value;
    TResult result;

    public TypeSwitch(T value)
    {
        this.value = value;
    }

    public TypeSwitch<T, TResult> ForType<TSpecific>(Func<TSpecific, TResult> caseFunc) where TSpecific : T
    {
        if (value is TSpecific)
        {
            matched = true;
            result = caseFunc((TSpecific)value);
        }
        return this;
    }

    public TResult GetValue()
    {
        if (!matched)
        {
            throw new InvalidCastException("No case matched");
        }
        return result;
    }
}

我确信它可以被清理干净,在大多数情况下,Eric Lippert 是正确的,这个前提存在根本性的缺陷。但是,如果您遇到唯一的其他选择是一堆is和演员的情况,我认为这更干净一点!

于 2013-11-13T20:59:25.670 回答