SOLID 原则,用外行人的话来说:接口隔离
SOLID 原则,用外行人的话来说:接口隔离
存在理由:我开始撰写关于 SOLID 软件开发原则的文章。具体来说,我的目标是让其他开发人员更容易理解这些原则,他们(就像我一样)发现不得不解读关于这个问题的冗长、复杂的文章和书籍很麻烦。这些原则供所有人学习和使用,但我发现它们很难完全掌握;很可能因为我来自非英语背景。因此,通过本系列文章,我打算尝试解开这些原则的神秘面纱,并抱有最好的意图。这些原则适用于软件开发的许多层面。在我的文章中,我特别致力于描述它们与编程的关系。我希望它对您有所帮助。感谢您的光临。
这将是一个关于 SOLID 的 5 篇文章系列。SOLID 正在风靡一时;至少从我正在阅读的招聘广告来看是这样;“您需要遵守 SOLID 原则”等等。那么,SOLID 究竟是什么?简单来说,它是在开发面向对象系统时的一组指导方针。它们是一组概念,已被证明对许多编写大量软件的人来说非常有价值。如果您愿意,这是软件工程领域的长辈们讲述的故事,您需要注意它,这样您就可以在您的简历上夸耀您正在研究 SOLID 原则,并且您将成为一个更好的开发人员,因为您了解它们,我向您保证。 事实上,您仅仅是_想_了解它们就让您成为了一个更好的开发人员!
SOL[I]D - 接口隔离原则
我们关于 SOLID 原则的文章系列的第 4 部分带我们了解了接口隔离。我们再次求助于维基百科以获取官方解释,它声明,[引用] “不应强迫任何客户端依赖它不使用的方法。” [/引用]。让我们深入了解一下 - 那么,这一切都与什么有关呢?
真的很简单:基本上,这个原则意味着,如果您正在实现接口并且您的实现没有实现该接口的所有方法,那么这表明您的接口臃肿,可能需要重新设计。
先举一个简短的例子。同样,这些都是非常简单的例子,仅用于让您掌握这个概念。假设我们有这个接口,我们希望实现它,以及两个这样的实现
public interface ILogLocation
{
string LogName { get; set; }
void Log(string message);
void ChangeLogLocation(string location);
}
public class DiskLogLocation : ILogLocation
{
public string LogName { get; set; }
public DiskLogLocation(string _logName)
{
LogName = _logName;
}
public void Log(string message)
{
// do something to log to a file here
}
public void ChangeLogLocation(string location)
{
// do something to change to a new log-file path here
}
}
public class EventLogLocation : ILogLocation
{
public string LogName { get; set; }
public EventLogLocation()
{
LogName = "WindowsEventLog";
}
public void Log(string message)
{
// do something to log to the event-viewer here
}
public void ChangeLogLocation(string location)
{
// we can't change the location of the windows event log,
// so we'll simply return
return;
}
}
上面是 ILogLocation
接口的两个实现。两者都实现了 ChangeLogLocation()
方法,但只有我们的 DiskLogLocation
实现为我们带来了有意义的东西。这很混乱;就像把一个冬天的毛线帽戴在你的夏帽上,它们都是帽子,但你不需要同时戴着它们。假设我们有三个 DiskLogLocations
和一个 EventLogLocation
:现在,出于配置目的,我们可以通过使用它们的接口来遍历它们,这将是一个完全合理的事情...
var logLocations = new List<iloglocation>() {
new DiskLogLocation("DiskWriteLog"),
new DiskLogLocation("DiskReadLog"),
new EventLogLocation(),
new DiskLogLocation("FileCheckLog")
};
foreach( var logLocation in logLocations)
{
logLocation.ChangeLogLocation(@"c:\" + logLocation.LogName + ".txt");
}</iloglocation>
...所以我们在上面做了,但正如您所看到的,这导致对 EventLogLocation
对象的 ChangeLogLocation()
的不必要调用。这是不必要的,它不会导致任何结果,而是有可能引入错误,应该被视为代码异味,并且通常“不应该被调用” - 原谅这个双关语。
所以我们可以这样来做
public interface ILogLocation
{
string LogName { get; set; }
void Log(string message);
}
public interface ILogLocationChanger
{
void ChangeLogLocation(string location);
}
public class DiskLogLocation : ILogLocation, ILogLocationChanger
{
public string LogName { get; set; }
public DiskLogLocation(string _logName)
{
LogName = _logName;
}
public void Log(string message)
{
// do something to log to a file here
}
public void ChangeLogLocation(string location)
{
// do something to change to a new log-file path here
}
}
public class EventLogLocation : ILogLocation
{
public string LogName { get; set; }
public EventLogLocation()
{
LogName = "WindowsEventLog";
}
public void Log(string message)
{
// do something to log to the event-viewer here
}
}
在上面,我们将 ChangeLogLocation()
方法移动到它自己的接口中,ILogLocationChanger
- 仅由 DiskLogLocation
实现。这是一种更好的方法;再也不会有不必要的方法实现!此外,我们现在可以通过它们实现的抽象来区分实现。我们可以潜在地重用一个或多个接口用于其他类。
所以这就是它 - 接口隔离原则,它基本上意味着“如果你发现你必须实现你的类不需要的方法,或者你只是返回,或者抛出异常,那么就拆分你的接口”。简单!
顺便说一句,您可能会思考这个接口隔离原则与单一职责原则的区别;作为提醒,单一职责原则告诉我们一个模块应该只承担单一的职责。这与我们刚刚接受的这个接口隔离原则不是一回事吗?嗯,它们有些相似。存在差异,但我不会深入探讨。相反,我将参考 StackOverflow 用户 Andreas Hallberg,我认为他撰写的解释比我能想到的任何解释都更好:(参考:http://stackoverflow.com/questions/8099010/is-interface-segregation-principle-only-a-substitue-for-single-responsibility-pr)[引用]“以一个类的例子为例,其职责是将数据持久化到例如硬盘驱动器。将类分成读写部分没有实际意义。但是一些客户端应该只使用该类来读取数据,一些客户端只写入数据,还有一些两者都做。在这里应用 ISP 具有三个不同的接口,这将是一个很好的解决方案。”[/引用] 太棒了!所以我们了解到,SR 原则关乎“一件事,而且只是一件事”与 IS 原则关乎“我们需要什么,而且只需要我们需要的东西”。相关的原则,都有效,都很重要。