【问题标题】:Strategy pattern or no strategy pattern?策略模式还是无策略模式?
【发布时间】:2020-01-15 07:16:15
【问题描述】:

在不输入学术定义的情况下,假设当您有一个将执行操作的客户端代码(上下文)时使用策略模式,并且该操作可以以不同的方式(算法)实现。例如:https://www.dofactory.com/net/strategy-design-pattern

将使用哪种策略(或算法)取决于某些输入条件的许多场合。这就是策略模式有时与工厂模式结合使用的原因。客户端将输入条件传递给工厂。然后工厂知道必须创建哪个策略。然后Client执行创建的Strategy的操作。

但是,我曾多次遇到一个在我看来恰恰相反的问题。要执行的操作总是相同的,但它只会根据一系列输入条件来执行。例如:

public interface IStrategy
{
    string FileType { get; }

    bool CanProcess(string text);
}

public class HomeStrategy : IStrategy
{
    public string FileType => ".txt";

    public bool CanProcess(string text)
    {
        return text.Contains("house") || text.Contains("flat");
    }
}

public class OfficeStrategy : IStrategy
{
    public string FileType => ".doc";

    public bool CanProcess(string text)
    {
        return text.Contains("office") || text.Contains("work") || text.Contains("metting"); 
    }
}

public class StragetyFactory
{
    private List<IStrategy> _strategies = new List<IStrategy>{ new HomeStrategy(), new OfficeStrategy() };

    public IStrategy CreateStrategy(string fileType)
    {
        return _strategies.Single(s => s.FileType == fileType);
    }
}

现在客户端代码将从某个存储库中获取文件并将文件保存在数据库中。就是这个操作,把文件存入数据库,就看文件的类型和每个文件的具体情况了。

    public class Client
    {
        public void Execute()
        {
            var files = repository.GetFilesFromDisk();
            var factory = new StragetyFactory();

            foreach (var file in files)
            {
                var strategy = factory.CreateStrategy(file.Type);

                if (strategy.CanProcess(file.ContentText))
                {
                    service.SaveInDatabase(file);
                }
            }
        }
    }

我认为这是与策略模式不同的模式是错误的吗? (尽管我在上面的代码中调用了 Strategy ,因为我在好几次看起来都是这样的)

如果这个问题与策略模式解决的问题不同,那么它是哪种模式?

【问题讨论】:

  • 这里的策略似乎不能真正互换。文本文件策略不仅对于 docx 文件效率低下,而且无法正常工作。这似乎应该是一个模板类,具有像 C++ 中的 std:: 库这样的实例化。
  • 你的工厂只是返回一个基于文件扩展名选择的类的实例。它本质上与 switch 语句相同。不仅如此,如果在同一个 factory 上再次调用 CreateStrategy,它将返回 same 对象。 factory 的用户可能会发现这种名为 Create 的方法有点令人惊讶,并且可能会导致错误。

标签: c# design-patterns strategy-pattern


【解决方案1】:

并不是真正的策略模式,因为正如strategy pattern in Wikipedia 中的定义所说:

在计算机编程中,策略模式(也称为 策略模式)是一种行为软件设计模式 在运行时选择算法。而不是实现一个单一的 算法直接,代码接收运行时指令,在 一系列可供使用的算法。[1]

您没有选择在运行时执行的算法,您只需检查条件以查看文件类型是否满足条件,然后执行算法。

您希望这种情况发生改变吗?您是否需要它是可扩展的,以便将来如果您需要根据文件类型执行不同的代码,您可以轻松做到这一点。

如果这些问题的答案是,那么您可以保留策略并应用少量更改。

首先定义定义要执行的代码的基本策略类

public abstract class StrategyBase
{
   public abstract bool CanProcess(string fileType);
   public virtual void Execute(File file)
   {
        _service.SaveInDatabase(file);
   }
}

你的策略改变以从基础派生

public class HomeStrategy : StrategyBase
{
    public string FileType => ".txt";

    public override bool CanProcess(string text)
    {
        return text.Contains("house") || text.Contains("flat");
    }
}

// 对其余策略执行相同的操作...

正如评论中提到的,它并不是真正的工厂,因为它不会在每次调用时创建新策略。它更像是一个提供者,提供基于文件类型执行的策略。

public class StragetyProvider
{
    private List<StrategyBase> _strategies = new List<StrategyBase>{ new HomeStrategy(), new OfficeStrategy() };

    public StrategyBase GetStrategy(string fileType)
    {
        return _strategies.FirstOrDefault(s => s.CanProcess(fileType));
    }
}

因此客户端代码变得更加简单:

public class Client
    {
        public void Execute()
        {
            var files = repository.GetFilesFromDisk();
            var provider = new StragetyProvider();

            foreach (var file in files)
            {
                var strategy = provider.GetStrategy(file.Type);
                strategy?.Execute(file);
            }
        }
    }

注意,当您需要添加新条件时,您只需实现一个派生自StrategyBase 的新类并将其添加到提供程序中的策略列表中,无需进行其他更改。如果您需要为某些新文件类型执行不同的逻辑,您将创建新策略和override Execute 方法,仅此而已。


如果这确实看起来有点矫枉过正,并且您不需要使用新行为扩展此解决方案,并且您唯一想要的就是能够添加新条件,然后使用另一种方法。

public interface ISatisfyFileType
{
    bool Satisfies(string fileType);
}

public class HomeCondition : ISatisfyFileType
{
    public string FileType => ".txt";

    public bool Satisfies(string text)
    {
        return text.Contains("house") || text.Contains("flat");
    }
}

// the rest of conditions

将所有条件合二为一

public class FileConditions
{
  private List<ISatisfyFileType> _conditions = new List<ISatisfyFileType>{ new HomeStrategy(), new OfficeStrategy() };

  public bool Satisfies(string fileType) =>
     _conditions.Any(condition => condition.Satisfies(fileType));

}

还有客户:

public class Client
    {
        public void Execute()
        {
            var files = repository.GetFilesFromDisk();
            var fileTypeConditions = new FileConditions();

            foreach (var file in files)
            {
                if (fileTypeConditions.Satisfies(file.ContentText))
                {
                    service.SaveInDatabase(file);
                }
            }
        }
    }

如果您需要一个新条件而不接触客户端代码,这还具有实现新条件并将其添加到FileConditions 类的好处。

【讨论】:

  • 感谢您的回答@Michael。你明确表示我对此表示怀疑是对的。我已经多次出现这种称为策略的实现。也许这是因为它没有名字。最后,这是一个反复出现的问题:如何在不更改客户端代码的情况下为逻辑添加更多条件?而解决方案就是上面的实现。