单一责任原则声明:
一个班级应该有一个,而且只有一个改变的理由。
开放/封闭原则声明:
您应该能够扩展类行为,而无需对其进行修改。
如果一个类只有一个理由可以改变,但是不应该被修改,那么开发人员如何尊重这两个原则呢?
示例
工厂模式就是一个很好的例子,它有一个单一的责任,但可能违反开放/封闭的原则:
public abstract class Product
{
}
public class FooProduct : Product
{
}
public class BarProduct : Product
{
}
public class ProductFactory
{
public Product GetProduct(string type)
{
switch(type)
{
case "foo":
return new FooProduct();
case "bar":
return new BarProduct();
default:
throw new ArgumentException(...);
}
}
}
当我需要在稍后阶段将ZenProduct
添加到工厂时会发生什么?
答案 0 :(得分:2)
这就像讨论“扩展类行为”的语义一样。将新类型添加到工厂是修改现有行为,它不会扩展行为,因为我们还没有改变工厂做的一件事。我们可能需要延长工厂但我们没有扩展它的行为。扩展行为意味着引入新行为,并且每次创建类型的实例或授权工厂的调用者时都会更多地遵循事件的行 - 这两个示例都扩展(引入新的)行为。
一个班级应该有一个,而且只有一个改变的理由。
问题中的示例是用于创建Product
个实例的工厂,并且更改它的唯一有效原因是更改它创建的Product
个实例,例如添加新的ZenProduct
个实例。 1}}。
您应该能够扩展类行为,而无需对其进行修改。
实现这一目标的一个非常简单的方法是使用Decorator
装饰器模式通常用于遵守单一责任原则,因为它允许在具有独特关注区域的类之间划分功能。
public interface IProductFactory
{
Product GetProduct(string type);
}
public class ProductFactory : IProductFactory
{
public Product GetProduct(string type)
{
\\ find and return the type
}
}
public class ProductFactoryAuth : IProductFactory
{
IProductFactory decorated;
public ProductFactoryAuth(IProductFactory decorated)
{
this.decorated = decorated;
}
public Product GetProduct(string type)
{
\\ authenticate the caller
return this.decorated.GetProduct(type);
}
}
在应用SOLID原则时,装饰器模式是一种强大的模式。在上面的示例中,我们在不更改ProductFactory
的情况下为ProductFactory
添加了身份验证。
答案 1 :(得分:1)
一个班级应该有一个,而且只有一个改变的理由。
这基本上意味着,您的班级应该代表单一责任,此后不应修改以适应新功能。
例如,如果您有课程,负责以pdf格式打印报告。之后,您希望添加新功能以支持其他格式的打印报告。然后,不应修改现有代码,而应扩展它以支持其他格式,这也意味着 扩展类行为,而不修改它
答案 2 :(得分:1)
我认为这取决于您对SRP的解释。这个东西总是有点主观。让100个人定义"单一责任"你可能得到100个不同的答案。
使用Ravi's answer中的方案,一个典型的解决方案可能是拥有一个公开ReportGenerator
方法的GeneratePdf
类。如果需要,可以稍后使用额外的GenerateWord
方法进行扩展。但就像你自己一样,我觉得这有点风味。
我可能会将GeneratePdf
方法重构为PdfReportGenerator
类,然后通过ReportGenerator
公开。这样ReportGenerator
只有一个责任;这是揭示各种报告生成机制(但不包含其逻辑)。然后可以在不扩大责任的情况下延长它。
我说如果你发现了冲突,很可能是一种建筑气味,需要快速审查,看看能否以更好的方式完成。
答案 3 :(得分:0)
我有一个类StudentOrganiser
类,它具有IStudentRepository
依赖性。由IStudentRepository
公开的接口是GetStudent(int studentId)
Class遵循SRP,因为它没有任何与管理与存储库源的连接相关的逻辑。
Class遵循OCP,因为如果我们想要将存储库源从SQL更改为XML,StudentOrganiser
不需要进行任何更改=>开放进行延期,但已关闭进行修改。
考虑StudentOrganiser
是否被设计为不依赖于IStudentRepository
,那么类本身内部的方法必须注意实例化new StudentSqlRepository()
如果后来的要求也会支持StudentXMLRepository
{1}}基于某些运行时条件,您的方法将以某种case switch
范式结束,从而违反SRP,因为方法也沉迷于实际的存储库决策因素中。通过注入存储库依赖关系,我们从类中取消了该职责。现在StudentOrganiser
类可以扩展为支持StudentXMLRepository
而无需任何修改。