我一直在阅读一些关于SOLID原则和依赖性反转的文章。从我的角度来看,我必须使用一个界面来与任何一个班级交谈。我的课程是通过使用接口聊天。
我正在使用抽象类,但对于我的代码的第二部分,我使用了一个接口。
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();
}
}
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?
答案 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!");
}
}
现在您已经反转了依赖关系 - MessageManager
和Sms
仅依赖于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,你现在最好避免继承。