抽象数据类型(有时)是个好主意吗?

时间:2008-10-06 11:43:40

标签: c# design-patterns

有很多次你有一个接口接受类似的类型参数,这些参数具有单独的域逻辑含义:

public static class Database
{
   public static bool HasAccess(string userId, string documentId) { return true; }
}

现在很容易找到某个关键documentId而不是userId,反之亦然。可以通过抽象参数的数据类型来防止这种情况:

public class UserId
{
   public string Value { get; internal set; }
   public static implicit operator string(UserId id) { return id.Value; }
}

public class DocumentId
{
   public string Value { get; internal set; }
   public static implicit operator string(DocumentId id) { return id.Value; }
}

public static class Database
{
    public static bool HasAccess(UserId userId, DocumentId documentId) { return true; }
}

这样,如果您按顺序输入参数,就会得到一个很好的编译器警告:

UserId e = new UserId() { Value = "a" };
DocumentId d = new DocumentId() { Value = "b" };

Database.HasAccess(d, e);

您还可以在不影响系统其他部分的情况下更改抽象类型,但这不太可能。抽象类型是一个好主意,以获得更多的类型安全吗?

这个问题与C#解决方案有关,但欢迎使用其他语言的简短描述。

编辑:从字符串中移除隐式强制转换并将手指指向C#标记。

9 个答案:

答案 0 :(得分:2)

我认为您回答了自己的问题 - 更好的数据完整性和验证,更好的系统

答案 1 :(得分:1)

有趣,但我怀疑在许多情况下(特别是seialization / RPC API),这只会增加混乱/开销。另外 - 一个小的实现细节,但是考虑到这种方法,我会使包装器完全不可变,而不仅仅是“内部设置”不可变。

TBH - 我可能更倾向于使用单元测试...有时候简单就是美丽。另一个问题是,由于你有隐式运算符,它不会阻止你做更多的事情:

string user = "fred";
SomeMethodThatWantsADocument(user);

那应该编译;隐含的操作员撤消你所有的好工作......

答案 2 :(得分:1)

这是typedef在C ++中变得有用的地方。你可以将UserID和DocumentID作为typedeffed类型,因此在没有强制转换的情况下不能互换,但是除了快速记录之外,不要求编译器说“这应该是一个与其他类型不同的独立类型,即使它实际上只是输入X'。

答案 3 :(得分:1)

在这种情况下,对我来说看起来并不值得。

你已经添加了12行,分布在两个额外的课程中。在某些语言中,您需要为此管理两个新文件。 (在C#中不确定)。你已经引入了许多额外的认知负荷。每当您导航班级列表时,都会显示这些类;它们出现在您自动生成的文档中;它们是你的代码库的新手在他们试图学习他们的方式时看到的东西,他们在编译器的依赖图中等等。程序员必须知道类型并在他们调用HasAccess时创建两个新对象

为了什么?为了防止您在检查某人是否有权访问数据库时意外混淆了用户名和文档ID。该检查应该在正常系统中写入两次,也许三次。 (如果你写的很多,你可能没有在数据库访问代码中得到足够的重用)

所以,我会说这是过剩的宇航员。我的经验法则是类或类型应该封装变体行为,而不是变体使用被动数据。

答案 4 :(得分:0)

是的,有时候这是一个好主意。但如果你对此过于痴迷,你就会成为一名建筑宇航员。

关于类型安全性论点 - 它确实增加了类型安全性,但许多语言在没有它的情况下管理得很好。

在我看来,最好的方法是将它作为一个字符串开始,然后当你发现自己重用界面时,在那时重构为更抽象的类型。

预测未来太难以浪费时间尝试。

答案 5 :(得分:0)

对于你的单元测试应该防止的东西,似乎是很多开销,至少在这种情况下。

答案 6 :(得分:0)

您不问及不回答的问题是最能确定新类型是否重要的​​问题:

  1. 这个系统的预计,实际生命周期是多少?如果答案是2年以上,则您应该至少对数据库和用户ID具有一个抽象级别。换句话说,您的数据库应该是抽象的,您的用户和凭据应该是抽象的。然后根据抽象定义实现数据库和用户标识。这样,如果需要更改,您的更改将在最需要的地方本地。

  2. 拥有用户标识数据类型有哪些收益和损失?应该在可用性,表现力和类型安全性方面回答这个问题。如果在可用性和表现力方面有明显的提升,那么所创建的课程或额外线路的数量在很大程度上是无关紧要的 - 万岁,你赢了。让我举一个明显丢失的例子 - 我使用了一个类层次结构,它包含一个带有几个具体子类型的抽象基类。他们不是为子类和适当的访问器提供构造函数,而是创建了一个工厂方法,该方法将XML字符串或流作为参数,并从中构造适当的具体类。可用性的损失使得这个库变得很痛苦 - 甚至他们的示例代码也让人感到厌倦。虽然我可以构建他们提供的所有内容,但是对于典型问题,它会感到十分苛刻并且生成运行时而不是编译时错误。

答案 7 :(得分:0)

虽然在一天结束时,你可能不在乎,抽象越多,维护就越困难(特别是对其他人而言)。如果在六个月内您必须开始挖掘此代码以查找或修复错误,甚至添加新功能,那么记住您所做的事情和原因将需要更长的时间。如果其他人正在这样做,那么乘以那个时间。当你编写新代码时,优雅的代码总是很好,但我总是喜欢在未来的维护者需求中权衡它。

答案 8 :(得分:0)

这对我来说就像一个YAGNI问题。如果你只是因为它可能有用而这样做,那通常不足以说明额外的复杂性。此外,正如其他人所指出的那样,这是单元测试应该捕获的东西。

我要记住的另一件事是抽象是否意味着保护程序员自己。是的,理论上很容易触发两个字符串参数。但是,严肃地说,关注论证排序已经成为大多数语言编程的基本方面。这不应该经常发生,而且肯定是测试应该抓住的东西。如果这类事情是您组织中的常见问题,我会说您有更大的问题要担心。