56

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

我在谷歌上搜索了一些材料和代码示例,但我发现的大多数材料并没有让它更容易理解,反而变得难以理解。

例如,如果我有一个用户列表,并且从该列表中我有一个名为 Control 的类,它可以执行很多操作,例如在用户进出时发送问候语和再见消息,验证用户是否应该能够进入的天气踢他,接收用户命令和消息等。

从这个例子中你不需要太多了解我已经在一节课上做太多了,但是我对之后如何拆分和重组还不够清楚。

如果我了解 SRP,我会有一个用于加入频道的课程,用于问候和再见,用于用户验证的课程,用于阅读命令的课程,对吗?

但是,例如,我将在哪里以及如何使用踢?

我有验证课程,所以我确信我会在那里进行各种用户验证,包括天气或不应该踢用户。

所以 kick 函数会在通道加入类中,如果验证失败会被调用?

例如:

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

如果你们能在这里帮助我提供易于理解的在线免费 C# 材料,或者向我展示我将如何拆分引用的示例以及如果可能的话一些示例代码、建议等,我将不胜感激。

4

3 回答 3

60

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

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

这实际上意味着每个对象(类)都应该有一个单一的职责,如果一个类有多个职责,这些职责就会耦合并且不能独立执行,即在一个特定的实现中,一个对象的更改可能会影响甚至破坏另一个。

必须阅读的是源本身(“敏捷软件开发、原则、模式和实践”中的 pdf 章节):单一职责原则

话虽如此,您应该设计您的课程,以便理想情况下它们只做一件事并做好一件事。

首先考虑一下您拥有哪些“实体”,在您的示例中我可以看到User以及Channel它们之间进行通信的媒介(“消息”)。这些实体彼此之间有一定的关系:

  • 用户有多个他加入的频道
  • 一个频道有多个用户

这也自然会导致执行以下功能列表:

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

SRP 是一个重要的概念,但不应单独存在——对您的设计同样重要的是依赖倒置原则(DIP)。要将其合并到设计中,请记住,您的User,MessageChannel实体的特定实现应该依赖于抽象或接口,而不是特定的具体实现。出于这个原因,我们从设计接口而不是具体类开始:

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

该列表没有告诉我们的是执行这些功能的原因。我们最好将“为什么”(用户管理和控制)的责任放在一个单独的实体中——这样,如果“为什么”发生变化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;
        //..
    }
    //..
}

您会看到,在上面的设计中,SRP 甚至还没有接近完美,即 anIChannel仍然依赖于抽象IUserIMessage.

最后,人们应该争取一种灵活、松散耦合的设计,但总要做出权衡,灰色区域也取决于您希望应用程序在哪里改变。

在我看来,极端的SRP会导致非常灵活但又碎片化和复杂的代码,这些代码可能不像更简单但耦合更紧密的代码那样容易理解。

事实上,如果总是期望两个职责同时发生变化,那么您可能不应该将它们分成不同的类,因为这会导致,引用 Martin 的话,产生“不必要的复杂性”的味道。永远不变的职责也是如此——行为是不变的,没有必要拆分它。

这里的主要思想是你应该做出判断,你看到责任/行为将来可能独立改变,哪些行为相互依赖并且总是同时改变(“绑在臀部”)以及哪些行为一开始就永远不会改变。

于 2011-09-29T02:40:47.497 回答
21

我很轻松地学习了这个原理。它被分成三个小的,一口大小的部分呈现给我:

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

满足这些标准的代码满足单一职责原则。

在您上面的代码中,

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 的其他部分大有帮助。

于 2012-05-18T20:35:19.930 回答
3

我的建议是从基础开始:你有什么东西?你提到了很多东西,比如Message,,等等。除了简单的东西,你还有属于你的东西的行为。行为举几个例子:UserChannel

  • 可以发送消息
  • 频道可以接受用户(或者您可以说用户可以加入频道)
  • 频道可以踢用户
  • 等等...

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

从这里开始,OOP 中有两种常见的思想流派:完全封装和单一职责。前者会导致您将所有相关行为封装在其拥有的对象中(导致设计不灵活),而后者会建议不要这样做(导致松散耦合和灵活性)。

我会继续,但已经很晚了,我需要睡一觉......我正在将此作为社区帖子,所以有人可以完成我已经开始的工作并改进我到目前为止所拥有的......

快乐学习!

于 2011-09-27T04:25:43.050 回答