【问题标题】:Not all Interface Members will be Implemented并非所有接口成员都将被实现
【发布时间】:2010-07-09 06:39:55
【问题描述】:

说“某些”接口成员没有为某些类实现是很常见的,因为它们在某些情况下不适用,所以您在方法体中抛出一个未实现的错误是正确的吗?

例如,假设我创建了一个接口 IAPIAuthentication,该接口作为类合约的服务,这些类将执行对 3rd 方 API 的身份验证请求,例如 Facebook 和其他我们将在稍后实现的 API。

所以我的 IAPIAuthentication 接口可能具有以下属性:

// The URI that the auth HTTP Request will go to (minus any querystring values, this is just the base)
AuthenticationURI (property)

// unique ID for your API account with whatever API you are using (Facebook, Picasa, whatever)
ClientID  (property)

// unique secret code also obtained when you sign up for an API account and used in auth calls
ClientSecret (property)

// a confirmation code sent back from the 
AuthenticationVerificationCodeID (method)

// a boolean property set to true if an AuthenticationVerificationID was received back after an Auth request
AuthenticationWasSuccessful (property)

// sends the actual HTTP Request to the specified Uri
SendRequest()

好吧,在很多情况下,其他 API 在其身份验证过程中需要相同的信息(例如 PhotoBucket 等)

好的,所以我创建了这个接口,当我为这些 API 创建包装器时,该接口将用于各种实现,整个目的是在我开始使用一些我将在这里构建这些通用接口的包装器。这些是大多数 NVP API 的基本构建块,因此无论我正在实现什么 API 或我在此处创建的任何接口中至少 90% 的值,我在这些接口中放入的内容通常都会被使用。

所以我将创建一个类,例如实现该接口的 FacebookAuth。所有的发现和花花公子。好的,下次我在一个新项目上工作时,我说嘿,我还将实现该接口,使其符合创建第三方包装器项目的团队标准/模式)并且我知道我需要让我们说所有这些属性,但可能会减去一两个,因为该提供者在其身份验证过程中不需要该信息。可以说它只是一个不需要的。

所以我的问题是:

  1. 当我稍后开始创建所有这些 3rd 方 API 包装器项目时,我的方法是否对重用和一致性目标有意义?

  2. 一般而言,正如您所知道的那样,您可以通过在 .NET 中抛出一个未实现的异常来避免实现某个接口方法,如果有人试图在您的子类中使用该方法。我不确定属性...如果您绝对必须(罕见的一次性),您将如何忽略它们中的任何一个。那么,就所有成员将始终 100% 全面使用而言,期望您的界面永远不会“完美”是“正常的”吗?我的意思是可以说只放入将 100% 使用的元素。好的,但这取决于...因为在这种情况下,大多数情况下某些 API 将全部使用...只是一些一次性的不会...所以对我来说,包含一些可能不会使用的 API 仍然是有意义的在其他地方。

我只是想要一些基于经验的输入......在这一点上,在接口方面比我更有经验的开发人员。我没有太多使用接口..我知道它们是什么(合同,yada yada),但我试图找出它们的最佳用途,我真的认为这是一个很好的用途。我也会有抽象类......只有几个,所以我知道两者之间的区别。我只想知道可以这么说拥有不完美的接口是否可以接受。我想这就是他们版本 API 的原因吗?但即使在现有版本中,您也会有一些类不会完全实现接口的所有成员,但会实现该接口以实现一致性和重用,无论在您的应用程序或包装器中整体...

我希望我没有说太多。如果我不清楚,请告诉我。请记住,我上面的示例并不完整,但可以理解。

【问题讨论】:

  • 我建议将其设为社区 wiki - 这不是一个有明确答案的问题。
  • 哪些部分是不必要的?只是想知道因为我正在解释范围,使用示例以及与之相关的问题。对我而言,所有这些信息对于了解我的使用环境以及出现此类问题的原因至关重要。
  • 我想我想知道,因为“不必要”与每个人对帖子的看法有关,我想你可以说

标签: c#


【解决方案1】:

通常不希望有部分实现的接口。

我的建议是分离你的关注点,即将你的接口分成几个接口,这样实现就可以是完整和自主的。

但是,在某些情况下,统一接口的好处胜过关注点分离,恕我直言,这些情况很少,但它们确实存在。一个示例是在 .NET BCL 中,其中一些 ICollection 类是只读的,因此不支持 AddRemoveClear。为了避免对控制流使用异常,ICollection 提供了一个IsReadOnly 属性来指示这些方法不受支持。

如果您仍然决定为所有代码保留一个接口,请抛出 NotSupportedException 而不是 NotImplementedException,让您选择不显式实现该操作。

【讨论】:

  • +1 我想这么说,但找不到雄辩的方式
  • 是的,这是一个分离。身份验证是任何 API(如 Flickr、Facebook 等)的登录过程的一部分。但您说的是在此接口中甚至对这个列表进行子集化只是因为也许 5 个 API 中的 2 个不需要让我们说出秘密属性作为一个例子,但其他大多数将为其 Auth 进程实现此接口的包装器呢?
  • 我的意思是,一般来说,你应该分开。如果您决定不分离,您应该提供一种机制来检测哪些部分受支持,哪些不支持。决定权在你。
  • 我有几个按功能分隔的接口......所以应该很好。我只是不认为任何层在所有情况下都会有 100% 使用每个接口的子类将在所有情况下实现所有成员。您将有一些成员根本没有在某些子类中使用,这很好,只要它只有几个而不是很多......很多会表明接口设计不好,对吗?当您说支持哪些部分时,您的意思是在您的子类中还是什么?抱歉,不确定您在说什么背景或内容
  • 你用很少的标点符号和相当非正式的风格写作,使你的帖子(不必要地)难以阅读。是的,一般来说,许多不受支持的操作都带有错误界面设计的味道。当我说“哪些部分受支持,哪些不支持”时,我指的是实现类支持/不支持的接口部分(当您说子类时,我认为您的意思是)。
