Sco*_*ski 73

根据"米奇小麦"的反应,这有点切线.

一般来说,每当我看到人们引用框架设计指南时,我总是喜欢提到:

您通常应该在大多数情况下忽略框架设计指南.

这不是因为框架设计指南的任何问题.我认为.NET框架是一个很棒的类库.框架设计指南中有很多奇妙的东西.

但是,设计指南不适用于大多数程序员编写的大多数代码.它们的目的是创建一个由数百万开发人员使用的大型框架,而不是使库编写更有效.

其中的许多建议可以指导您做以下事情:

  1. 可能不是最直接的实现方式
  2. 可能导致额外的代码重复
  3. 可能有额外的运行时开销

.net框架很大,非常大.它是如此之大,以至于假设任何人都对它的每个方面都有详细的了解是绝对不合理的.实际上,假设大多数程序员经常遇到他们以前从未使用过的框架部分,这样会更安全.

在这种情况下,API设计者的主要目标是:

  1. 使事情与框架的其余部分保持一致
  2. 消除API表面区域中不必要的复杂性

框架设计指南促使开发人员创建实现这些目标的代码.

这意味着做一些事情,比如避免继承层,即使它意味着重复代码,或者将所有异常抛出代码推送到"入口点"而不是使用共享助手(这样堆栈跟踪在调试器中更有意义),以及很多其他类似的事情.

这些指南建议使用属性而不是标记接口的主要原因是删除标记接口使得类库的继承结构更加平易近人.与具有15种类型和2层次结构的类相比,具有30种类型和6层继承层次结构的类图非常令人生畏.

如果确实有数百万开发人员使用您的API,或者您的代码库非常大(例如超过100K LOC),那么遵循这些准则可以提供很多帮助.

如果500万开发人员花15分钟学习API而不是花费60分钟学习它,结果是净节省了428个人年.那是很多时间.

但是,大多数项目不涉及数百万开发人员或100K + LOC.在一个典型的项目中,有4个开发人员和大约50K loc,这组假设有很大不同.团队中的开发人员将更好地理解代码的工作原理.这意味着优化快速生成高质量代码,减少错误数量和进行更改所需的工作量更有意义.

花费1周时间开发与.net框架一致的代码,而8小时编写易于更改且错误较少的代码可能导致:

  1. 后期项目
  2. 降低奖金
  3. 增加错误数量
  4. 在办公室度过的时间更多,在海滩上喝玛格丽塔酒的时间更少.

没有4,999,999其他开发人员来吸收成本通常是不值得的.

例如,对标记接口的测试归结为单个"is"表达式,并且导致查找属性的代码更少.

