【问题标题】:When do I use abstract classes versus interfaces with regards to dependency Injection?关于依赖注入,我什么时候使用抽象类和接口?
【发布时间】:2012-11-06 16:43:19
【问题描述】:

我一直在阅读一些关于 SOLID 原则和依赖倒置的文章。从我的角度来看,我必须使用接口与任何类进行对话。我的班级正在使用界面聊天。

第一个问题:

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

用法1


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

用法2


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?

【问题讨论】:

  • 因此,如果该示例具有要继承的抽象类 Message 上的属性,例如 int ID 和 string Sender,那么最干净的方法是让 Message : IMessage 继承方法,然后注入一个 Message 对象:public MessageManager(Message message)?只是想知道由于某种原因这是否是不好的做法,因为我不经常看到带有类的 DI。

标签: c# .net oop design-patterns


【解决方案1】:

这里的抽象类与重复代码作斗争。接口 - 定义合约 (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!");
    }
}

现在你有了反向依赖——MessageManager 和 Sms 只依赖于IMessage。您可以将任何 IMessage 实现注入到 MessageManager(MessageManager 现在适合 OCP - 对扩展开放,但对修改关闭)。

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

【讨论】:

  • 我遇到了类似的问题,最终将我的接口更改为其他类所依赖的抽象类...而不是我应该创建一个实现接口的抽象类,然后让我的其他类继承自它?
  • @NSjonas 我通常从界面开始。实际上,当您进行由外向内的开发时,在开始编写它们之前,您总是有更深层次的对象的接口。接口是完全可模拟的,这也有好处。当我有几个实现接口的类并且有重复的代码(数据或方法)时,我从这些类中提取基类,并使基类实现原始接口。如果基类不单独使用,那么我将其抽象化。所以是的,抽象类通常应该实现接口,其他类从它继承。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • “如果抽象类确实触发了该方法”的一些示例将有助于澄清您的答案。如果它有没有任何代码的方法,它怎么能做到这一点?
    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

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

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

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

      与往常一样,答案是:“视情况而定。”您不能使用接口执行这些方法,因为接口仅指定合同并且没有实现。如果合同就是您所需要的,那么请使用界面。如果您需要子类共享代码,请使用抽象类。

      【讨论】:

        【解决方案6】:

        除了一些听他们的理由之外,我没有太多要补充的其他答案:

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

        假设您有一组需要在运行之间保持的对象。向基类添加保存功能是很诱人的,这样其他人就不必担心保存的工作方式。问题是,如果你完全隐藏它,你就会制造一个测试噩梦,必须测试实现,否则很多功能只能被降级为集成测试。将 Strategy 用于保存功能将完全解决问题,并且两者可以简单地结合起来。

        问题更多的是诱惑,而不是单纯的坏。继承不会阻止 DI,但它也不鼓励它。如果您尝试使用 SOLID 和 DI,最好暂时避免继承。

        【讨论】:

          猜你喜欢
          • 2022-01-02
          • 2017-04-15
          • 1970-01-01
          • 1970-01-01
          • 2021-09-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-13
          相关资源
          最近更新 更多