繁体   English   中英

接口和抽象类在解耦方面的区别?

[英]Difference between Interface and Abstract class in terms of Decoupling?

我们知道Interface和Abstract类之间基本上有两个重要的区别。

  1. 我们可以在抽象类中使用函数定义。 当我们想要在类中添加函数而不需要追踪它的所有实现时,这是有利的。

  2. 我们可以有多个接口实现。

我刚才知道我们可以在解耦方面区分它们吗?

你的评论...

另外,如果您能提供一个非常基本的链接来解释接口和抽象类的解耦?

我们通常使用业务逻辑层数据访问层 (包含抽象函数)和DataAccess.SqlServer层 对? 尽管我们知道业务需求,但为什么我们要创建数据访问层 (包含抽象函数),为什么业务逻辑层不能直接访问DataAccess.SqlServer层

解耦

在编程和设计中,这通常是使代码可以在尽可能少的依赖性下重复使用的行为。

在这种情况下的工厂模式

使用工厂模式时,您有一个集中式工厂,可以创建对象而无需自己定义它们。 这取决于对象的定义。

摘要和界面

接口

定义接口是最佳实践,因为它允许使用轻量级类型进行推理,并且还提供所有继承类必须遵守的蓝图。 例如, IDisposable必须实现Dispose方法。 请注意,这与接口分离,因为继承IDisposable每个类都将定义自己的Dispose方法的函数。

抽象

Abstract类似于接口,因为它用于继承和推理,但它包含所有类将继承的定义。 每辆汽车都会有一个引擎,所以汽车的一个好的抽象类可以包括一套预定义的引擎方法。

编辑

说明

在这里,您将看到一个使用接口和抽象类的继承的简单示例。 当接口由抽象类继承然后定制它的方法时,就会发生解耦。 这允许类继承抽象类,并且仍然具有与接口相同的类型。 优点是当期望的类型是原始接口时,可以使用继承抽象类的类。

解耦

该优点允许使用符合预期接口的任何实现。 因此,可以编写和传入许多不同的重载。这是一个例子。

接口定义

public interface IReady
{
    bool ComputeReadiness();
}

遗产

public abstract class WidgetExample : IReady
{
    public int WidgetCount { get; set; }
    public int WidgetTarget { get; set; }
    public bool WidgetsReady { get; set; }

    public WidgetExample()
    {
        WidgetCount = 3;
        WidgetTarget = 45;
    }

    public bool ComputeReadiness()
    {
        if (WidgetCount < WidgetTarget)
        {
            WidgetsReady = false;
        }
        return WidgetsReady;
    }
}


public class Foo : WidgetExample
{
    public Foo()
    {
        this.WidgetTarget = 2;
    }
}

public class Bar : IReady
{
    public bool ComputeReadiness()
    {
        return true;
    }
}

解耦

public class UsesIReady
{
    public bool Start { get; set; }
    public List<string> WidgetNames { get; set; }

    //Here is the decoupling. Note that any object passed
    //in with type IReady will be accepted in this method
    public void BeginWork(IReady readiness)
    {
        if (readiness.ComputeReadiness())
        {
            Start = true;
            Work();
        }
    }

    private void Work()
    {
        foreach( var name in WidgetNames )
        {
            //todo: build name
        }
    }
}

多态性

public class Main
{
    public Main()
    {
        //Notice that either one of these implementations 
        //is accepted by BeginWork

        //Foo uses the abstract class
        IReady example = new Foo();
        UsesIReady workExample = new UsesIReady();
        workExample.BeginWork(example);

        //Bar uses the interface
        IReady sample = new Bar();
        UsesIReady workSample = new UsesIReady();
        workSample.BeginWork(sample);
    }
}

我一直在寻找答案,这些问题似乎都有些复杂。 所以这是我(希望)更简单的答案。

  • 没有任何实现细节可用于当前代码范围时,应使用接口
  • 当您可以使用某些实施细节时,应使用摘要
  • 而且,为了完整性,当所有实现细节都可用时,您应该使用

