"程序接口,而非实现"是什么意思?

nev*_*ame 119 oop ooad design-patterns interface software-design

在阅读有关设计模式时,人们会发现这句话.

但是我不明白,有人可以帮我解释一下吗?

thi*_*eek 133

接口只是合同或签名,他们对实现一无所知.

对接口进行编码意味着,客户端代码始终保存由工厂提供的Interface对象.工厂返回的任何实例都是Interface类型,任何工厂候选类必须已实现.这样客户端程序就不会担心实现,接口签名决定了所有操作都可以完成.这可用于在运行时更改程序的行为.从维护的角度来看,它还可以帮助您编写更好的程序.

这是一个基本的例子.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

alt text http://ruchitsurati.net/myfiles/interface.png

这只是一个基本的例子,对该原理的实际解释超出了本答案的范围.

编辑

我已经更新了上面的示例,并添加了一个抽象的Speaker基类.在此更新中,我向所有Spakers添加了一个功能"SayHello".所有发言者都说"Hello World".这是具有类似功能的常见功能.请参阅类图,您会发现Speaker抽象类实现了ISpeaker接口,并将Speak()标记为抽象,这意味着每个Speaker实现负责实现Speak方法,因为它在Speaker和Speaker之间有所不同.但是所有发言者都一致地说"你好".因此,在抽象的Speaker类中,我们定义了一个表示"Hello World"的方法,每个Speaker实现都将派生出SayHello方法.

考虑一个案例,其中SpanishSpeaker不能说你好,所以在这种情况下你可以覆盖西班牙语说话者的SayHello方法并引发适当的异常.

请注意,我们尚未对Interface ISpeaker进行任何更改.并且客户端代码和SpeakerFactory也不受影响.这就是我们通过Programming-to-Interface实现的.

我们可以通过简单地添加一个基本抽象类Speaker和每个实现中的一些小修改来实现这种行为,从而保持原始程序不变.这是任何应用程序的理想功能,它使您的应用程序易于维护.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

alt text http://demo.ruchitsurati.net/myfiles/interface1.png

  • 对接口的编程不仅仅是**关于引用变量的类型.这也意味着您不会对您的实现使用任何隐含的假设.例如,如果你使用`List`作为类型,你仍然可以通过反复调用`get(i)`来假设随机访问很快. (18认同)
  • 工厂与编程接口正交,但我认为这种解释使它看起来好像是它的一部分. (13认同)

Vin*_*nie 26

将接口视为对象与其客户之间的契约.也就是说,接口指定了对象可以执行的操作,以及用于访问这些内容的签名.

实现是实际行为.比方说,你有一个方法sort().您可以实现QuickSort或MergeSort.只要接口没有改变,这对调用排序的客户端代码无关紧要.

像Java API和.NET Framework这样的库大量使用接口,因为数百万程序员使用提供的对象.这些库的创建者必须非常小心,他们不会更改这些库中的类的接口,因为它会影响使用该库的所有程序员.另一方面,他们可以根据自己的喜好改变实施方式.

如果作为程序员,您对代码进行编码,那么一旦它发生更改,代码就会停止工作.所以以这种方式考虑接口的好处:

  1. 它隐藏了你不需要知道的东西,使对象更容易使用.
  2. 它提供了对象如何表现的契约,因此您可以依赖它


Ode*_*ded 15

这意味着您应该尝试编写代码,以便它使用抽象(抽象类或接口)而不是直接实现.

通常,实现通过构造函数或方法调用注入到代码中.因此,您的代码知道接口或抽象类,并可以调用此合同上定义的任何内容.作为实际对象(接口/抽象类的实现),使用对象进行调用.

这是Liskov Substitution Principle(LSP)的一个子集,SOLID原则的L.

.NET中的一个例子是IList代替Listor 代码Dictionary,因此你可以使用IList在代码中可互换实现的任何类:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}
Run Code Online (Sandbox Code Playgroud)

