我何时使用抽象类与接口相关的依赖注入?

时间:2012-11-18 16:59:39

标签: c# .net oop design-patterns

我一直在阅读一些关于SOLID原则和依赖性反转的文章。从我的角度来看,我必须使用一个界面来与任何一个班级交谈。我的课程是通过使用接口聊天。

第一个问题:

我正在使用抽象类,但对于我的代码的第二部分,我使用了一个接口。

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

    }
}

用法:



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

    }
}

Usage1和usage2有什么区别?我何时选择usage1或Usage2?

6 个答案:

答案 0 :(得分:22)

这里的抽象类与重复的代码作斗争。接口 - 定义合同(API)。

依赖于接口 - 它们只是描述依赖关系的契约(API),并且可以轻松地模拟它们。所以,从界面开始:

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

这是你的依赖类,它应该只依赖于抽象(即接口):

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

然后创建类,它将实现您的接口:

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!");
    }
}

现在您已经反转了依赖关系 - MessageManagerSms仅依赖于IMessage。您可以将任何IMessage实现注入MessageManager(MessageManager现在适合OCP - 打开以进行扩展,但关闭以进行修改)。

在多个IMessage实现者​​中有重复代码时,创建基本抽象消息类 。当您创建抽象类(位置,移动重复代码的位置)时,您不应更改接口,因为合同保持不变。只需从原始IMessage接口继承您的基类。

答案 1 :(得分:2)

接口的原始定义是一个包含所有纯虚方法(即抽象方法)的抽象类,这就是您在C ++中描述接口的方式。如果您没有使用默认定义创建虚函数,那么您根本不需要抽象类。如果你确实有一些默认功能,你希望你的Message类的子代继承(并允许覆盖或不覆盖),那么你将使用一个抽象类。抽象类还可以定义受保护的方法和/或属性和字段,这些可以由从抽象类继承的类使用,但不能由使用抽象类的类使用。界面中的所有方法都是公共的。在你布局界面的情况下会很好。

答案 2 :(得分:1)

基于我所看到的,你根本就没有真正使用过界面。当然你实现了这些方法,但是消费类不应该真正了解甚至关心实现。因此,您不应该真正看到任何引用或转换为Sms。您应该使用IoC框架,例如Unity,Ninject,structuremap。如果你确实需要一个返回IMessage的公共属性,那么它应该返回IMessage而不是Sms,不管你应该这样做是不同的对话。

那就是说,第一个用法在IMessage中没有任何东西,所以它毫无价值。此外,我经常使用抽象/基类来处理接口的多个实现之间的常见功能。在您的场景中,不需要抽象类。我在没有任何代码的情况下创建抽象方法的唯一一次是,抽象类实际上是以某种方式触发该方法,但是期望派生类实现该功能。

无论如何,回答你问题用法#2似乎更接近正确的解决方案,但只是删除对Sms的引用并让IoC容器处理它。

答案 3 :(得分:1)

接口不包含任何实现代码,并且不强制实现者从给定类派生其实现。基类(抽象与否)可以包含一些逻辑。这可能是一个优点,也可能是一个不受欢迎的约束。

当然,您可以结合使用这两种方法。定义和编程接口,同时提供实现此接口的基类(或几个基类),并提供一些简化实现者任务的基本逻辑。实现接口的人可以决定采用简单的方法,扩展基类或创建全新的东西并直接实现接口。

.NET Framework类库为Collection<T>KeyedCollection<TKey,TItem>等集合提供基类,这些集合都实现IList<T>,您可以将其用作创建自己的专用集合的基础。但当然,您可以从头开始直接实施IList<T>

答案 4 :(得分:1)

在接口上选择抽象类的原因与可重用代码有关。你使用依赖注入的事实并没有改变这个答案。

所有子类都有共同的功能吗?如果是这样,它可以在基类中定义。

是否应该使用模板方法设计模式?如果是,则算法存在于基类中。

一如既往,答案是:“这取决于。”您不能使用接口来执行这些方法,因为接口仅指定合同而没有实现。如果您只需要合同,那么请使用界面。如果您需要子类共享代码,那么请使用抽象类。

答案 5 :(得分:0)

我没有太多要添加到提供的其他答案,除了听取他们的一些理由:

只有抽象方法的抽象类本质上是一个接口。不同之处在于可能具有某种形式的虚拟实现,并且其中存在陷阱:当尝试使用基类减少重复时,它非常容易隐藏依赖项。

假设您有一组需要在运行之间保留的对象。将保存功能添加到基类是很诱人的,这样其他人就不必担心保存的工作原理。问题是,如果你完全隐藏它,你就会创建一个测试噩梦,其中必须测试实现,否则许多功能只会降级到集成测试。使用策略进行保存功能可以完全解决问题,两者可以简单地组合在一起。

问题更多是诱惑而不是简单的坏事。继承不停止 DI,但它也不鼓励它。如果你想进入SOLID和DI,你现在最好避免继承。