在解耦方面,虽然我有点同意Shelakel,为了这个问题的目的,并说明完全脱钩的设计实践,我建议如下:

  • 始终使用接口来定义外部行为。
  • 当您有一些可用的实现细节时 ,使用抽象类来定义它们,但是在抽象类上实现接口 ,并依次从这些类继承。

这确保了以后如果您需要在新实现中更改一些模糊的实现细节,您可以在不修改现有抽象类的情况下执行此操作,并且还能够将不同的实现类型分组到不同的抽象类中。

编辑:我忘了包含链接:) http://www.codeproject.com/Articles/11155/Abstract-Class-versus-Interface

抽象类和接口不是完全独立的选择。 我经常定义一个接口和一个实现该接口的抽象类。

接口确保最大的解耦,因为它不会强制您的类属于特定的继承层次结构,因此您的类可以继承任何其他类。 换句话说,任何类都可以从接口继承,而已经从其他类继承的类不能从接口类继承。

另一方面,在抽象类中,您可以分解出所有实现共有的代码,而使用Interfaces,您必须从头开始实现所有内容。 总而言之,通常最好的解决方案是使用BOTH一个抽象类和一个接口,因此如果可能的话,可以从重新使用抽象类中包含的公共代码转移到从头开始重新实现接口,如果需要的话。

为了解耦而去耦是徒劳的。

接口用于集成,其中不需要知道具体细节(例如SendEmail())。 常见用途包括组件,服务,存储库以及IOC和通用实现的标记。

包含接口的泛型类型约束的扩展方法允许类似于Scala中具有类似可组合性的特征的功能。

public interface IHasQuantity { double Quantity { get; } }
public interface IHasPrice { decimal PricePerUnit { get; } }

public static class TraitExtensions
{
    public static decimal CalculateTotalPrice<T>(this T instance)
        where T : class, IHasPrice, IHasQuantity
    {
        return (decimal)instance.Quantity * instance.PricePerQuantity;
    }
}

在我看来,抽象类和类继承被过度使用。

SOLID设计原则告诉我们,Liskov的替换原则意味着只有在继承的类可以替代祖先的情况下才应该使用类继承。 这意味着应该实现所有方法(不抛出新的NotImplementedExeption())并且应该按预期运行。

我个人已经发现类继承在Template Method模式和状态机的情况下都很有用。 在大多数情况下,构建模式等设计模式比深层继承链更有用。

现在回到你的问题; 如果不是所有时间都应该使用大多数接口。 类继承应该在内部使用,并且只能在外部用于定义,之后应该使用接口进行交互,并通过工厂提供具体实现或通过IOC容器注入。

理想情况下,在使用外部库时,应创建接口并实现适配器以仅公开所需的功能。 大多数这些组件允许预先配置或在运行时通过IOC容器进行解析。

在解耦方面,将应用程序与其实现(尤其是外部依赖关系)分离以最小化更改原因非常重要。

我希望我的解释能指出你正确的方向。 请记住,优先重构工作实现,然后定义接口以公开功能。

我不打算讨论这两种结构的优缺点,因为有足够的资源。

但是,就一个组件与另一个组件“解耦”而言,接口继承比抽象类或一般的类继承要好得多(实际上我不认为抽象或不抽象在解耦方面没有太大的区别,因为所有abstract does是在没有具体实现的情况下阻止实例化的类)。

上述论点的原因是,接口允许您将暴露范围缩小到“依赖组件”所需的绝对最小值,如果它需要单个方法接口可以轻松地做到这一点,或者甚至是没有任何方法的标记接口。 对于基类(抽象或具体),这可能很难,因为它应该为该基础实现所有“通用”功能。 因此,依赖于“基本类型”的组件将自动“看到”所有常见功能,即使它不需要它们用于它的目的。

接口还为您提供了最大的灵活性,因为即使是从没有任何共同点的基础继承的类,仍然可以实现接口,并且可以被期望该接口的组件使用。 这方面的好例子是IDisposable接口。

