When do I use abstract classes versus interfaces w

2020-06-13 11:40发布

问题:

I have been reading some articles about the SOLID principles and dependency Inversion. From my point of view, I must use an interface to talk to any class. My classes are chatting by using interfaces.

First Question:

I am using an abstract class but for the second part of my code, I use an Interface.

Usage1


namespace DependencyInjection
{

    public interface IMessage
    {

    }
    public abstract class Message
    {
        public abstract void Get();
        public abstract void Send();
    }

    public class Sms : Message, IMessage
    {
        public override void Get()
        {
            Console.WriteLine("Message Get!");
        }
        public override void Send()
        {
            Console.WriteLine("Message Send!");
        }
    }

    public class MessageManager
    {
        private IMessage _message;

        public Sms Sms
        {
            get { return _message as Sms; }
            set { _message = value; }
        }

        public MessageManager(IMessage message)
        {
            _message = message;
        }

    }
}

Usage:



    class Program
    {
        static void Main(string[] args)
        {
            MessageManager msg = new MessageManager(new Sms());
            msg.Sms.Get();
            msg.Sms.Send();
            Console.Read();
        }
    }

Usage2


namespace DependencyInjection
{

    public interface IMessage
    {
        public  void Get();
        public  void Send();
    }


    public class Sms :  IMessage
    {
        public  void IMessage.Get()
        {
            Console.WriteLine("Message Get!");
        }
        public  void IMessage.Send()
        {
            Console.WriteLine("Message Send!");
        }
    }

    public class MessageManager
    {
        private IMessage _message;

        public Sms Sms
        {
            get { return _message as Sms; }
            set { _message = value; }
        }

        public MessageManager(IMessage message)
        {
            _message = message;
        }

    }
}

What is the difference between Usage1 and usage2? When do I choose usage1 or Usage2?

回答1:

Abstract classes here to fight with duplicated code. Interfaces - to define contracts (API).

Depend on interfaces - they simply describe contract (API) of dependency, and they can by mocked easily. So, start with interface:

public interface IMessage
{
    void Get(); // modifiers like public are not allowed here
    void Send();
}

And here is your dependent class, which should depend only on abstraction (i.e. interface):

public class MessageManager
{
    private IMessage _message;

    // depend only on abstraction 
    // no references to interface implementations should be here
    public IMessage Message
    {
        get { return _message; }
        set { _message = value; }
    }

    public MessageManager(IMessage message)
    {
        _message = message;
    }
}

Then create class, which will implement your interface:

public class Sms : IMessage
{
    // do not use explicit implementation 
    // unless you need to have methods with same signature
    // or you want to hide interface implementation
    public void Get()
    {
        Console.WriteLine("Message Get!");
    }

    public void Send()
    {
        Console.WriteLine("Message Send!");
    }
}

Now you have inversed dependencies - MessageManager and Sms depend only on IMessage. You can inject any IMessage implementations to MessageManager (MessageManager now fit OCP - open for extension, but closed for modification).

Create base abstract message class only when you have duplicated code in several IMessage implementors. When you create abstract class (place, where you move duplicated code), you should not change interface, because contract stays the same. Just inherit your base class from original IMessage interface.



回答2:

The original definition of an interface is an abstract class with all pure virtual methods (i.e. abstract methods), this was how you describe an interface in C++. If you are not creating virtual functions with default definitions then you really do not need an abstract class at all. If you do have some default functionality that you would like the children of your Message class to inherit (and allow to override or not) then you would use an abstract class. An abstract class can also define protected methods and/or properties and fields, these can be used by classes that inherit from the abstract class but not by classes that use the abstract class. All methods in an interface would be public. In the case you laid out an interface would be fine.



回答3:

based on what I see you aren't really using an interface at all. Sure you implement the methods, but the consuming class shouldn't really know or even care about the implementation. Therefore, you shouldn't really see any reference or casting to Sms. You should be using a IoC framework such as Unity, Ninject, structuremap. If you actually need a public property returning an IMessage, it should return IMessage and not Sms, whether you should be doing that is a different conversation.

That said, the first Usage doesn't have anything in the IMessage so it is worthless. Also, I often use an abstract / base class to handle common functionality between multiple implementations of an interface. In you scenario, there is no need for the abstract class. The only time I create abstract methods without any code is if the abstract class actually fires that method in some capacity but expects the derived class to implement the functionality.

Anyway, to answer you question Usage #2 seems closer to the correct solution, but just remove the references to Sms and let an IoC container handle that.



回答4:

An interface does not contain any implementation code and does not force the implementer to derive its implementation form a given class. A base class (abstract or not) can already contain some logic. This can be an advantage as well as an undesirable constraint.

Of course you can combine both approaches. Define and program against an interface and at the same time provide a base class (or several base classes) implementing this interface and proving some basic logic that simplifies an implementers task. A person implementing the interface can decide to go the easy way and to extend a base class or to create something completely new and implement the interface directly.

The .NET Framework class library provides base classes for collections like Collection<T> or KeyedCollection<TKey,TItem> both implementing IList<T> that you can use as a base for creating your own specialized collection. But of course you can start from scratch and implement IList<T> directly.



回答5:

The reason to choose an abstract class over an interface has to do with reusable code. The fact that you're using dependency injection doesn't change this answer.

Do all of the subclasses have common functionality? If so, it can be defined in the base class.

Should the template method design pattern be used? If so, the algorithm exists in the base class.

As always, the answer is: "It depends." You can't do these approaches with interfaces as interfaces only specify the contract and don't have implementation. If the contract is all you need, then go with an interface. If you need code to be shared by the subclasses, then go with an abstract class.



回答6:

I don't have a lot to add to the other answers provided, apart from some reasons to listen to them:

An abstract class with only abstract methods is essentially an interface. The difference is that it could have some form of virtual implementation, and therein lies the trap: its all too easy when trying to reduce duplication using a base class to hide dependencies.

Let's say you have a set of objects which need to persist between runs. It's tempting to add a save functionality to the base class so that no one else has to worry about how the save works. The problem is, if you totally hide it, you create a testing nightmare where the implementation has to be tested, or else a lot of functionality is relegated to only integration testing. Using Strategy for the save functionality would totally resolve the issue, and the two can be combined simply enough.

The problem is more temptation than one simply being bad. Inheritance does not stop DI, but it doesn't encourage it either. If you're trying to get into SOLID and DI, you may be better off avoiding inheritance for now.