用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级,使他们理想地只做一件事,做一件好事。
首先想想你有什么“实体”,在你的例子中,我可以看到User
和Channel
以及它们之间通过它们沟通的介质(“消息”)。这些实体之间有一定的关系:
- 一个用户有多个他join的频道
- 一个频道有很多用户
这也导致自然地做了以下function列表:
- 用户可以请求join频道。
- 用户可以发送消息到他join的频道
- 用户可以离开一个频道
- 一个频道可以拒绝或允许用户的请求join
- 一个频道可以踢一个用户
- 频道可以向频道中的所有用户广播消息
- 频道可以向频道中的个人用户发送问候消息
SRP是一个重要的概念,但不应该自成一体 – 对于您的devise同样重要的是依赖倒置原则 (DIP)。 要将其融入到devise中,请记住, User
, Message
和Channel
实体的特定实现应取决于抽象或接口,而不是特定的具体实现。 出于这个原因,我们开始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理和控制)的责任放在一个单独的实体中 – 这样, 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; //.. } //.. }
你可以看到,在上面的devise中,SRP并没有接近完美,即一个IChannel
依然依赖于IUser
和IMessage
的抽象。
最后,我们应该争取一个灵活的,松散耦合的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议是从基础开始:你有什么东西 ? 你提到了多个东西,如Message
, User
, Channel
等。除了简单的事情 ,你也有属于你的东西的 行为 。 一些行为的例子:
- 一条消息可以被发送
- 一个频道可以接受一个用户(或者你可以说一个用户可以join一个频道)
- 一个频道可以踢一个用户
- 等等…
请注意,这只是查看它的一种方法。 您可以抽象出这些行为中的任何一个,直到抽象意味着什么都没有意义。 但是,一层抽象通常不会受到伤害。
从这里开始,OOP中有两种常见的思想stream派:完整封装和单一责任。 前者会导致你将所有相关的行为封装在其拥有的对象中(导致不灵活的devise),而后者会build议反对它(导致松散的耦合和灵活性)。
我会继续下去,但是已经很晚了,我需要睡一会儿了…我把这个作为一个社区post,所以有人可以完成我已经开始的工作,并且改进我到目前为止所做的…
快乐学习!