【问题标题】:Good Case For Interfaces接口的好案例
【发布时间】:2010-11-23 01:31:36
【问题描述】:

我在一家公司工作,有些公司要求在我们的代码中使用接口(Visual Studio C# 3.5)。

我想询问需要接口的 Iron Clad 推理。 (我的目标是证明接口是编程的正常部分。)

我不需要说服力,我只需要一个很好的论据来说服别人。

我正在寻找的论点是基于事实的,而不是基于比较的(即“因为 .NET 库使用它们”是基于比较的。)

反对他们的论点是:如果一个类被正确设置(带有它的公共和私有成员),那么一个接口只是额外的开销,因为那些使用该类的人仅限于公共成员。如果您需要一个由多个类实现的接口,那么只需设置继承/多态。

【问题讨论】:

  • 我不明白反对...的论点
  • 如果你需要向你的团队解释为什么接口是一个好的编程标准,那么你应该找一份新工作..没有冒犯..
  • 我同意找新工作的评论。
  • 感谢所有伟大的答案!
  • "为什么在 goto 工作正常的情况下使用函数" 基本上是一样的说法。

标签: c# c#-3.0 interface


【解决方案1】:

代码解耦。通过对接口进行编程,您可以将使用接口的代码与实现接口的代码解耦。这允许您更改实现,而无需重构所有使用它的代码。这与继承/多态性结合使用,允许您互换使用多种可能的实现中的任何一种。

模拟和单元测试。当方法是虚拟的时,最容易使用模拟框架,默认情况下您可以通过接口获得。这实际上是我创建接口的最大原因。

定义可能适用于许多不同类的行为,使它们可以互换使用,即使类之间没有关系(除了定义的行为)。例如,Horse 和 Bicycle 类可能都具有 Ride 方法。您可以定义一个定义 Ride 行为的接口 IRideable,并且任何使用此行为的类都可以使用 Horse 或 Bicycle 对象,而无需在它们之间强制进行非自然继承。

【讨论】:

  • 我本来打算写这样的东西,但我的口才不够。所以我让别人更好地去做。解耦是它为灵活性、可管理性和可重用性提供的最重要的编程部分。
  • 作为 Horse/Bicycle 注释的附录,接口的好处是能够在不限制继承的情况下泛化类上的操作:stackoverflow.com/questions/558152/…
  • 如果实现更改涉及新方法签名,则添加此类方法会破坏所有使用该接口的客户端。这是一种怎样的脱钩?我们应该以接口和抽象类的结合为目标吗?
  • 那么在这种情况下,您不会更改实现,而是更改合同。您不应该期望与合同脱钩,而只是合同的具体实施。
【解决方案2】:

如果您的商店正在执行自动化测试,那么接口对于依赖注入和能够单独测试软件单元是一大福音。

【讨论】:

    【解决方案3】:
    • 测试驱动开发
    • 单元测试

    没有接口产生解耦的代码会很痛苦。最佳实践是针对接口而不是具体实现进行编码。接口起初看起来很垃圾,但一旦你发现了好处,你就会一直使用它们。

    【讨论】:

      【解决方案4】:

      启用类的单元测试。

      为了有效地跟踪依赖关系(如果接口没有被检出和触摸,只有类的语义可能会改变)。

      因为没有运行时开销。

      启用依赖注入。

      ...也许是因为现在是 2009 年,而不是 70 年代,而现代语言设计师实际上对他们在做什么有一个线索?

      并不是每个类接口都应该抛出接口:只是那些对系统来说很重要,并且可能会经历重大变化和/或扩展的接口。

      【讨论】:

        【解决方案5】:
        • 您可以实现多个接口。您不能从多个类继承。
        • ..就是这样。其他人关于代码解耦和测试驱动开发的观点并未触及问题的关键,因为您也可以使用抽象类来做这些事情。

        【讨论】:

        • 不一定,有共同逻辑可以共享的时候使用抽象类。我可能有 2 个实现相同接口但结构完全不同的类。
        • 如果您只想覆盖基本实现,为什么还要使用抽象类呢?因此接口更适合单元测试。抽象类确实有它们的位置。
        • 我同意抽象类显然应该提供一些实现,否则它们应该是接口。但我是直接回答问题的。
        【解决方案6】:

        除了其他答案中解释的内容外,interfaces 允许您在 .NET 中模拟 多重继承,否则是不允许的。

        唉,就像有人说的那样

        技术由两类人主导:理解他们不管理的东西的人和管理他们不理解的东西的人。

        【讨论】:

        • 这是一个非常好的观点。因为 .NET 不支持多重继承,所以接口的使用几乎是强制性的。
        • Meta-Knight:这是党的路线,但我不确定我是否相信。您不能只是以不同的方式构造事物以在一个子类中不需要多个超类吗?您是否还会说“因为 .NET 不支持多次调度,所以几乎必须编写自己的调度程序”?总是有更强大的对象系统,但您不必总是模仿它们的所有功能来完成某事。
        【解决方案7】:

        接口允许您声明一个可以在许多类型 (IEnumerable) 之间共享的概念,同时允许这些类型中的每一个具有自己的继承层次结构。

        在这种情况下,我们所说的是“这个东西可以枚举,但这不是它的单一定义特征”。

        接口允许您在定义实施者的能力时做出最少的必要决策。当你创建一个类而不是一个接口时,你已经声明你的概念是类的,不能用于结构。在类中声明成员时,您还需要做出其他决定,例如可见性和虚拟性。

        例如,您可以创建一个包含所有公共抽象成员的抽象类,这与接口非常接近,但您已声明该概念在所有子类中都是可覆盖的,而您不必这样做决定是否使用接口。

        它们还使单元测试更容易,但我不认为这是一个强有力的论点,因为您可以构建一个没有单元测试的系统(不推荐)。

        【讨论】:

          【解决方案8】:

          接口和抽象类对不同的事物建模。当你有一个 isA 关系时,你从一个类派生,所以基类模拟了一些具体的东西。当你的类可以执行一组特定的任务时,你就实现了一个接口。

          想想可序列化的东西,(从设计/建模的角度来看)拥有一个名为 Serializable 的基类是没有意义的,因为说某事是可序列化的没有意义。让某些东西实现 Serializable 接口更有意义,因为说“这是类可以做的事情,而不是类是什么”

          【讨论】:

            【解决方案9】:

            我不明白它的额外开销如何。

            接口提供了灵活性、可管理的代码和可重用性。对接口进行编码,您无需担心您正在使用的特定类的具体实现代码或逻辑。你只是期待一个结果。很多类对同一个特性有不同的实现(StreamWriter、StringWriter、XmlWriter)。你不必担心它们是如何实现写入的,你只需要调用它。

            【讨论】:

            • 如果您创建自己的接口,则会产生额外的开销,因为您必须创建和维护更多代码。对于要拉入界面的每个方法,您必须复制方法签名。然后,每次需要更改方法签名时,至少需要在两个地方进行更改。
            • 你在这里支持这两个论点。 StreamWriter 和 StringWriter 是抽象类(TextWriter)的实现; XmlWriter 与 Object 和 IDisposable 无关。
            • 是的,好点,我没想过.NET 是如何实际实现它的。我试图想到一个共享功能(写入)但以不同方式实现它们的类的快速示例..
            【解决方案10】:

            继承参数的问题在于,您要么拥有一个巨大的上帝类,要么拥有如此之深的层次结构,这会让您头晕目眩。最重要的是,你最终会得到一个你不需要或没有任何意义的类的方法。

            我看到很多“没有多重继承”,虽然这是真的,但它可能不会让您的团队分阶段,因为您可以拥有多个级别的继承来获得他们想要的东西。

            我想到了一个 IDisposable 实现。您的团队将在 Object 类上放置一个 Dispose 方法,并让它在系统中传播,无论它对对象是否有意义。

            【讨论】:

              【解决方案11】:

              反对他们的理由是:如果 正确设置了一个类(及其 公共和私人成员)然后是 接口只是额外的开销 因为那些使用该类的人是 仅限于公众成员。如果你 需要有一个接口 然后由超过 1 个班级实施 只需设置继承/多态。

              考虑以下代码:

              interface ICrushable
              {
                void Crush();
              }
              
              public class Vehicle
              {
              }
              
              public class Animal
              {
              }
              
              public class Car : Vehicle, ICrushable
              {
                public void Crush()
                {
                   Console.WriteLine( "Crrrrrassssh" );
                }
              }
              
              public class Gorilla : Animal, ICrushable
              {
                public void Crush()
                {
                   Console.WriteLine( "Sqqqquuuuish" );
                }
              }
              

              建立一个将动物与车辆相关联的类层次结构是否有意义,即使两者都可以被我的巨型破碎机破碎?没有。

              【讨论】:

                【解决方案12】:

                接口声明了任何实现它的对象都将遵守的契约。这使得确保代码质量比试图强制执行书面(不是代码)或口头结构容易得多,当一个类用接口引用装饰时,需求/合同就很清楚了,代码在你实现之前不会编译该接口完全且类型安全。

                使用接口(此处列出)还有许多其他重要原因,但可能不会像良好的老式“质量”声明那样引起管理层的共鸣;)

                【讨论】:

                  【解决方案13】:

                  嗯,我的第一反应是,如果你必须解释为什么需要接口,那无论如何都是一场艰苦的战斗 :)

                  话虽如此,除了上面提到的所有原因之外,接口是松散耦合编程的唯一方法,需要即时更新/替换组件的 n 层架构等 - 但根据个人经验,这也是架构团队负责人的深奥概念,结果我们生活在 dll 地狱中 - 在 .net 世界中没有少!

                  【讨论】:

                    【解决方案14】:

                    请提前原谅我的伪代码!

                    阅读 SOLID 原则。使用接口的 SOLID 原则有几个原因。接口允许您解耦对实现的依赖。您可以更进一步,使用 StructureMap 之类的工具,真正让耦合消失。

                    你可能习惯的地方

                    Widget widget1 = new Widget;
                    

                    这明确表示您要创建一个新的 Widget 实例。但是,如果您在另一个对象的方法内部执行此操作,您现在是在说另一个对象直接依赖于 Widget 的使用。所以我们可以说类似

                    public class AnotherObject
                    {
                        public void SomeMethod(Widget widget1)
                        {
                            //..do something with widget1
                        }
                    }
                    

                    我们在这里仍然依赖于 Widget 的使用。但至少这更可测试,因为我们可以将 Widget 的实现注入到 SomeMethod 中。现在,如果我们改用接口,我们可以进一步解耦。

                    public class AnotherObject
                    {
                        public void SomeMethod(IWidget widget1)
                        {
                            //..do something with widget1
                        }
                    }
                    

                    请注意,我们现在不需要 Widget 的特定实现,而是要求任何符合 IWidget 接口的东西。这意味着可以注入任何东西,这意味着在日常使用代码时,我们可以注入 Widget 的实际实现。但这也意味着当我们想要测试这段代码时,我们可以注入一个 fake/mock/stub(取决于您对这些术语的理解)并测试我们的代码。

                    但是我们怎样才能更进一步。通过使用 StructureMap,我们可以进一步解耦这段代码。在最后一个代码示例中,我们的调用代码看起来像这样

                    public class AnotherObject
                    {
                        public void SomeMethod(IWidget widget1)
                        {
                            //..do something with widget1
                        }
                    }
                    
                    public class CallingObject
                    {
                        public void AnotherMethod()
                        {
                            IWidget widget1 = new Widget();
                            new AnotherObject().SomeMethod(widget1);
                        }
                    }
                    

                    正如您在上面的代码中看到的,我们通过传入一个符合 IWidget 的对象来删除 SomeMethod 中的依赖项。但是在 CallingObject().AnotherMethod 中我们仍然有依赖。我们也可以使用 StructureMap 来移除这个依赖!

                    [PluginFamily("Default")]
                    public interface IAnotherObject
                    {
                        ...
                    }
                    
                    [PluginFamily("Default")]
                    public interface ICallingObject
                    {
                        ...
                    }
                    
                    [Pluggable("Default")]
                    public class AnotherObject : IAnotherObject
                    {
                        private IWidget _widget;
                        public AnotherObject(IWidget widget)
                        {
                            _widget = widget;
                        }
                    
                        public void SomeMethod()
                        {
                            //..do something with _widget
                        }
                    }
                    
                    [Pluggable("Default")]
                    public class CallingObject : ICallingObject
                    {
                        public void AnotherMethod()
                        {
                            ObjectFactory.GetInstance<IAnotherObject>().SomeMethod();
                        }
                    }
                    

                    请注意,在上面的代码中,我们没有实例化另一个对象的实际实现。因为所有东西都是为 StructurMap 连接的,所以我们可以允许 StructureMap 根据代码运行的时间和地点传入适当的实现。现在代码非常灵活,我们可以通过配置或在测试中以编程方式指定我们想要使用哪个实现。此配置可以即时完成或作为构建过程的一部分等。但它不必在任何地方硬连线。

                    【讨论】:

                      【解决方案15】:

                      Appologies,因为这不能回答您关于接口案例的问题。

                      但是我建议让相关人员阅读..

                      Head First Design Patterns

                      --李

                      【讨论】:

                        【解决方案16】:

                        接口根本不是“必需的”,这是一个设计决定。我认为您需要说服自己,为什么根据具体情况,使用接口是有益的,因为添加接口是有开销的。另一方面,为了反驳反对接口的论点,因为你可以“简单地”使用继承:继承有它的缺点,其中一个是 - 至少在 C# 和 Java 中 - 你只能使用一次继承(单一继承);但第二个——也许更重要的是——继承要求你不仅要了解父类的工作原理,还要了解所有祖先类的工作原理,这使得扩展更难但也更脆弱,因为父类发生了变化'实现很容易破坏子类。这是 GOF 书中教给我们的“组合优于继承”论点的症结所在。

                        【讨论】:

                        【解决方案17】:

                        您获得了一套指导方针,您的老板认为这些指导方针适合您的工作场所和问题领域。因此,要对更改这些准则具有说服力,并不是要证明界面总体上是一件好事,而是要证明您在工作场所需要它们。

                        您如何证明您在工作场所编写的代码中需要接口?通过在您的实际代码库中找到一个位置(不是在其他人产品的某些代码中,当然也不是在一些关于 Duck 在 IAnimal 中实现 makeNoise 方法的玩具示例中),其中基于接口的解决方案比基于继承的解决方案更好。向你的老板展示你所面临的问题,并询问修改指导方针以适应这种情况是否有意义。这是一个值得学习的时刻,每个人都在关注相同的事实,而不是用笼统和推测互相打击。

                        该指南似乎是出于对避免过度设计和过早概括的担忧。因此,如果您按照 我们应该在此处设置一个界面以防万一将来我们不得不...的方式进行争论,这是出于好意,但是对于您的老板来说,这会引起同样的后果- 工程警钟首先激发了指导方针。

                        等到有一个很好的客观案例,这既适用于您在生产代码中使用的编程技术,也适用于您与经理争论的事情。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2013-03-25
                          • 2011-03-30
                          • 2011-07-18
                          • 2017-03-14
                          • 2014-06-20
                          • 1970-01-01
                          相关资源
                          最近更新 更多