接口和抽象类在解耦方面有什么区别?

18

我们知道接口和抽象类之间基本上有两个重要的区别。

  1. 我们可以在抽象类中定义函数。当我们想在一个类中添加一个函数而不需要跟踪它的所有实现时,这是有优势的。

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

我刚了解到我们可以从耦合的角度区分它们?

您的评论...

如果您能够提供一个非常基本的链接,用于解释接口和抽象类的解耦合,那就更好了?

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


9
黄金法则:抽象:是-一个和接口:能够做到。例如:亚洲人是一个人,电话可以播放MP3(还有CD播放器、iPod等)。 - vtortola
9个回答

19

解耦

在编程和设计中,通常是尽可能少地依赖其他模块来创建可重用的代码。

工厂模式在这个上下文中

使用工厂模式时,您有一个集中的工厂可以创建对象,而不必自己定义它们。这将由对象的定义确定。

抽象和接口

接口

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

抽象

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

编辑

说明

这里您将看到一个使用接口和抽象类的简单继承示例。当接口被抽象类继承并自定义其方法时,解耦发生。这允许类继承抽象类并仍然具有与接口相同的类型。优势在于,继承抽象类的类可以在预期类型为原始接口时使用。

解耦

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

示例

接口定义

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);
    }
}

我看不出为什么要将你的 WidgetExample 类标记为抽象类。它没有公开任何应该由子类实现的抽象方法。 - mipe34
@TravisJ,你能给一个反对解耦的例子吗? - Pankaj
@ytftyffty - 没有,想不出来。没有理由不进行解耦。 - Travis J
这就像是一个工厂模式,它解释了你不应该在其他类中创建一个类的实例,而只需将类实例传递给函数参数,并且函数参数将是一个接口。但是,在解耦方面,接口和抽象类之间有什么区别呢?我的理解正确吗? - Pankaj

6
我一直在查看答案,它们都对问题有些复杂了。所以这是我(希望)更简单的答案:
- 当代码的当前范围没有任何实现细节时,应该使用接口。 - 当您有一些实现细节时,应该使用抽象类。 - 当所有实现细节都可用时,应该使用类。
就解耦而言,虽然我有点同意Shelakel的观点,但为了回答这个问题并完全说明分离的设计实践,我建议如下:
- 始终使用接口来定义外部行为。 - 当您拥有一些实现细节时,请使用抽象类来定义它们,但在抽象类上实现接口,并从中继承。
这确保了以后如果您需要在新的实现中更改一些模糊的实现细节,则可以这样做而无需修改现有的抽象类,并且还可以将不同的实现类型分组到不同的抽象类中。
编辑:我忘记包含链接了 :) http://www.codeproject.com/Articles/11155/Abstract-Class-versus-Interface

我们通常使用业务层数据访问层(包含抽象函数)DataAccess.SqlServer层。对吧?尽管我们了解业务需求,为什么还要创建数据访问层(包含抽象函数)呢?为什么不能让业务逻辑层直接访问DataAccess.SqlServer层呢?我在阅读你的话之后提出了这个问题:当当前代码范围内没有可用的实现细节时,应该使用接口 - Pankaj
当我提到作用域时,我指的是层级结构。解耦的想法有两个主要原因:
  1. 将代码块模块化以便于维护。
  2. 能够替换某些代码块而不干扰外部代码的操作。
如果您决定更改为Oracle数据库,您可以通过实现DataAccess.SqlServer层的替换层,并可能在数据访问层中进行一些小修改,从而实现更改。您的业务层应该没有任何更改。这是理论...但我认为我从未见过它在实际中完美运行 :)
- major-mann

3
抽象类和接口并不是互斥的选择。我经常定义一个接口和一个实现该接口的抽象类。
接口确保最大程度的解耦,因为它不会强制要求您的类属于特定的继承层次结构,因此您的类可以继承自任何其他类。换句话说,任何类都可以继承自一个接口,而已经继承了其他类的类不能继承自抽象类。
另一方面,在抽象类中,您可以提取出所有实现共有的代码,而在接口中,您必须从头开始实现所有内容。 因此,通常最好的解决方案是同时使用抽象类和接口,这样如果可能的话,可以从抽象类中重用公共代码,如果需要的话,可以从头开始重新实现接口。

3

仅为了解耦而进行解耦是徒劳无功的。

