10

我一直在阅读一些关于 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;
        }

    }
}

用法1和用法2有什么区别?我什么时候选择usage1或usage2?

4

6 回答 6

26

抽象类在这里与重复的代码作斗争。接口 - 定义合约 (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只需从原始接口继承您的基类。

于 2012-11-18T18:30:33.153 回答
2

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

于 2012-11-18T18:01:09.607 回答
1

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

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

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

于 2012-11-18T17:24:08.773 回答
1

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

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

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

于 2012-11-18T17:27:27.827 回答
1

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

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

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

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

于 2012-11-18T17:29:53.503 回答
1

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

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

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

问题是比一个简单的坏更诱惑。继承不会阻止DI,但也不会鼓励它。如果你想进入 SOLID 和 DI,你最好暂时避免继承。

于 2013-10-09T19:45:58.023 回答