C# 在这种情况下,单一责任原则如何避免代码气味?

C# 在这种情况下,单一责任原则如何避免代码气味?,c#,design-patterns,single-responsibility-principle,C#,Design Patterns,Single Responsibility Principle,我是设计模式的新手,我知道单一责任原则的目的,但不能100%确定它如何避免许多微小的变化。以下是我的例子: //very crude implementation public class Journal { private readonly List<string> entries = new List<string>(); private static int count = 0; public void AddEntry(string te

我是设计模式的新手,我知道单一责任原则的目的,但不能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());
    }
}
封装

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

单一责任

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

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

您可以将其全部存储为Journal类中的静态成员 您可以将其全部作为参数传递给SaveToCloud方法 将其存储为静态成员相当有限,您可能会遇到并发问题

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

相反,如果您使用Save方法创建了PersistenceManager类,那么您可以向该类添加连接字符串等,并且无需更改任何调用方

依赖倒置

实体必须依赖抽象,而不是具体化

通过在Journal类中将其作为静态方法实现,可以消除依赖项反转的可能性。要保存日记账的类应将其定义为接口:

public interface IPersistenceManager
{
    void Save(string name);
}
请注意,它并没有在末尾说ToDisk——只要日志被保存,调用者就不应该关心日志保存在哪里。然后,当您的需求从存储在磁盘上变为存储在云中时,您不需要对调用方进行任何代码更改。

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

正如你所认识的,这是一个坏楔子的薄边。很快你就需要一本日记了。saveToCloud和日记课也必须了解所有关于网络的知识

在这种情况下,必须将日志工作与磁盘工作分开,以便将日志工作放在journal类中,将磁盘工作放在其他位置

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


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

看到这一点,我有一个问题:如何加载日志?可能是在journal类之外,对吧?@John为了让事情变得简单,假设我只需要创建和保存journal一些有用的阅读材料,如果你还没有看到这个@Nkosi,但是在我的例子中,你能告诉我为什么在journal类中放置save方法会导致很多更改吗?我在帖子中添加了一个使用DI的新版本,你能看一下它是否正确吗?是的,你发布的新版本很好地说明了这一点。您可以将使用者从存储在磁盘上更改为存储在云中,而无需修改使用者的代码。很抱歉再次提出此问题。您提到,将额外的状态存储为静态成员是相当有限的,您可能会遇到并发问题。但只要我们不修改代码中的状态,只需手动更改它们,就不会有任何问题了?我们不会让它们成为静态的,我们可以让getter访问这些状态是的,通过这种方式实现它是可能的。但是,客户并不清楚,在使用静态保存方法之前,他们必须为ConnectionString设置一个值——编译器不会强制这样做,您只需要知道。如果在PersistenceManager类中具有该状态,则可以在构造函数中要求连接字符串。不要创建需要先运行某种初始化代码才能使用的静态方法,因为很可能您会忘记运行它 没什么意义。
public interface IPersistenceManager
{
    void Save(string name);
}