【问题标题】:What's better? Enum or Type checking?什么更好?枚举或类型检查?
【发布时间】:2013-07-07 12:56:38
【问题描述】:

假设我想将一些 xml 解析成一个强类型的类。当我得到xml时,我不知道它应该是A型还是B型,直到我打开它并查看它。我可以看一下,然后返回一个像这样的枚举:

BaseType x = null;
TypeInfoEnum typeInfo = BaseType.GetTypeInfo(xml);

if(typeInfo == TypeInforEnum.TypeA)
{
    x = BaseType.ParseXmlToTypeA(xml);

    // do other work on Type A
}

else if(typeInfo == TypeInfoEnum.TypeB)
{
    x = BaseType.ParseXmlToTypeB(xml);

    // do other work on Type B
}

或者我可以只用一种方法处理解析并检查类型:

BaseType x = BaseType.ParseXml(xml);

if(x.GetType() == typeof(TypeA))
{
    // do work on Type A
}
else if(x.GetType() == typeof(TypeB))
{
    // do work on Type B
}

只是想从您喜欢的设计角度了解其他人的想法。现在,细节不是很重要。我只是根据 xml 中的内容从单个 XML 源创建 2 种不同的类型。没什么复杂的。

更新:

感谢到目前为止的回答。类型在这里并不重要,但作为示例,类层次结构可能如下所示:

class BaseType
{
    public string CommonData { get; set; }
}

class TypeA : BaseType
{
    public string TypeASpecificData { get; set; }
}

class TypeB : BaseType
{
    public string TypeBSpecificData { get; set; }
}

由于此功能将被整合到其他人将使用的程序集中,我喜欢使用 Enum 的第一个选项,因为让 API 的用户检查某物的类型似乎很尴尬,即使用 Enum 在语义上似乎更彻底。

【问题讨论】:

  • 您能否举例说明 TypeA 和 TypeB 类的外观?
  • 我猜只是一个错字,但仍然:if(typeInfo == TypeInforEnum.TypeA) one r too many
  • @Master117 太多什么?
  • 我会使用第一种方式,在解析之前总是检查以节省时间,我也认为在这里切换会非常好,但我仍然有点困惑 BaseType 如何有 2 种不同的类型,如第一个例子。
  • 关于您的评论,我宁愿通过界面查看它:if (myBaseTypeInstance is ITypeB) { } 或等效项。至少这样,如果您有任何理由将 TypeASpecificDataTypeBSpecificData 组合成一个共享类,您可以这样做。但也许这对你来说不是问题。我认为调用者检查反序列化类型的类型没有理由感到尴尬(尽管调用者事先知道他们期望什么并利用泛型可能更有意义,但是可能超出您的设计范围;如果更有意义,您可以更改)

标签: c# .net oop design-patterns types


【解决方案1】:

在第一个选项中,您实际上是在复制信息(类型 + 枚举)而没有明显的好处。因此,考虑到这两个选项,我会选择第二个,尽管我更喜欢更惯用的 is 而不是 GetType 比较:

BaseType x = BaseType.ParseXml(xml);

if(x is TypeA)
{
    // do work on Type A
}
else if(x is TypeB)
{
    // do work on Type B
}

不过,您可以考虑第三种选择:

BaseType x = BaseType.ParseXml(xml);
x.DoWork();

DoWork 是 BaseType 的抽象方法,在 TypeA 和 TypeB 中被覆盖:

public abstract class BaseType
{
    public abstract void DoWork();
}
public class TypeA : BaseType
{
    public override void DoWork() {
        // do work on Type A
    }
}
public class TypeB : BaseType
{
    public override void DoWork() {
        // do work on Type B
    }
}

【讨论】:

  • 第三个选项需要注意的一点是,DoWork 作为 TypeX 实例的责任是否有意义。在某些情况下,它可能会,但在许多情况下,遵循这种方法会导致不相关的责任不断增加到类层次结构中。
  • @DanBryant:非常好的观点。这正是我将其作为“另一种选择”而不是“理想解决方案”的原因。我只是缺乏语言来描述为什么......
【解决方案2】:

您需要做的是拥有两种不同的方法——一种处理类型 A,另一种处理类型 B:

public void DoWork(A a) { .. }

public void DoWork(B b) { .. }

然后您只需将实例发送到doWork。这将导致您的代码在没有任何类型检查的情况下准确地完成需要完成的工作:

BaseType x = BaseType.ParseXml(xml);
DoWork(x);

