我正在努力学习单一责任原则(SRP),但这很困难,因为我很难弄清楚我应该从一个班级中删除的时间和内容,以及我应该放置/组织它的位置。< / p>
我正在谷歌搜索一些材料和代码示例,但我找到的大多数材料,而不是让它更容易理解,使其难以理解。
例如,如果我有一个用户列表,并且从该列表中我有一个 class Called Control可以执行很多事情,例如发送问候和 当用户进/出时再见消息,验证用户的天气 应该能够进入或不进入并踢他,接收用户命令和消息等。
从这个例子中你不需要太多了解我已经在一个班级做了太多但是我不清楚如何拆分和重新组织它。
如果我理解SRP,我会有一个加入频道的课程,问候和再见,一个用户验证课程,一个阅读命令的课程,对吗?
但是我在哪里以及如何使用踢球?
我有验证课程,所以我相信我会在那里进行各种用户验证,包括天气或不应该踢用户。
因此,kick函数将位于通道连接类中,并在验证失败时被调用?
例如:
public void UserJoin(User user)
{
if (verify.CanJoin(user))
{
messages.Greeting(user);
}
else
{
this.kick(user);
}
}
如果你们可以借助易于理解的C#在线和免费资料,或者通过向我展示如何分割引用的示例以及可能的一些示例代码,建议等,我将不胜感激。
答案 0 :(得分:58)
让我们从Single Responsibility Principle(SRP)的实际含义开始:
这实际上意味着每个对象(类)应该只有一个责任,如果一个类有多个职责,这些职责就会被耦合,不能独立执行,即一个人的变化会影响甚至打破另一个特定实现
必须阅读的是源本身(来自"Agile Software Development, Principles, Patterns, and Practices"的pdf章节):The Single Responsibility Principle
话虽如此,你应该设计你的课程,这样他们理想情况下只做一件事并做好一件事。
首先考虑一下你所拥有的“实体”,在你的例子中我可以看到User
和Channel
以及它们之间通信的媒介(“消息”)。这些实体有某种关系彼此:
这也自然导致以下功能列表:
SRP是一个重要的概念,但很难独立 - 对于您的设计同样重要的是Dependency Inversion Principle(DIP)。要将其合并到设计中,请记住User
,Message
和Channel
实体的特定实现应该依赖于抽象或接口,而不是特定的具体实现。出于这个原因,我们开始设计接口而不是具体的类:
public interface ICredentials {}
public interface IMessage
{
//properties
string Text {get;set;}
DateTime TimeStamp { get; set; }
IChannel Channel { get; set; }
}
public interface IChannel
{
//properties
ReadOnlyCollection<IUser> Users {get;}
ReadOnlyCollection<IMessage> MessageHistory { get; }
//abilities
bool Add(IUser user);
void Remove(IUser user);
void BroadcastMessage(IMessage message);
void UnicastMessage(IMessage message);
}
public interface IUser
{
string Name {get;}
ICredentials Credentials { get; }
bool Add(IChannel channel);
void Remove(IChannel channel);
void ReceiveMessage(IMessage message);
void SendMessage(IMessage message);
}
这个列表没有告诉我们是什么原因执行这些功能。我们最好将“为什么”(用户管理和控制)的责任放在一个单独的实体中 - 这样,如果“为什么”改变,User
和Channel
实体就不必改变。我们可以在这里利用策略模式和DI,并且IChannel
可以具有任何具体实现,取决于给我们“为什么”的IUserControl
实体。
public interface IUserControl
{
bool ShouldUserBeKicked(IUser user, IChannel channel);
bool MayUserJoin(IUser user, IChannel channel);
}
public class Channel : IChannel
{
private IUserControl _userControl;
public Channel(IUserControl userControl)
{
_userControl = userControl;
}
public bool Add(IUser user)
{
if (!_userControl.MayUserJoin(user, this))
return false;
//..
}
//..
}
您可以看到,在上述设计中,SRP甚至不接近完美,即IChannel
仍然依赖于抽象IUser
和IMessage
。
最后,人们应该努力寻求灵活,松散耦合的设计,但总会有权衡,灰色区域也取决于你期望应用程序的变化。
在我看来,SRP被带到 extreme 会导致非常灵活但是碎片化和复杂的代码,这些代码可能不像更简单但更紧密耦合的代码那样易于理解。
事实上,如果两个职责始终预期会同时发生变化,你可以说不应该将它们分成不同的类别,因为这会引起马丁的“不必要的复杂气味”。 。对于永不改变的责任也是如此 - 行为是不变的,没有必要拆分它。
这里的主要想法是你应该做出一个判断调用,你看到责任/行为可能会在未来独立地改变,这种行为是相互依赖的,并且总是会同时发生变化(“嘻哈“)以及哪种行为首先不会改变。
答案 1 :(得分:19)
我很容易学习这个原理。它呈现给我三个小的,一口大小的部分:
符合这些标准的准则符合单一责任原则。
在上面的代码中,
public void UserJoin(User user)
{
if (verify.CanJoin(user))
{
messages.Greeting(user);
}
else
{
this.kick(user);
}
}
UserJoin不符合SRP;它正在做两件事,即如果用户可以加入则问候用户,或者如果他们不能加入则拒绝他们。重新组织方法可能更好:
public void UserJoin(User user)
{
user.CanJoin
? GreetUser(user)
: RejectUser(user);
}
public void Greetuser(User user)
{
messages.Greeting(user);
}
public void RejectUser(User user)
{
messages.Reject(user);
this.kick(user);
}
从功能上讲,这与最初发布的代码没有什么不同。但是,这段代码更易于维护;如果出现新的业务规则,由于最近的网络安全攻击,您想记录被拒绝用户的IP地址,该怎么办?您只需修改RejectUser方法即可。如果您想在用户登录时显示其他消息怎么办?只需更新方法GreetUser。
根据我的经验,SRP可用于维护代码。并且可维护的代码往往在很大程度上实现了SOLID的其他部分。
答案 2 :(得分:3)
我的建议是从基础开始:你有什么的东西?您提到了多个内容,例如Message
,User
,Channel
等。除了简单的内容之外,您还有属于事物的行为。一些行为示例:
请注意,这只是一种查看方式。您可以抽象出任何一种行为,直到抽象意味着什么都没有!但是,抽象层通常不会受到伤害。
从这里开始,OOP有两种常见的思想流派:完全封装和单一责任。前者会引导您将所有相关行为封装在其拥有的对象中(导致设计不灵活),而后者会建议反对它(导致松耦合和灵活性)。
我会继续,但现在已经很晚了,我需要睡一觉......我正在把它作为一个社区帖子,所以有人可以完成我已经开始的工作,并改进我到目前为止所拥有的...
快乐学习!