用C#学习单一责任原则

我正在尝试学习单一职责原则(SRP),但是要解决这个问题非常困难,因为我很难弄清楚什么时候该从一个class级中删除,以及在哪里放置/组织。

我一直在search一些材料和代码示例,但是我发现的大多数材料并不便于理解,因此很难理解。

例如,如果我有一个用户列表,并从该列表中,我有一个类叫做控制,做很多事情,如当用户进出时发送问候语和再见消息,validation天气用户应该能够进入或不并踢他,接收用户命令和消息等

从这个例子中,你不需要太多的理解,我已经在一个class上做了太多的事情,但是我还不清楚如何在之后进行拆分和重组。

如果我理解SRP,我将有一个join频道的课程,问候语和再见,一个用户validation类,一个阅读命令的类,对吧?

但是我会在哪里以及如何使用脚踢?

我有validation类,所以我相信我会有各种用户validation,包括天气或不应该被踢。

那么kick函数会在channel join类里面,如果validation失败,会被调用吗?

例如:

public void UserJoin(User user) { if (verify.CanJoin(user)) { messages.Greeting(user); } else { this.kick(user); } } 

如果你们可以借助简单易懂的C#素材,在线免费或者向我展示我将如何拆分引用的示例,如果可能的话还有一些示例代码,build议等,

让我们从单一责任原则 (SRP)的实际意义开始:

一个class级应该只有一个改变的理由。

这实际上意味着每个对象(类)应该有一个单一的责任,如果一个类有多个责任,这些责任变得耦合,不能独立执行,即一个变化可以影响甚至打破另一个在一个特定的实现。

(源自“敏捷软件开发,原则,模式和实践”的 pdf章节): 一个责任原则

话虽如此,你应该devise自己的class级,使他们理想地只做一件事,做一件好事。

首先想想你有什么“实体”,在你的例子中,我可以看到UserChannel以及它们之间通过它们沟通的介质(“消息”)。这些实体之间有一定的关系:

  • 一个用户有多个他join的频道
  • 一个频道有很多用户

这也导致自然地做了以下function列表:

  • 用户可以请求join频道。
  • 用户可以发送消息到他join的频道
  • 用户可以离开一个频道
  • 一个频道可以拒绝或允许用户的请求join
  • 一个频道可以踢一个用户
  • 频道可以向频道中的所有用户广播消息
  • 频道可以向频道中的个人用户发送问候消息

SRP是一个重要的概念,但不应该自成一体 – 对于您的devise同样重要的是依赖倒置原则 (DIP)。 要将其融入到devise中,请记住, UserMessageChannel实体的特定实现应取决于抽象或接口,而不是特定的具体实现。 出于这个原因,我们开始devise接口,而不是具体的类:

 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); } 

这个列表没有告诉我们这些function是由什么原因执行的。 我们最好将“为什么”(用户pipe理和控制)的责任放在一个单独的实体中 – 这样, UserChannel实体在“为什么”更改时不必更改。 我们可以在这里利用策略模式和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; //.. } //.. } 

你可以看到,在上面的devise中,SRP并没有接近完美,即一个IChannel依然依赖于IUserIMessage的抽象。

最后,我们应该争取一个灵活的,松散耦合的devise,但总会有一些权衡,灰色地带也取决于你希望你的应用程序改变的地方。

在我看来,SRP 极端化 ,导致了非常灵活的,但也是分散和复杂的代码,可能不像简单但更紧密耦合的代码那样容易理解。

事实上,如果两个责任总是在同一时间改变,那么你可以说不应该把它们分成不同的类别,因为这会引起马丁的“不必要的复杂性的气味”。 对于永不改变的责任也是如此 – 行为是不变的,不需要分裂。

这里的主要思想是,你应该做出一个判断呼叫,你认为在未来,责任/行为可能独立地改变,哪一种行为是相互依赖的,并且总是同时发生变化(“与时俱进”)哪一种行为永远不会改变。

我有一个非常容易的时间学习这个原则。 它被呈现给我三个小的,一口大小的部分:

  • 做一件事
  • 只做那件事
  • 做好这件事

符合这些标准的守则履行单一责任原则。

在你的上面的代码中,

 public void UserJoin(User user) { if (verify.CanJoin(user)) { messages.Greeting(user); } else { this.kick(user); } } 

UserJoin不履行SRP; 它是做两件事情,即问候用户,如果他们可以join,或拒绝他们,如果他们不能。 重组方法可能会更好:

 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); } 

从function上来说,这与最初发布的代码没有区别。 但是,这个代码更易于维护; 如果由于最近的networking安全攻击而导致新的业务规则出现,那么您是否希望logging被拒绝的用户的IP地址? 您只需修改方法RejectUser。 如果您想在用户login时显示其他消息,该怎么办? 只需更新方法GreetUser。

在我的经验SRP使可维护的代码。 而且可维护的代码往往在实现SOLID的其他部分方面有很长的路要走。

我的build议是从基础开始:你有什么东西 ? 你提到了多个东西,MessageUserChannel等。除了简单的事情 ,你也有属于你的东西的 行为 。 一些行为的例子:

  • 一条消息可以被发送
  • 一个频道可以接受一个用户(或者你可以说一个用户可以join一个频道)
  • 一个频道可以踢一个用户
  • 等等…

请注意,这只是查看它的一种方法。 您可以抽象出这些行为中的任何一个,直到抽象意味着什么都没有意义。 但是,一层抽象通常不会受到伤害。

从这里开始,OOP中有两种常见的思想stream派:完整封装和单一责任。 前者会导致你将所有相关的行为封装在其拥有的对象中(导致不灵活的devise),而后者会build议反对它(导致松散的耦合和灵活性)。

我会继续下去,但是已经很晚了,我需要睡一会儿了…我把这个作为一个社区post,所以有人可以完成我已经开始的工作,并且改进我到目前为止所做的…

快乐学习!