单一职责原则如何避免这种场景下的代码异味?
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
来处理文件保存。
但是为什么我不能在Journal
class中保留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.
是否需要持久性管理器之类的东西取决于应用程序的其余部分。
我是设计模式的新手,我知道单一职责原则的目的,但不是 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
来处理文件保存。
但是为什么我不能在Journal
class中保留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.
是否需要持久性管理器之类的东西取决于应用程序的其余部分。