所以我的建议是:

  1. 如果您正在开发用于广泛消费的类库(或UI小部件),请严格遵循框架指南.
  2. 如果您的项目中有超过100K的LOC,请考虑采用其中一些
  3. 否则完全忽略它们.

  • 我不是说指导方针很糟糕.我说它们应该是不同的,具体取决于代码库的大小和您拥有的用户数量.很多设计指南都基于维护二进制可比性这样的东西,这对于一些项目使用的"内部"库来说并不像BCL那样重要.其他指南,如与可用性相关的指南,几乎总是很重要.道德规则是不要对指导方针过于虔诚,特别是对小型项目. (15认同)
  • 我个人看到我作为库编写的任何代码,我将在稍后使用.我并不关心消费是否普遍存在 - 遵循指南可以提高一致性,并且当我需要查看我的代码并在多年后理解它时减少意外... (12认同)
  • +1 - 没有完全回答OP的问题 - MI的目的 - 但仍然非常有帮助. (5认同)
  • @ScottWisniewski:我认为你错过了重点.框架指南只适用于大型项目,它们适用于中型和小型项目.当你总是尝试将它们应用于Hello-World程序时,它们会变得过度杀戮.例如,无论app大小如何,将接口限制为5种方法始终是一个很好的经验法则.你想念的另一件事,今天的小应用程序可能成为明天的大型应用程序.因此,最好使用适用于大型应用程序的良好原则构建它,以便在扩展时,您不必重新编写大量代码. (5认同)
  • 我不太明白遵循(大多数)设计准则将如何导致一个耗时8周的项目突然耗时1周。例如:命名一个虚拟保护的模板方法DoSomethingCore而不是DoSomething并没有太多的工作,您可以清楚地知道这是一个模板方法... IMNSHO,编写应用程序而无需考虑API的人们(但是..我不是框架开发人员,我不在乎我的API!`正是那些编写大量重复(而且也是未记录且通常不可读的)代码的人,而不是相反。 (2认同)
  • @ RickO'Shea对不起,您有这种感觉。是的,我确实阅读了OP。第一句话说“这是切线”。无论如何……我的帖子的症结在于质量标准是如何相对的,需要基于成本效益分析。由于参考了框架设计指南,因此与讨论相关。如果您认为质量标准不是相对的,我将请您考虑诸如NASA之类的情况。如果您遵循他们的质量标准,那将使您破产。如果他们不这样做,人们就会死。 (2认同)

Mit*_*eat 44

标记接口用于将类的功能标记为在运行时实现特定接口.

界面设计.NET类型设计准则-界面设计劝阻赞成使用C#属性的使用的标记接口,但作为@Jay Bazuzi指出,更容易检查比属性标记的接口:o is I

所以不是这样的:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

.NET指南建议您这样做:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 
Run Code Online (Sandbox Code Playgroud)

  • 此外,我们可以使用标记接口完全使用泛型,但不能使用属性. (25认同)
  • 虽然我喜欢属性以及它们从声明的角度看它们的外观,但它们在运行时并不是一等公民,并且需要大量相对较低级别的管道才能使用. (18认同)
  • @Jordão - 这完全是我的想法.作为一个例子,如果我想抽象数据库访问代码(比如Linq to Sql),拥有一个通用接口会使它变得更容易.实际上,我认为用属性编写这种抽象是不可能的,因为你不能转换为属性而不能在泛型中使用它们.我想你可以使用一个空的基类,其他类都可以从中派生出来,但这种感觉或多或少与拥有一个空接口相同.此外,如果您后来意识到您需要共享功能,则该机制已经到位. (4认同)

Tom*_*m B 22

由于每个其他答案都说"应该避免它们",因此解释原因会很有用.

首先,为什么使用标记接口:它们的存在是为了允许使用实现它的对象的代码检查它们是否实现了所述接口,如果有,则以不同的方式处理对象.

这种方法的问题在于它破坏了封装.对象本身现在可以间接控制外部使用方式.此外,它了解将要使用的系统.通过应用标记接口,类定义表明它希望用于检查标记存在的某个地方.它隐含地了解它所使用的环境,并试图定义它应该如何被使用.这违背了封装的想法,因为它了解完全在其自身范围之外存在的系统的一部分的实现.

在实际水平上,这降低了可移植性和可重用性.如果在不同的应用程序中重用该类,则需要复制该接口,并且在新环境中它可能没有任何意义,使其完全冗余.

因此,"标记"是关于类的元数据.这个元数据不是由类本身使用,只对(某些!)外部客户端代码有意义,因此它可以以某种方式处理对象.因为它只对客户端代码有意义,所以元数据应该在客户端代码中,而不是类API.

一个"标记接口"和普通接口之间的区别是,方法的接口告诉外面的世界怎么可以使用,而空的接口意味着它告诉外面的世界如何应该被使用.


Mar*_*ist 7

当语言不支持有区别的联合类型时,标记界面有时可能是必要的恶魔.

假设您要定义一个方法,该方法需要一个类型必须恰好是A,B或C之一的参数.在许多功能优先的语言(如F#)中,这样的类型可以干净地定义为:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C
Run Code Online (Sandbox Code Playgroud)

但是,在诸如C#的OO优先语言中,这是不可能的.在这里实现类似功能的唯一方法是定义接口IArg并用它"标记"A,B和C.

当然,你可以通过简单地接受类型"对象"作为参数来避免使用标记接口,但是你会失去表现力和某种程度的类型安全性.

受歧视的联合类型非常有用,并且已经存在于函数式语言中至少30年.奇怪的是,直到今天,所有主流OO语言都忽略了这一功能 - 尽管它本身与功能编程无关,但属于类型系统.


Ric*_*ein 6

标记接口只是一个空的接口.类会将此接口实现为由于某种原因而使用的元数据.在C#中,您更常使用属性来标记类,原因与您在其他语言中使用标记接口的原因相同.


Mr *_*son 6

这两种扩展方法将解决 Scott 声称的标记接口优于属性的大多数问题:

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}
Run Code Online (Sandbox Code Playgroud)

现在你有:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

相对:

if (o is IFooAssignable)
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

正如 Scott 所说,我不明白使用第一种模式构建 API 所需的时间是第二种模式的 5 倍。