18

众所周知,接口和抽象类之间基本上有两个重要区别。

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

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

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

你的评论...

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

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

4

9 回答 9

19

解耦

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

在这种情况下的工厂模式

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

抽象和接口

界面

定义接口是最佳实践,因为它允许使用轻量级类型进行推理,并且还提供了所有继承类都必须遵守的蓝图。例如,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);
    }
}
于 2012-11-28T20:08:52.837 回答
6

我一直在寻找答案,对于这个问题来说,它们似乎都有点复杂。所以这是我(希望)更简单的答案。

  • 当代码的当前范围没有任何实现细节可用时,应使用接口。
  • 当您可以获得一些实现细节时,应该使用摘要
  • 而且,为了完整起见,当所有实现细节都可用时,您应该使用classes

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

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

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

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

于 2012-12-17T15:16:57.267 回答
3

抽象类和接口不是互斥的选择。我经常定义一个接口和一个实现该接口的抽象类。

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

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

于 2012-12-16T11:44:11.373 回答
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 设计原则告诉我们 Liskov 的替换原则意味着只有在继承的类可以替换祖先时才应该使用类继承。这意味着应该实现所有方法(不抛出新的 NotImplementedExeption())并且应该按预期运行。

我个人发现类继承在模板方法模式以及状态机的情况下很有用。在大多数情况下,诸如构建器模式之类的设计模式比深度继承链更有用。

现在回到你的问题;如果不是所有时间,接口应该被使用最多。类继承应该在内部使用,并且仅在外部用于定义目的,然后接口应该用于交互和通过工厂提供的具体实现或通过 IOC 容器注入。

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

在解耦方面,将应用程序与其实现(尤其是外部依赖项)解耦以尽量减少更改的原因非常重要。

我希望我的解释能为您指明正确的方向。请记住,最好重构工作实现,然后定义接口以公开功能。

于 2012-12-16T19:24:50.870 回答
2

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

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

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

接口还为您提供了最佳的灵活性,因为即使是从没有共同点的基类继承的类,仍然可以实现接口,并由期望该接口的组件使用。IDisposable接口就是一个很好的例子。

因此,我的结论是,对于解耦问题,您的所有组件都依赖于接口而不是基类型,并且如果您发现实现该接口的大多数类都有一个通用实现,那么就有一个实现该接口的基类并从该基类继承其他类。

于 2012-12-12T04:36:03.600 回答
1

核心区别在于:

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

  • 抽象类公开零个或多个成熟的方法,后代可以使用或覆盖这些方法,提供自己的实现。这种方法允许您定义可自定义的“默认”行为。抽象类允许您轻松添加新方法而不会出现任何问题(NotImplementedException向抽象类添加方法时真的很出色),而向接口添加方法需要您修改所有实现它的类。

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

  • 同时提供 USB 和 LAN 端口的硬盘是多接口继承的一个很好的证明
  • 带有标记为“蓝牙”的 LED 但板载没有蓝牙硬件的笔记本电脑很好地类比了不实现抽象方法的概念(你有 LED,你有小 B 符号,但屋顶下什么都没有)。

编辑 1

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

于 2012-12-12T08:35:16.673 回答
1

使用抽象类定义契约意味着你的实现者必须继承这个抽象类。由于 C# 不支持多重继承,因此这些实现者将无法拥有替代的类层次结构,这对某些人来说可能是相当有限的。换句话说,抽象类基本上会抢夺类层次结构特性的实现者,而这通常是获取或使用(框架或类库的)某些其他功能所必需的。

使用接口定义契约使实现者可以自由地使用类层次结构,以使用他们认为合适的任何方式,换句话说,提供更多的实现自由。

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

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

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

于 2012-12-16T02:10:56.043 回答
1

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

  1. 不能实例化一个抽象类
  2. 只能在抽象类中使用抽象方法
于 2015-02-06T08:45:50.397 回答
0

对接口进行编码提供了可重用性和多态性。就类实现接口而言,接口或抽象类可以传递给参数,而不是实现接口的类。Us 常见的技术问题是通过设计接口和抽象类并实现它并给出子类化具体的功能实现。想象它类似的框架。框架定义了接口和抽象类,并实现了大家通用的。抽象的由客户端根据自己的要求实现。

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 
于 2012-12-19T03:24:58.647 回答