【解决方案2】:

根据您的描述,您正在创建一个将由多个类似服务实现的接口。由于我不知道您的要求、约束或接口作者/实现者之间的职责分工,因此让我来检查一些可供您使用的选项。这绝不是一个详尽的列表 - 只是那些常见的:

  1. 创建一个“超集”通用接口,对于某些实现,它可能无法完全实现。异常报告哪些功能不受支持。
  2. 创建一个“最低公分母”接口,该接口只有所有实现都可以支持的成员。不会抛出不受支持的异常,因为所有功能都需要每个人都支持。
  3. 为每个实现服务创建完全独立的接口(这实际上破坏了通用、统一接口的价值)。
  4. 作为 #1 的变体,添加一个可以报告受支持功能的属性/方法(因此您不会使用异常作为检查受支持功能的手段)。
  5. 作为 #2 的变体,创建可逐步添加额外功能的继承接口。

选项 #1 会使编写可靠代码变得困难。您总是发现自己将每个方法调用包装在一个 try/catch 块中——这可能变得不可读。更糟糕的是,如果您忘记在某处执行此操作,您可以通过调用堆栈传播未处理的异常,这本身可能会造成破坏。然而,这种方法对于那些实现接口的人来说是最容易处理的——如果不支持某项功能,只需抛出 NotSupportedException——很容易。

选项 #2 可能需要进行大量分析才能正确识别受支持功能的真正常见子集。这种方法更加复杂,因为如果您错误地猜测所有实现支持某个功能的能力,您可能会发现自己处于默认情况#1 - 要求您添加所有必要的逻辑来处理潜在的异常。

选项#3基本上是:“我放弃。这里没有通用接口”。 有时这是一个可以接受的答案。试图在一个不会自然出现的接口上插入一个接口可能比简单地针对单独的(不相关的)接口或实现类进行编码更糟糕。在这种情况下,您通常会为每个已知实现实施“策略”,并将功能分组为更粗粒度的方法,这些方法可以在所有情况下可靠地实现。

选项 #4 很有帮助,因为它允许您在调用某个功能之前先检查它是否受支持。如果调用,实现者仍然可能会抛出 NotSupportedException,不同之处在于您不会调用一开始就不受支持的实现。这里的问题是,您需要设计一种机制,允许消费者测试支持哪些功能。这种方法不是非常面向对象或特别可扩展(如果您的接口不断发展),但它很简单。

选项 #5 尝试通过分区功能改进 #2 - 可能没有有意义的“最低公分母”接口 - 相反,可能存在一系列相关接口可以一起使用。此选项还可能导致零散接口激增,这可能成为维护挑战。从好的方面来说,这种方法允许您通过尝试强制转换为特定接口来轻松测试实现支持哪些功能。它也比某种SupportedCapabilities 枚举更具可扩展性。在这种方法中,每个实现都应该支持所有接口或不支持接口。这并不总是能够实现,并且与选项 #2 中描述的挑战相冲突。

正如谚语所说:“任何编程问题都可以通过添加另一个抽象级别来解决”。但是,对于您要解决的问题,什么级别的抽象是必要的或适合的,这完全取决于您。

【讨论】:

    【解决方案3】:

    对于此处的其他答案,如果您确实必须提供接口的不完整实现,那么您应该在访问不受支持的成员时抛出 NotSupportedException。您还可以使用explicit interface implementation 降低这些成员的可见度。

    public interface IExample
    {
        void Add(int id);
        void Remove(int id);
        bool Contains(int id);
    }
    
    public class ReadOnlyExample : IExample
    {
        private readonly HashSet<int> _ids;
    
        public ReadOnlyExample(IEnumerable<int> ids)
        {
            _ids = new HashSet<int>(ids);
        }
    
        public bool Contains(int id)
        {
            return _ids.Contains(id);
        }
    
        void IExample.Add(int id)  // explicit interface implementation
        {
            throw new NotSupportedException("This example is read-only!");
        }
    
        void IExample.Remove(int id)  // explicit interface implementation
        {
            throw new NotSupportedException("This example is read-only!");
        }
    }
    

    【讨论】:

      【解决方案4】:

      我同意哈佛的回答。此外,关于如何处理属性,您可以让不受支持的属性 getter/setter 像方法一样抛出异常。例如:

      public string ClientID
      {
          get
          {
              throw new NotSupportedException();
          }
      }
      

      【讨论】:

      • 是的,这就是我的意思...在属性中抛出异常...例如,如果一个 API 不需要使用 ClientSecret 属性,则基本上忽略它,但仍然使用该接口中的其余部分几乎是所有 API 中的大多数
      猜你喜欢
      • 1970-01-01
      • 2021-04-22
      • 1970-01-01
      • 2021-09-03
      • 2013-08-19
      • 1970-01-01
      • 2016-05-08
      • 2015-06-01
      • 1970-01-01
      相关资源
      最近更新 更多