接口旨在用于集成,其中不需要知道具体细节即可使用(例如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设计原则告诉我们,里氏替换原则意味着只有当继承的类可以替代祖先类时才应该使用类继承。这意味着所有方法都应该被实现(不要抛出NotImplementedExeption()),并且应该按预期行事。
我个人发现,在模板方法模式和状态机的情况下,类继承很有用。建造者模式等设计模式在大多数情况下比深层次的继承链更有用。
现在回到你的问题上;接口应该在大多数情况下使用。类继承应该在内部使用,并且仅在定义目的下外部使用,之后应该使用接口进行交互,并通过工厂或通过IOC容器注入提供具体实现。
理想情况下,在使用外部库时,应创建一个接口并实现适配器以仅公开所需的功能。这些组件中的大多数允许在运行前或运行时进行配置,以通过IOC容器解析。
在解耦方面,将应用程序与其实现(特别是外部依赖项)解耦非常重要,以最小化更改的原因。
希望我的解释能指导你正确的方向。请记住,最好先重构工作实现,然后再定义接口以公开功能。

2

我不会讨论这两种构造的优缺点,因为已经有了足够的资源。

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

以上观点的原因是,接口可以将“依赖组件”所需的最小暴露范围缩小到绝对最小程度,如果它需要单一方法则接口可以很容易地实现,甚至可以是没有任何方法的标记接口。通过基类(抽象或具体),这可能会很困难,因为它应该为该基类实现所有“共同”功能。因此,依赖于“基本类型”的组件将自动“看到”即使它们不需要它们以用于其目的的所有通用功能。

接口还给你最大的灵活性,因为即使从没有共同之处的基类继承的类仍然可以实现一个接口,并被期望该接口的组件使用。 IDisposable接口是很好的例子。

因此,我的结论是,为了解耦所有组件都应该依赖于接口而不是基本类型,如果您发现大多数实现该接口的类具有共同的实现,则需要一个实现该接口的基类,并从该基类继承其他类。


1

理解和记住接口抽象类之间的区别最好的方法是记住抽象类是一个普通类,你可以用抽象类做任何你可以用普通类做的事情,只有两个例外

  1. 你不能实例化一个抽象类
  2. 你只能在抽象类中拥有抽象方法

1

核心区别在于:

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

  • 抽象类公开零个或多个完整的方法,后代可以使用或覆盖它们,并提供自己的实现。这种方法允许您定义可定制的“默认”行为。抽象类允许您轻松添加新方法而不会出现问题(当向抽象类添加方法时,NotImplementedException真正发挥作用),而将方法添加到接口需要修改实现它的所有类。

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

  • 提供USB和LAN端口的硬盘是多接口继承的良好示例。
  • 具有标有“蓝牙”的LED但没有蓝牙硬件的笔记本电脑是未实现抽象方法概念的良好类比(你有LED,你有小B符号,但屋顶下面什么也没有)。

编辑1

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


你能解释一下这句话的意思吗?“使用抽象类(在Visual Basic中为MustInherit)来代替接口,以便将契约与实现分离。” - Pankaj

1
使用抽象类定义合同意味着您的实现者必须从此抽象类继承。由于C#不支持多重继承,这些实现者将无法拥有替代类层次结构,这可能对某些人来说相当限制。换句话说,抽象类基本上剥夺了实现者的类层次结构特性,而这通常需要获取或使用一些其他功能(框架或类库)。
使用接口定义合同意味着类层次结构可以自由使用,使实现者可以按照自己的方式使用任何方法,换句话说,提供了更多的实现自由度。
从评估标准的角度来看,当我们在这里谈论耦合时,我们可以谈论三个可分离的作者的关注点,即使用(调用)API/合同的客户端、API/合同的定义者和API/合同的实现者;我们可以谈论自由度(限制越少,越好)、封装性(必要的认知越少,越好)以及面对变化的弹性。
我认为,与抽象类相比,接口导致耦合更松散,特别是在定义者和实现者之间,因为它为实现者提供了更高的自由度。
另一方面,当涉及版本控制时,您可以至少向抽象类添加另一种方法,而无需更新子类实现,前提是添加的方法在抽象类中有一个实现。 跨DLL边界版本控制通常意味着添加另一个接口,这更加复杂,难以推出。(当然,如果您可以将所有实现重构在一起(例如,因为它们都在同一个DLL中),则这不是问题)。

0
编程到接口提供了可重用性和多态性。只要类实现了接口,就可以将接口或抽象类传递给参数,而不是实现接口的类。通过设计接口和抽象类并实现它以及给子类提供特定的功能实现来处理常见的技术问题。想象一下它就像框架。框架定义接口和抽象类,并实现所有共同的内容。那些抽象的部分由客户端根据自己的需求实现。
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 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接