Стратегия или нет стратегии? - PullRequest
0 голосов
/ 15 января 2020

Не вводя в академические определения c, допустим, что шаблон стратегии используется, когда у вас есть клиентский код (Context), который будет выполнять операцию, и эта операция может быть реализована различными способами (алгоритмами). Например: https://www.dofactory.com/net/strategy-design-pattern

Какая стратегия (или алгоритм) будет использоваться, во многих случаях зависит от некоторых условий ввода. Поэтому иногда используется шаблон стратегии в сочетании с шаблоном фабрики. Клиент передает условия ввода на Фабрику. Тогда Фабрика знает, какую Стратегию нужно создать. Затем Клиент выполняет операцию созданной Стратегии.

Однако я несколько раз сталкивался с проблемой, которая кажется мне противоположной. Выполняемая операция всегда одна и та же, но она будет выполняться только в зависимости от семейства условий ввода. Например:

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);
    }
}

Теперь клиентский код получит файлы из некоторого хранилища и сохранит файлы в базе данных. Это операция, сохраняющая файлы в базе данных, только в зависимости от типа файла и конкретных c условий для каждого файла.

    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);
                }
            }
        }
    }

Неправильно ли я полагать, что это другое шаблон, чем шаблон стратегии? (хотя я и назвал стратегию в приведенном выше коде, потому что мне уже несколько раз казалось, что это так)

Если эта проблема отличается от той, которую решает шаблон стратегии, то какой это шаблон?.

1 Ответ

1 голос
/ 15 января 2020

Не совсем шаблон стратегии, потому что, как определено в шаблоне стратегии в Википедии, говорит:

В компьютерном программировании шаблон стратегии (также известный как шаблон политики) является поведенческим шаблоном разработки программного обеспечения , который позволяет выбрать алгоритм во время выполнения . Вместо непосредственной реализации одного алгоритма код получает инструкции во время выполнения, которые в семействе алгоритмов использовать. [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, и добавляете его в список стратегий в поставщике, и никаких других изменений не требуется. Если вам потребуется выполнить другую логику c для некоторого нового типа файла, вы создадите новую стратегию и override Выполните метод, и все.


Если это так действительно выглядело как излишнее, и вам больше не нужно расширять это решение новым поведением, и единственное, что вам нужно - это иметь возможность добавить новое условие, тогда go, с другим подходом.

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, если вам нужен новый состояние без прикосновения к коду клиента.

...