单一职责原则如何避免这种场景下的代码异味?

How single responsibility principle avoid code smell in this scenario?

我是设计模式的新手,我知道单一职责原则的目的,但不是 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 方法不应该包含在 class 中,它应该是一个专门的 class,像 PersistenceManager 来处理文件保存。

但是为什么我不能在Journalclass中保留SaveToDisk()方法呢?如果有任何新的要求,例如将日志保存到云端,那么我只需添加一个新方法 SaveToCloud(),任何依赖的客户端 classes 都可以使用 SaveToCloud(),这是我唯一需要的修改制作方法是在 Journal class 中添加 SaveToCloud(),完全可以吗?

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

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

封装

通过将持久化代码移至单独的 PersistenceManager class,您可以保证 SaveToDisk() 方法不会修改日志的任何私有变量,除非使用public 期刊的方法和属性。

单一职责

But why can't I keep the SaveToDisk() method in Journal class? If there is any new requirements such as Save the Journal to cloud, then I just add a new method SaveToCloud(), and any dependent client classes can use SaveToCloud(), the only modification I need to make is adding SaveToCloud() in Journal class, which is totally fine?

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

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

将其存储为静态成员相当有限,您可以 运行 解决并发问题。

每次都将参数传入SaveToCloud()方法,意味着你需要遍历每一个原来调用SaveToDisk()的class,并更新它来存储你需要的参数。这些是您要避免的 'lots of tiny changes'。

如果相反,您使用 Save() 方法创建了 PersistenceManager class,那么您可以将连接字符串等添加到此 class,并且 none 的呼叫者需要更改。

依赖倒置

Entities must depend on abstractions not on concretions.

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

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

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

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

如您所知,这是坏楔子的薄边。很快您将需要 Journal.saveToCloud() 并且 Journal class 也必须了解所有关于网络的知识。

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

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

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