所以,我的结论是解耦关注所有组件都依赖于接口而不是基类型,如果你发现实现该接口的大多数类都有一个共同的实现,那么就有一个实现该接口的基类并从该基类继承其他类。

核心区别在于:

  • 接口公开零个或多个方法签名 ,所有后代必须依次实现(否则代码甚至不会编译)。 接口公开的方法可以隐式实现(从接口派生的每个类型都可以访问它们)或显式实现(只有在将对象类型化为接口类型本身时才能访问方法)。 在这个问题中可以找到更多细节和示例

  • 抽象类暴露零个或多个完整的方法,后代可以使用或覆盖,提供自己的实现。 此方法允许您定义可自定义的“默认”行为。 抽象类允许您轻松添加没有问题的新方法(在向抽象类添加方法时, NotImplementedException真的很闪亮),而向接口添加方法则需要修改实现它的所有类。

最后一点是,一个类可以同时实现多个接口。 一些现实世界的例子可能是:

  • 提供USB和LAN端口的硬盘驱动器是多接口继承的良好示范
  • 笔记本电脑上有一个标有“蓝牙”但没有蓝牙硬件的笔记本电脑很好地类比了没有实现抽象方法的概念(你有LED,你有小B符号,但屋顶下没什么)。

编辑1

这是一个MSDN链接,解释了如何在接口和类之间进行选择。

使用抽象类定义合同意味着您的实现者必须从此抽象类继承。 由于C#不支持多重继承,因此这些实现者将无法拥有备用类层次结构,这对某些人来说可能非常有限。 换句话说,抽象类基本上会破坏类层次结构功能的实现者,这通常需要获取或使用某些其他功能(框架或类库)。

使用接口定义合同会使类层次结构免费供您的实现者使用他们认为合适的任何方式,换句话说,提供更多的实现自由。

从评估标准的角度来看,当我们讨论耦合时,我们可以谈到三个可分离作者的关注点,客户使用(调用)API /合同,API /合同的定义者以及API /合同的实现者; 我们可以谈论自由(限制越少越好),封装(必要的意识越少越好),以及面对变化时的弹性。

我认为接口导致比抽象类更松散的耦合,特别是在定义者和实现者之间,因为实现者提供了更高的自由度。

另一方面,当涉及到版本控制时,您至少可以向抽象类添加另一个方法,而不必更新子类实现,前提是添加的方法在抽象类中有实现。 跨DLL边界的版本控制接口通常意味着添加另一个接口,推出起来要复杂得多。 (当然,如果您可以将所有实现重构在一起(例如,因为它们都在同一个DLL中),这不是问题。)

理解和记住接口抽象类之间差异的最好方法是,要记住抽象类是一个普通类 ,你可以用普通类抽象类 ,但有两个例外

  1. 无法实例化抽象类
  2. 只能在抽象类中使用抽象方法

编码到接口提供了可重用性和多态性。就类实现接口而言,接口或抽象类可以传递给参数而不是实现接口的类。在设计接口和抽象类中处理常见的技术问题并实现它并给出子类化特定的功能实现。想象它就像framework.Framework定义接口和抽象类并实现它对所有人都是通用的。那些抽象的是客户端根据自己的要求实现的。

public interface Polymorphism{
void run();
Void Bark();
Energy getEnergy(Polymorphism test);
Public abstract class EnergySynthesis implements Polymorphism{
abstract void Energy();
Void Bark(){
 getEnergy(){

}
void run(){
getEnergy();

}public EnegyGeneartion extends EnergySynthesis  {
Energy getEnergy(Polymorphism test){
return new Energy( test); 
}
MainClass{

EnegyGeneartion test=new EnegyGeneartion ();
test.getEnergy(test);
test.Bark()
.
.
.
.
.
//here in Energy getEnergy(Polymorphism test) any class can be passed as parameter that implemets interface 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM