“针对接口编程,而不是针对实现编程”是什么意思?意思是?

发布于 2024-08-29 23:35:38 字数 53 浏览 4 评论 0原文

人们在阅读设计模式时偶然发现了这句话。

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

One stumbles upon this phrase when reading about design patterns.

But I don't understand it, could someone explain this for me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

獨角戲 2024-09-05 23:35:38

接口只是合约或签名,他们不知道
有关实现的任何信息。

针对接口进行编码意味着,客户端代码始终保存由工厂提供的接口对象。工厂返回的任何实例都是 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
}

替代文本

这只是一个基本示例,
原理的实际解释是
超出了本答案的范围。

编辑

我已经更新了上面的示例并添加了一个抽象的Speaker基类。在本次更新中,我为所有扬声器添加了一个功能“SayHello”。所有发言者都会说“Hello World”。这是具有相似功能的共同特征。参考类图,你会发现Speaker抽象类实现了ISpeaker接口,并将Speak()标记为抽象,这意味着每个Speaker 实现都负责实现Speak() 方法,因为它随Speaker 的不同而不同。但所有发言者都一致说“你好”。因此,在抽象的Speaker类中,我们定义了一个表示“Hello World”的方法,并且每个Speaker实现都将派生出SayHello()方法。

考虑一下 SpanishSpeaker 无法 Say Hello 的情况,在这种情况下,您可以重写西班牙语扬声器的 SayHello() 方法并引发适当的异常。

请注意,我们有
未对界面进行任何更改
是扬声器。以及客户端代码和
peakerFactory 也不受影响
不变。这就是我们通过接口编程实现的目标。

我们可以通过简单地添加一个基本抽象类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
}

替代文字

Interfaces are just contracts or signatures and they don't know
anything about implementations.

Coding against interface means, the client code always holds an Interface object which is supplied by a factory. Any instance returned by the factory would be of type Interface which any factory candidate class must have implemented. This way the client program is not worried about implementation and the interface signature determines what all operations can be done. This can be used to change the behavior of a program at run-time. It also helps you to write far better programs from the maintenance point of view.

Here's a basic example for you.

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
}

alt text

This is just a basic example and
actual explanation of the principle is
beyond the scope of this answer.

EDIT

I have updated the example above and added an abstract Speaker base class. In this update, I added a feature to all Speakers to "SayHello". All speaker speak "Hello World". So that's a common feature with similar function. Refer to the class diagram and you'll find that Speaker abstract class implement ISpeaker interface and marks the Speak() as abstract which means that the each Speaker implementation is responsible for implementing the Speak() method since it varies from Speaker to Speaker. But all speaker say "Hello" unanimously. So in the abstract Speaker class we define a method that says "Hello World" and each Speaker implementation will derive the SayHello() method.

Consider a case where SpanishSpeaker cannot Say Hello so in that case you can override the SayHello() method for Spanish Speaker and raise proper exception.

Please note that, we have
not made any changes to Interface
ISpeaker. And the client code and
SpeakerFactory also remain unaffected
unchanged. And this is what we achieve by Programming-to-Interface.

And we could achieve this behavior by simply adding a base abstract class Speaker and some minor modification in Each implementation thus leaving the original program unchanged. This is a desired feature of any application and it makes your application easily maintainable.

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
}

alt text

旧人九事 2024-09-05 23:35:38

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

实施是实际的行为。举例来说,您有一个方法 sort()。您可以实现快速排序或合并排序。只要接口不改变,这对于调用排序的客户端代码来说应该不重要。

Java API 和 .NET Framework 等库大量使用接口,因为数以百万计的程序员使用所提供的对象。这些库的创建者必须非常小心,不要更改这些库中类的接口,因为这会影响所有使用该库的程序员。另一方面,他们可以随心所欲地改变实现。

如果作为程序员,您针对实现进行编码,那么一旦它发生更改,您的代码就会停止工作。因此,请这样考虑接口的好处:

  1. 它隐藏了您不需要知道的内容,从而使对象更易于使用。
  2. 它提供了对象行为方式的契约,因此您可以依赖它

Think of an interface as a contract between an object and its clients. That is the interface specifies the things that an object can do, and the signatures for accessing those things.

Implementations are the actual behaviours. Say for example you have a method sort(). You can implement QuickSort or MergeSort. That should not matter to the client code calling sort as long as the interface does not change.

Libraries like the Java API and the .NET Framework make heavy use of interfaces because millions of programmers use the objects provided. The creators of these libraries have to be very careful that they do not change the interface to the classes in these libraries because it will affect all programmers using the library. On the other hand they can change the implementation as much as they like.

If, as a programmer, you code against the implementation then as soon as it changes your code stops working. So think of the benefits of the interface this way:

  1. it hides the things you do not need to know making the object simpler to use.
  2. it provides the contract of how the object will behave so you can depend on that
你的心境我的脸 2024-09-05 23:35:38

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

通常,实现是通过构造函数或方法调用注入到代码中的。因此,您的代码了解接口或抽象类,并且可以调用此合约中定义的任何内容。当使用实际对象(接口/抽象类的实现)时,调用是在该对象上进行的。

这是里氏替换原理 (LSP) 的子集, L of SOLID 原则。

.NET 中的一个示例是使用 IList 而不是 ListDictionary 进行编码,因此您可以使用任何实现 IList 的类 在代码中可互换:

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

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

It means that you should try to write your code so it uses an abstraction (abstract class or interface) instead of the implementation directly.

