策略模式与开闭原则冲突

Strategy Pattern and Open-Closed Principle Conflict

我正在通读策略模式并尝试实施它,但我一直坚持决定我认为违反开闭原则的策略实施。

在策略模式中,我们编写接口并基于客户端交互,我们将传递策略实现。

现在,如果我们有一堆策略,那么我们需要决定使用条件,客户选择哪种策略,例如

IStrategy str;
    if(stragety1) {
     str = new Strategy1()
    } else if (stragety2) {
     str = new Strategy2()
    } and so on..
str.run()

现在按照开闭原则,上面的扩展是开放的但它不是修改

如果我以后需要添加另一个策略(扩展),我确实需要更改此代码。

有没有办法避免这种情况,或者我们需要如何实施策略模式?

我认为,对 Closed for Modifications 存在误解。

1988 年,梅耶尔说:
当您的应用程序扩展新功能时,可能 不应更改正常工作的软件。

Rober C. Matrin 说:

这个定义显然已经过时了。 非常仔细地考虑一下。如果系统中所有模块的行为都可以扩展,而无需修改它们,那么您就可以向该系统添加新功能而无需修改任何旧代码.这些功能将 仅通过编写新代码 添加。 https://8thlight.com/blog/uncle-bob/2014/05/12/TheOpenClosedPrinciple.html

在不修改旧代码的情况下增加一些新代码不违反开闭原则。

这确实不是禁止修改的,但这是由于您的初始化方式所致。您正在使用一个值(枚举?)来确定应使用哪个 Strategy subclass。正如@bpjoshi 指出他们的,这更像是一种工厂模式。

维基百科讨论了策略模式如何 support the Open/Closed Principle,而不是阻碍它。
在该示例中,他们使用 Car class 和 Brake 策略。有些汽车使用 ABS 制动,有些则没有。不同的Car子class子和实例可以被赋予不同的刹车策略

要关闭您的代码以进行修改,您需要 select 不同的策略。您想 select 在定义新行为或 subclass 的地方使用 Strategy。您必须重构您的代码,以便在扩展代码的地方应用特定的 Strategy subclass。

我觉得你说的这个决定应该是工厂的责任class。以下是一些示例代码:

public interface ISalary
{
    decimal Calculate();
}

public class ManagerSalary : ISalary
{
    public decimal Calculate()
    {
        return 0;
    }
}

public class AdminSalary : ISalary
{
    public decimal Calculate()
    {
        return 0;
    }
}

public class Employee
{
    private ISalary salary;

    public Employee(ISalary salary)
    {
        this.salary = salary;
    }

    public string Name { get; set; }

    public decimal CalculateSalary()
    {
        return this.salary.Calculate();
    }
}

Employee class 使用策略模式并遵循 Open/Closed 原则,即它对通过构造函数注入的新策略类型(ISalary 实现)开放,但对修改关闭。

缺少的部分是创建 Employee 对象的代码,例如:

public enum EmployeeType
{
    Manager,
    Admin
}

public class EmployeeFactory
{
    public Employee CreateEmployee(EmployeeType type)
    {
        if (type == EmployeeType.Manager)
            return new Employee(new ManagerSalary());

        else if (type == EmployeeType.Admin)
            return new Employee(new AdminSalary());

        etc

    }
}

这是一个非常简单的工厂模式。有更好的方法可以做到这一点,但这是解释这个概念的最简单方法。

1) 您必须将 selecting/creating 具体 策略与其用途分开。 IE。使用函数 selectStrategy,将其作为(构造函数)参数传递等

2) 没有办法完全避免条件创建,但你可以隐藏它(例如使用一些字典来映射状态=>策略)and/or 将它转移到应用程序的另一个级别。最后一种方法非常强大和灵活,但取决于任务。在某些情况下,您可以将 selecting/creating 放在使用它的同一级别上。在其他情况下,您甚至可能最终将 selecting/creating 委派到 highest/lowest 级别。

2.1) 您可以使用 Registry 模式并在添加新策略时避免修改 "core" 对象。