另一种选择是在两个类中实现 DoWork 方法:

 public abstract class BaseType {
    public abstract void DoWork();
 }

 public class A: BaseType { 

    public void DoWork() { ... }
 }

 public class B: BaseType { 

    public void DoWork() { ... }
 }

然后您的解析将如下所示:

BaseType x = BaseType.ParseXml(xml);
x.DoWork();

【讨论】:

  • 第一个示例仅在您首先cast x to dynamic 时才有效,以便编译器可以生成适当的类型比较和动态分派。如果直接传递基类型,它会在编译时尝试解析匹配并失败。
  • 不错的收获。这取决于反序列化发生的方式。但基本上,你是对的。
【解决方案3】:

我通常这样做:

1:定义某种键,例如您已经建议的:

TypeInfoEnum typeInfo
{
...
}

2:使用声明如下的解析器创建字典:

Dictionary<TypeInfoEnum, Func<XDocument, IBase>>

3:像这样实现你的公共方法:

public class Parser
{
    IBase Parse(XDocument xDocument)
    {
        TypeInfoEnum key = GetKeyForXDocument(xDocument);
        IBase x = DictionaryWithParsers[key](xDocument);

        return x;
    }
}

我忽略了错误处理和 GetKeyForXDocument 方法的实现,但这应该不是很困难。

您的 API 使用者会像这样使用它:

void SomeConsumingMethod()
{
    ...

    IBase x = serviceObject.Parse(xDocument);

    // Members declared in IBase:
    x.SomeMethod();

    // Members declared in ITypeA or ITypeB
    if (x is ITypeA)
        ((ITypeA)x).A();

    if (x is ITypeB)
        ((ITypeB)x).B();
}

【讨论】:

  • 我认为这对于某些场景来说是一个很好的解决方案,但是在我的具体使用中,用户只会得到 TypeA 或 TypeB,他们不能随意从 xml 中提取两者。 xml 的来源始终相同,但其中包含的是 A 或 B。
  • 消费者在调用你的方法之前是否知道会返回什么类型?
  • 不,他们不会。根据 XML 中的内容,他们基本上必须沿着路径 A 或路径 B 前进。现在,实际上,TypeA 和 TypeB 之间有大约 10 个公共字段,每个大约有大约 30 个非公共字段,这就是为什么我首先希望它们成为不同的对象。基本上,你打开 XML,然后根据里面的内容,你要么走 A 路径,要么走 B 路径。
  • 那我会按照我最初的建议去做。我添加了一些示例代码。
【解决方案4】:

我相信这样的事情会奏效,看起来更简单。

[XmlInclude(typeof(TypeA))]
[XmlInclude(typeof(TypeB))]
class BaseType
{
    public string CommonData { get; set; }
}

class TypeA : BaseType
{
    public string TypeASpecificData { get; set; }
}

class TypeB : BaseType
{
    public string TypeBSpecificData { get; set; }
}

反序列化:

var serializer = new XmlSerializer(typeof(BaseType));
BaseType result;

using (TextReader reader = new StringReader(xmlString))
{
    result = (BaseType)serializer.Deserialize(reader);
}

【讨论】:

    【解决方案5】:

    关注点分离的构造

    我发现违反了单一职责原则 (SRP),因为 XML 输入的解析是在该类中完成的。

    使用枚举启用 SRP 应用程序

    ... 那么类 A、B、C 的构造与类本身或它们的基类分离。不要为了解析枚举而定义基类。

    给定一个有效的enum 值,将该枚举传递给工厂。

    public static void main() {
        SomeClassFactory factory = new SomeClassFactory();
    
        // putting the parsing in the factory expresses the
        // association of the enum to its target types.
        SomeClassEnum someClassName = SomeClassFactory.Parse(xmlInput);
        BaseType someClassInstance = factory.Create(someClassName);
    }
    
    public class SomeClassFactory{
        // Potentially throws NotImplementedException
        public static SomeClassEnum Parse (string xmlInput) { ... }
    
        // the type is already resolved, so we don't need to do
        // it again in any of the code called herein.
        public SomeClassBase Create (SomeClassEnum thisClassName) {
            BaseType newInstance;
    
            switch(thisClassName) { 
                case SomeClassEnum.TypeA:
                    newInstance = buildTypeA();
                    break;
                // ...
            }
    
            return newInstance;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2013-03-20
      • 2019-12-30
      • 2018-10-31
      • 1970-01-01
      • 2013-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多