Normally the implementation is injected into your code through the constructor or a method call. So, your code knows about the interface or abstract class and can call anything that is defined on this contract. As an actual object (implementation of the interface/abstract class) is used, the calls are operating on the object.

This is a subset of the Liskov Substitution Principle (LSP), the L of the SOLID principles.

An example in .NET would be to code with IList instead of List or Dictionary, so you could use any class that implements IList interchangeably in your code:

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

Another example from the Base Class Library (BCL) is the ProviderBase abstract class - this provides some infrastructure, and as importantly means all provider implementations can be used interchangeably if you code against it.

冷弦 2024-09-05 23:35:38

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

问题的解决方案是在 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;
}

补充说明:
您是一位拥有多辆汽车的车主。您制定想要外包的服务。在我们的案例中,我们希望外包所有汽车的维护工作。

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

    替代方法。

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

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

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

If you were to write a Car Class in Combustion-Car era, then there is a great chance you would implement oilChange() as a part of this Class. But, when electric cars are introduced, you would be in trouble as there is no oil-change involved for these cars, and no implemention.

The solution to the problem is to have a performMaintenance() Interface in Car class and hide details inside appropriate implementation. Each Car type would provide its own implementation for performMaintenance(). As a owner of a Car all you have to deal with is performMaintenance() and not worry about adapting when there is a CHANGE.

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

Additional explanation:
You are a car owner who owns multiple cars. You carve out the service that you want to outsource. In our case we want to outsource the maintenance work of all cars.

  1. You identify the contract(Interface) that holds good for all your cars and service providers.
  2. Service providers come out with a mechanism to provide the service.
  3. You don't want to worry about associating the car type with the service provider. You just specify when you want to schedule maintenance and invoke it. Appropriate service company should jump in and perform the maintenance work.

    Alternate approach.

  4. You identify the work(can be a new interface Interface) that holds good for all your cars.
  5. You come out with a mechanism to provide the service. Basically you are going to provide the implementation.
  6. You invoke the work and do it yourself. Here you are going to do the job of appropriate maintenance work.

    What is the downside of the 2nd approach?
    You may not be the expert at finding the best way to do the maintenance. Your job is to drive the car and enjoy it. Not to be in the business of maintaining it.

    What it the downside of the first approach?
    There is the overhead of finding a company etc. Unless you are a rental car company, it may not be worth the effort.

人疚 2024-09-05 23:35:38

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

This statement is about coupling. One potential reason for using object oriented programming is reuse. So for example you can split your algorithm among two collaborating objects A and B. This might be useful for later creation of another algorithm, which might reuse one or another of the two objects. However, when those objects communicate (send messages - call methods), they create dependencies among each other. But if you want to use one without the other, you need to specify what should do some other object C do for object A if we replace B. Those descriptions are called interfaces. This allows object A to communicate without change with different object relying on the interface. The statement you mentioned says that if you plan to reuse some part of an algorithm (or more generally a program), you should create interfaces and rely on them, so you might change the concrete implementation any time without changing other objects if you use the declared interface.

泅渡 2024-09-05 23:35:38

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

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

1) 测试。

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

Worker类-> DAL类
不过,让我们添加一个接口。

Worker类->伊达尔-> DAL 类。

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

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

Worker类->伊达尔->如果假达尔。

2) 重用

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

WorkerClass ->伊达尔-> DALClass

WorkerClass ->伊达尔-> MongoDB类

As others have said, it means that your calling code should only know about an abstract parent, NOT the actual implementing class that will do the work.

What helps to understand this is the WHY you should always program to an interface. There's many reasons, but two of the easiest to explain are

1) Testing.

Let's say I have my entire database code in one class. If my program knows about the concrete class, I can only test my code by really running it against that class. I'm using -> to mean "talks to".

WorkerClass -> DALClass
However, let's add an interface to the mix.

WorkerClass -> IDAL -> DALClass.

So the DALClass implements the IDAL interface, and worker class ONLY calls through this.

Now if we want to write tests for the code, we could instead make a simple class that just acts like a database.

WorkerClass -> IDAL -> IFakeDAL.

2) Reuse

Following the example above, let's say we want to move from SQL Server (which our concrete DALClass uses) to MonogoDB. This would take major work, but NOT if we've programmed to an interface. In that case we just write the new DB class, and change (via the factory)

WorkerClass -> IDAL -> DALClass

to

WorkerClass -> IDAL -> MongoDBClass

漫雪独思 2024-09-05 23:35:38

接口描述了功能。在编写命令式代码时,讨论您正在使用的功能,而不是特定的类型或类。

interfaces describe capabilities. when writing imperative code, talk about the capabilities you are using, rather than specific types or classes.

缪败 2024-09-05 23:35:38

“针对接口编程,而不是实现”是与“四人帮”相关的设计原则,这是一群撰写“设计模式:可重用面向对象软件的元素”的作者。

主要思想是您的代码应该设计为依赖于抽象接口而不是具体实现。

例如,如果您有一个缓存接口,您的应用程序将使用它,而不必担心它背后是否是内存、文件或 Redis 缓存实现。

"Program to interfaces, not implementations" is a design principle associated with the Gang of Four, a group of authors who wrote 'Design Patterns: Elements of Reusable Object-Oriented Software'

The main idea is that your code should be designed to depend on abstract interfaces instead of a concrete implementation.

If you have a cache interface, for example, your application will consume it without worrying if it's a memory, file or redis cache implementation behind it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文