What does “program to interfaces, not implementations” mean?

前端 未结 7 1914
情深已故
情深已故 2020-11-22 07:41

One stumbles upon this phrase when reading about design patterns.

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

相关标签:
7条回答
  • 2020-11-22 07:59

    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

    0 讨论(0)
  • 2020-11-22 08:01

    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.

    0 讨论(0)
  • 2020-11-22 08:05

    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.

    0 讨论(0)
  • 2020-11-22 08:09

    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
    0 讨论(0)
  • 2020-11-22 08:17

    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

    0 讨论(0)
  • 2020-11-22 08:20

    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.

    0 讨论(0)
提交回复
热议问题