【问题标题】:Is it poor practice to identify object types via an enumeration property, instead of using GetType()?通过枚举属性而不是使用 GetType() 来识别对象类型是不好的做法吗?
【发布时间】:2011-12-31 04:26:47
【问题描述】:

我有一组对象,它们都实现了一个(自定义)接口:IAuditEvent

每个对象都可以存储在数据库中,并且每个对象类型都使用一个唯一的数字 id。

存储对象的方法围绕List<IAuditEvent>循环,因此它需要知道每个对象的具体类型才能存储正确的数字id。

IAuditEvent 上具有枚举属性以便每个对象都可以使用唯一的枚举值来标识其类型是不好的做法吗?

我可以看到最简单的解决方案是编写一个将Type 转换为整数的方法,但是如果我需要枚举审计事件以用于其他目的怎么办?将我的枚举属性放在IAuditEvent 上仍然是错误的吗?

【问题讨论】:

  • 为什么让IAuditEvent 的成员来识别特定类型的事件是不好的做法?有哪些潜在问题?
  • 哈哈!嗯,这就是我要问的。 ;-) 我认为有些人会争辩说,您给对象提供了两种不同的方式来识别自己,而这两种方式可能会不一致。
  • “通过枚举属性识别对象”是什么意思? GetType() 可用于识别对象的类型,而不是识别对象本身...
  • 谢谢@user1027167。我已经澄清了我的问题。
  • 实现接口的目的是抽象出实现——IOW你使用接口而不关心实现类型,因此不需要用枚举值来标识它。

标签: c# types interface enums


【解决方案1】:

实现接口的目的是抽象出实现 - IOW 你使用接口而不关心实现类型,因此不需要用枚举值来标识它。

话虽如此,我的做法是拥有一个通用的基类型,它既实现了接口又具有返回枚举的抽象属性:

public abstract class BaseType : IAuditEvent
{
    public abstract MyTypeEnum TypeId { get; }

    ... add any base implementation of the interface ...
}

然后在每个派生对象中:

public class MyConcreteType : BaseType 
{
    public MyTypeEnum TypeId { get { return MyTypeEnum.SpecificValue; } }

    ... any overrides, etc ....
}

这种方法有几个优点:

  • 它使您的代码保持干净。当在多个类中实现接口时,很有可能会有不同对象可以共享的接口的一些通用实现,可以放在基类中。明智地使用abstractvirtual 方法/属性。

  • 当需要根据实现者的类型进行分支时,使用枚举来标识您的对象可以帮助避免那些无休止和乏味的if (myObj.GetType() == typeof(ObjectA)) {} else if (myObject.GetType() == typeof(ObjectB))... 语句 - 现在您可以只使用基于枚举的 switch 语句由 TypeId 属性返回

如果您添加更多实现,您仍然会遇到必须扩展枚举的问题,但这是一个相对简单的代码更改,如果您要添加更多实现,您无论如何都必须重新编译(因此扩展枚举是没什么大不了的,但您确实希望尽可能避免更改已分配的值)。

【讨论】:

    【解决方案2】:

    简短回答:视情况而定。

    记住接口的用途。它们的全部意义在于向界面的用户隐藏实现。当涉及到接口时,我看到了两种类型的代码:

    使用界面的代码。这段代码应该只知道 IAudiEvent,而不是它的实现类。如果这段代码需要了解不同类型的审计事件(我的意思是最一般意义上的“类型”,而不是具体的类),那么我会说向 IAuditEvent 添加一个 Type 属性是一种很好的做法。就用户而言,甚至不需要为每种类型提供不同的实现。

    另一种类型的代码是实现接口的代码,我指的不仅是从 IAudiEvent 继承的类,还包括构造并旨在直接使用这些实现的类。如果 this and only this 代码需要知道它正在处理的 IAudiEvent 类型(这里我的意思是类中的类型),那么我会说添加 Type 属性是不好的做法,因为它暴露了一些实现。这段代码也可以做一个 instanceof 检查。

    【讨论】:

    • 一个有用的回复,谢谢。我开始怀疑我是否可能以错误的方式解决我的问题。
    【解决方案3】:

    此数据库类型 ID(或鉴别器)本质上是每种类型的元数据。在每个实例上混合数据和元数据并不是很好。我首选的解决方案是编写一个自定义属性来保存此元数据,将其应用于每种类型,然后使用TypeGetCustomAttributes 方法读取这些。

    [DatabaseDiscriminator(123)]
    public class MyAuditEvent : IAuditEvent
    {
    }
    

    【讨论】:

    • 如果开发人员忘记添加自定义属性会怎样?你能在编译时捕捉到它吗?
    • 不,它必须是运行时异常。我不认为这是一个巨大的缺点。毕竟,开发人员可以实现一个接口,但将适当的方法保留为throw new NotImplementedException()
    • 是的,这是真的。我可以使用反射来检查每个实现IAuditEvent 的对象是否具有自定义属性?我的应用程序已经运行了一系列“自测”(更侧重于数据库配置,但它也没有理由不能检查这一点)。
    • 是的,尽管您可能想考虑启动一个单元测试项目来捕获这类事情,因为您不一定希望在启动时运行很多这样的事情。
    • “你可能想考虑开始一个单元测试项目”——很好。
    【解决方案4】:

    是的,这很糟糕。您现在假设IAudit 的每个实现都知道其他实现,因为它们都应该有一个唯一的ID;此外,您需要为接口的每个新实例向​​enum 添加一个新值。这只是应用程序内部不需要的额外信息,而只是在数据表示中。

    宁可在您的业务层中有一个查找表:

    new Dictionary<Type, int> {
        { typeof(UserAudit), 1 },
        { typeof(OrderAudit), 2 }
    }
    

    【讨论】:

    • “是的,这很糟糕”——我想可能是这样!
    猜你喜欢
    • 1970-01-01
    • 2021-08-29
    • 1970-01-01
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-25
    相关资源
    最近更新 更多