基类库(BCL)中的另一个示例是ProviderBase抽象类 - 这提供了一些基础结构,并且重要的是意味着如果您对其进行编码,所有提供程序实现可以互换使用.

  • 客户端不与接口交互,而是通过接口:)对象通过方法(消息)与其他对象交互,接口是一种语言 - 当你知道某个对象(人)实现(说)英语(IList) ),你可以使用它,而不需要更多地了解该对象(他也是意大利人),因为在这种情况下不需要它(如果你想寻求帮助,你不需要知道他也说意大利语如果你懂英语)。 (2认同)

Mat*_*son 6

正如其他人所说,这意味着您的调用代码应该只知道抽象父级,而不是执行该工作的实际实现类。

有助于理解这一点的是为什么您应该始终对接口进行编程。原因有很多,但最容易解释的有两个:

1)测试。

假设我将整个数据库代码放在一个类中。如果我的程序知道具体的类,我只能通过针对该类实际运行它来测试我的代码。我使用 -> 来表示“与”交谈。

WorkerClass -> DALClass 但是,让我们添加一个接口。

WorkerClass -> IDAL -> DALClass。

因此 DALClass 实现了 IDAL 接口,并且工作器类仅通过此调用。

现在,如果我们想为代码编写测试,我们可以创建一个简单的类,它的作用就像数据库一样。

WorkerClass -> IDAL -> IFakeDAL。

2)重复利用

按照上面的示例,假设我们想要从 SQL Server(我们的具体 DALClass 使用)迁移到 MonogoDB。这将需要大量工作,但如果我们已经编程到接口则不需要。在这种情况下,我们只需编写新的数据库类,然后进行更改(通过工厂)

WorkerClass -> IDAL -> DALClass

WorkerClass -> IDAL -> MongoDBClass


Gab*_*bák 5

这个说法是关于耦合的。使用面向对象编程的一个潜在原因是重用。例如,您可以在两个协作对象 A 和 B 之间拆分您的算法。这可能对以后创建另一个算法很有用,该算法可能会重用两个对象中的一个或另一个。但是,当这些对象进行通信(发送消息 - 调用方法)时,它们会在彼此之间创建依赖关系。但是如果你想使用一个而没有另一个,你需要指定如果我们替换B,其他对象C应该为对象A做什么。这些描述称为接口。这允许对象 A 与依赖接口的不同对象进行通信而无需更改。您提到的声明说,如果您计划重用算法的某些部分(或更一般地说是程序),您应该创建接口并依赖它们,


Rag*_*ada 5

如果您要在“燃烧汽车”时代编写汽车类,那么您很有可能将oilChange()作为该类的一部分来实现。但是,当引入电动汽车时,您会遇到麻烦,因为这些汽车不涉及换油,也没有实施。

解决该问题的方法是在Car类中具有performMaintenance()接口,并在适当的实现中隐藏详细信息。每种Car类型都会为performMaintenance()提供其自己的实现。作为汽车的所有者,您所要做的就是performMaintenance(),而不用担心在有变更时就进行调整。

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

附加说明:您是拥有多辆车的车主。您决定要外包的服务。在我们的案例中,我们希望将所有汽车的维护工作外包。

  1. 您确定适合所有汽车和服务提供商的合同(接口)。
  2. 服务提供商提供了一种提供服务的机制。
  3. 您无需担心将汽车类型与服务提供商相关联。您只需要指定何时计划维护并调用它即可。适当的服务公司应介入并执行维护工作。

    替代方法。

  4. 您确定适合所有汽车的工作(可以是新的接口Interface)。
  5. 提供了一种提供服务的机制。基本上,您将提供实现。
  6. 您可以调用工作并自己完成。在这里,您将进行适当的维护工作。

    第二种方法的缺点是什么?您可能不是寻找最佳维护方法的专家。您的工作是驾驶汽车并享受它。不从事维护业务。

    第一种方法的缺点是什么?寻找公司等会产生开销。除非您是租车公司,否则可能不值得付出努力。