【问题标题】:How single responsibility principle avoid code smell in this scenario?在这种情况下,单一职责原则如何避免代码异味?
【发布时间】:2019-08-31 11:21:52
【问题描述】:

我是设计模式的新手,我知道单一职责原则的目的,但不能 100% 确定它如何避免大量微小的变化。下面是我的例子:

//very crude implementation
public class Journal
{
    private readonly List<string> entries = new List<string>();
    private static int count = 0;

    public void AddEntry(string text)
    {
       entries.Add($"{++count}: {text}");
    }

    public void RemoveEntry(int index)
    {
       entries.RemoveAt(index);
       count--;
    }

    public void SaveToDisk(string filename)
    {
       File.WriteAllText(filename, ToString());
    }
}

我知道SaveToDisk方法不应该包含在类中,它应该是一个专门的类,比如PersistenceManager来处理文件保存。

但是为什么我不能在Journal 类中保留SaveToDisk() 方法?如果有什么新的需求,比如将日志保存到云端,那么我只需添加一个新方法SaveToCloud(),任何依赖的客户端类都可以使用SaveToCloud(),我需要做的唯一修改是在@中添加SaveToCloud() 987654330@class,哪个没问题?

已编辑:以下是我的修改版本,请指出任何设计错误:

class Program
{
  static void Main(string[] args)
  {
    Consumer client = new Consumer(new DiskManager("C:\\journal.txt"));
    // consumer add text to Journal
    client.AddJournal("sometext");
    client.SaveJournal();
  }
}

public class Journal
{
  private readonly List<string> entries = new List<string>();

  public void AddEntry(string text)
  {
    entries.Add(text);
  }

  public void RemoveEntry(int index)
  {
    entries.RemoveAt(index);
  }
}

public interface IPersistenceManager
{
  void Save(Journal journal);
}

public class DiskManager : IPersistenceManager
{
  private string filePath;

  public DiskManager(string filePath)
  {
    this.filePath = filePath;
  }

  public void Save(Journal journal)
  {
    //XXX.XXX.Save(filePath);
  }
}

public class CloudManager : IPersistenceManager
{
  private string url;

  public CloudManager(string url)
  {
    this.url = url;
  }

  public void Save(Journal journal)
  {
    //XXX.XXX.Save(url);
  }
}


public class Consumer
{
  private Journal _journal = new Journal();
  private IPersistenceManager _manager;

  public void AddJournal(string note)
  {
    _journal.AddEntry(note);
  }

  public Consumer(IPersistenceManager manager)
  {
    _manager = manager;
  }
  public void SaveJournal()
  {
    _manager.Save(_journal);
  }
}

【问题讨论】:

  • 看到这里,我有一个问题:如何加载期刊?可能来自期刊课外,对吧?
  • @John 让事情变得简单,假设我只需要创建和保存日志
  • 如果您还没有看过这篇文章,请阅读一些有用的文章stackoverflow.com/a/7591887/5233410
  • @Nkosi 但就我而言,你能告诉我为什么将 save 方法放在日志类中会导致很多变化吗?

标签: c# design-patterns single-responsibility-principle


【解决方案1】:

封装

通过将持久性代码移动到单独的PersistenceManager 类中,您可以保证SaveToDisk() 方法不会修改任何日志的私有变量,除非使用日志的公共方法和属性。

单一职责

但是为什么我不能在 Journal 类中保留 SaveToDisk() 方法呢?如果有新的需求,比如将日志保存到云端,那么我只需添加一个新方法SaveToCloud(),任何依赖的客户端类都可以使用SaveToCloud(),我需要做的唯一修改是在日志中添加SaveToCloud()上课,哪个没问题?

将日志保存到云端将需要您维护一些额外的状态 - 连接字符串、api 密钥、可能是 blob 客户端等。您从哪里获得该状态?

  • 您可以将其全部存储为 Journal 类中的静态成员
  • 您可以将其全部作为参数传递给SaveToCloud() 方法

将其存储为静态成员相当有限,您可能会遇到并发问题。

每次将参数传递给SaveToCloud() 方法意味着您需要遍历每个最初调用SaveToDisk() 的类,并对其进行更新以存储您需要的参数。这些是您要避免的“大量微小变化”。

如果您创建了一个 PersistenceManager 类,并使用 Save() 方法,那么您可以将连接字符串等添加到此类中,并且不需要更改任何调用者。

依赖倒置

实体必须依赖于抽象而不是具体。

通过在Journal 类中将其实现为静态方法,您消除了依赖倒置的可能性。想要保存日志的类应该把它定义为一个接口:

public interface IPersistenceManager
{
    void Save(string name);
}

请注意,它最后没有说 ToDisk() - 调用者不应该关心 日志保存在哪里,只要它正在 被保存.然后,当您的需求从存储在磁盘上更改为存储在云端时,您无需对调用方进行任何代码更改。

【讨论】:

  • 我在帖子中添加了一个使用DI的新版本,请您看看是否正确?
  • 是的,您发布的新版本很好地证明了这一点。您可以将消费者从存储在磁盘上更改为存储在云端,而无需修改消费者的代码。
  • 很抱歉再次提出这个问题。您提到“将额外状态存储为静态成员是相当有限的,您可能会遇到并发问题。”但是只要我们不修改代码中的状态,手动修改,应该不会有什么问题吧?我们不会让它们成为静态的,可以让 getter 访问这些状态
  • 是的,可以这样实现。但是,客户并不清楚他们必须先为ConnectionString 设置一个值,然后才能使用静态Save() 方法——编译器不会强制执行此操作,您只需要知道。相反,如果您在 PersistenceManager 类中具有该状态,则可以在构造函数中要求连接字符串。不要创建需要在使用之前运行某种 init 代码的静态方法,因为您可能会在某个时候忘记运行它。
【解决方案2】:

Journal 班级的工作——它的唯一职责——是代表期刊。像Journal.saveToDisk() 这样的方法的问题在于它需要不属于该工作的工作,例如决定使用什么文件名,确保在进程中止时没有任何东西处于不良状态等。Journal类不需要知道如何处理磁盘。

如您所见,这是一个坏挖起杆的薄边。很快你就会需要一个Journal.saveToCloud() 并且Journal 类也必须了解所有关于网络的知识。

在这种情况下,您必须将日志工作与磁盘工作分开,这样您就可以将日志工作放在Journal 类中,而将磁盘工作放在其他地方。

一个常见的解决方案是添加byte [] Journal.toByteArray(),它将日志转换为数据,然后您可以将其保存到磁盘、云或其他任何地方。 Journal.writeToStream(Stream target) 将执行类似的功能。无论哪种方式,将日记翻译成字节序列都是特定于日记的工作,您可以将其放入 Journal 类中。

您是否需要像持久性管理器这样的东西取决于您的应用程序的其余部分。

【讨论】:

    猜你喜欢
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 2020-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-14
    相关资源
    最近更新 更多