通过枚举属性来识别对象类型,而不是使用GetType(),这是不好的做法吗?

本文关键字:GetType 类型 对象 识别 枚举 属性 | 更新日期: 2023-09-27 18:15:11

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

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

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

IAuditEvent上有一个枚举属性,这样每个对象都可以用唯一的枚举值识别它的类型,这是不好的做法吗?

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

通过枚举属性来识别对象类型,而不是使用GetType(),这是不好的做法吗?

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

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

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

最好在业务层中使用查找表:

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

简短的回答:看情况。

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

使用接口的代码。这段代码应该只知道IAuditEvent,而不知道它的实现类。如果这段代码需要了解不同类型的审计事件(我指的是最一般意义上的"类型",而不是特定的类),那么我认为在IAuditEvent中添加type属性是很好的做法。对于用户而言,甚至不需要为每种类型设置不同的实现。

另一种类型的代码是代码,实现接口,我的意思是不只是类从IAuditEvent继承,但也类构造,并意味着与这些实现直接工作。如果这个并且只有这个代码需要知道它正在处理什么类型的IAuditEvent(这里我指的是类中的类型),那么我想说添加type属性是不好的做法,因为它暴露了实现的比特。这段代码也可以做instanceof检查

实现接口的目的是抽象实现——如果你使用接口而不关心实现类型,那么就不需要用enum值来标识它。

话虽如此,我要做的是有一个公共基类型,它既实现了接口,又有一个返回enum的抽象属性:

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))...语句——现在你可以使用基于TypeId属性

  • 返回的枚举的switch语句。

如果你添加更多的实现,你仍然会遇到必须扩展枚举的问题,但这是一个相对简单的代码更改,如果你添加更多的实现,你必须重新编译(所以扩展枚举不是什么大问题,但你确实想避免改变已经分配的值,如果可能的话)。