C# 了解使用单一责任原则的实际好处
我试图理解SRP,但是,虽然我理解如何应用它背后的原因,但我并没有真正看到这样做的好处。考虑这个例子,取自Robert Martin的: 他建议将其分为两个接口:C# 了解使用单一责任原则的实际好处,c#,solid-principles,single-responsibility-principle,C#,Solid Principles,Single Responsibility Principle,我试图理解SRP,但是,虽然我理解如何应用它背后的原因,但我并没有真正看到这样做的好处。考虑这个例子,取自Robert Martin的: 他建议将其分为两个接口: interface IModemConnection { void Dial(string number); void Hangup(); } interface IModemDataExchange { void Send(char c); char Recv(); } 我也一直在阅读,这更进一步
interface IModemConnection
{
void Dial(string number);
void Hangup();
}
interface IModemDataExchange
{
void Send(char c);
char Recv();
}
我也一直在阅读,这更进一步:
interface IModemConnection : IDisposable
{
IModemDataExchange Dial(string number);
}
interface IModemDataExchange
{
void Send(char c);
char Recv();
}
此时,我理解了功能性(Send/Recv
)和非功能性(Dial/Hangup
)方面的含义,但在本例中,我看不到将它们分开的好处。考虑到这一基本实施:
class ConcreteModem : IModemConnection
{
public IModemDataExchange Dial(string number)
{
if (connection is successful)
{
return new ConcreteModemDataExchange();
}
return null;
}
public void Dispose()
{
//
}
public bool IsConnected { get; private set; }
}
在这一点上,让我再次引用罗伯特·马丁(Robert Martin)的话,尽管他所说的例子与PDF不同:
其次,如果对GraphicalApplication的更改导致矩形因某种原因发生更改,该更改可能会迫使我们重新构建、重新测试和重新部署CalculationAlgeMetricyApplication。如果我们忘记这样做,该应用程序可能会以不可预知的方式中断
这是我不明白的。如果我必须创建IModemDataExchange
的第二个实现,并且我想利用它,我仍然必须更改Dial
方法,这意味着该类还需要重新编译:
public IModemDataExchange Dial(string number)
{
if (some condition is met)
{
return new ConcreteModemDataExchange();
}
else if (another condition is met)
{
return new AnotherConcreteModemDataExchange();
}
return null;
}
我看不出这对减少变化对班级的影响起到了什么作用。它仍然需要重新编译,那么有什么好处呢?这样做对生成高质量代码非常重要,您从中获得了什么好处?如果您使用抽象工厂,则无需更改
ConcreteModem
。或者,如果您通过应在成功时创建的具体类型参数化genericModem
(或genericDial()
方法)
其思想是IModemConnection实现不依赖于除名称以外的任何有关IModeDataExchange实现的信息
<>向前,我会考虑以下方法:
interface IModemConnection : IDisposable
{
void Dial(string number);
}
interface IModemDataExchange
{
void Send(char c);
char Recv();
}
class ConcreteModemDataExchange : IModemDataExchange
{
ConcreteModemDataExchange(IModemDataExchange);
}
因此,要创建ConcreteModemDataExchange实例,您需要有一个连接。仍然存在断开连接实例的可能性,但情况不同
作为侧节点,我建议在出现故障时在
拨号中抛出异常。对我来说,上面的调制解调器示例似乎总是适用于而不是SRP,但这不是重点
在你提到的关于矩形的部分中,我认为你只是误解了它。Martin使用矩形作为共享库的示例。如果GraphicalApplication
需要一个新方法或更改Rectangle
类中的语义,那么这会影响ComputationalGeometryApplication
,因为它们都“链接”到Rectangle
库。他说它违反了SRP,因为它负责定义渲染边界和代数概念。想象一下,如果GraphicalApplication
从DirectX更改为OpenGL,其中y坐标是反向的。您可能希望更改矩形上的某些内容以便于执行此操作,但这样做可能会导致计算代数应用程序的更改
在我的工作中,我试图遵循这些原则和TDD,我发现SRP使编写类测试变得简单,并且使类保持专注。遵循SRP的类通常非常小,这降低了代码和依赖项的复杂性。在删除类时,我会尽量确保一个类是“做一件事”,还是“协调两件(或更多)事情”。这让他们集中精力,让他们改变的理由只取决于他们做的一件事,这对我来说就是SRP的重点。主要的好处是非常明显的。通过拆分,您为您的模型提供了更好的逻辑分组,从而使意图更清晰,维护更容易
如果我必须创建IModemDataExchange的第二个实现,并且我想利用它,那么我仍然必须更改拨号方法
是的,这是必须的,但这不是好处。一个好处是,当您对IModemDataExchange
接口本身进行任何修改时,您只需更改接口的具体实现,而不必更改ConcreteModem
本身,这将使拨号
方法的订户的维护更加容易。另一个好处是,现在即使您必须编写一个附加的IModemDataExchange
实现,那么它在ConcreteModem
类中所需的更改也会最小化,因此没有直接耦合通过分离责任,您可以将修改的副作用降至最低。
不需要重新编译不是这里的本质。从严格意义上讲,如果其中一个接口在另一个项目中呢?它保存了一个项目的重新编译。重点在于不需要在很多地方更改代码。当然,任何更改都需要重新编译。我对调制解调器的工作原理知之甚少,所以我努力想出一个有意义的例子。但是,考虑一下:
分离了拨号逻辑之后,现在如果程序的其他部分只需要拨号,我们可以只传入一个IModemConnection。
这甚至在调制解调器类本身中也很有用,使用依赖项注入:
public class Modem : IModemConnection, IModemDataExchange
{
public IModemConnection Dialer {get; private set;}
public Modem(IModemConnection Dialer)
{
this.Dialer=Dialer;
}
public void Dial(string number)
{
Dialer.Dial(number);
}
public void Hangup()
{
Dialer.Hangup();
}
// .... implement IModemDataExchange
}
现在你可以:
public class DigitalDialer : IModemConnection
{
public void Dial(string number)
{
Console.WriteLine("beep beep");
}
public void Hangup()
{
//hangup
}
}
及
现在,如果您想更改调制解调器工作方式的某些方面(本例中的拨号方式),您所做的更改将在一个具有单一职责(拨号)的拨号器类中进行本地化。我在连接引号和示例代码之间的点时遇到问题。这句话似乎引用了两个具有共享代码库的不同应用程序。@Gusdor它确实引用了两个不同的应用程序,但从我迄今为止读到的关于SRP的所有内容来看,它似乎是为了提高代码库的灵活性,同时减少更改的影响
public class DigitalDialer : IModemConnection
{
public void Dial(string number)
{
Console.WriteLine("beep beep");
}
public void Hangup()
{
//hangup
}
}
public class AnalogDialer : IModemConnection
{
public void Dial(string number)
{
Console.WriteLine("do you even remember these?");
}
public void Hangup()
{
//hangup
}
}