Шаблон декоратора - НЕПРАВИЛЬНЫЙ выбор в этом сценарии.
Проблема, с которой вы здесь сталкиваетесь, это
- при условии x, вызовите один метод
- при условии y, вызовите другой метод
- ...
Это предварительное условие для шаблона стратегии, и ваше первоначальное решение было довольно близко к этому.Проблема в моем понимании состоит в том, что вы используете Исключение для определения потока программы, что является ПЛОХОЙ вещью: исключения требуют места в стеке, и они должны быть выброшены только при ИСКЛЮЧИТЕЛЬНЫХ обстоятельствах.В то время как в вашем случае ожидается, что данная стратегия не будет работать
IFileBackupStrategy
{
bool Backup(File fileToBackup);
}
IFileBackupContext
{
File ForBackup { set; }
bool Backup();
}
class CloudBackUp : IFileBackupStrategy
{
private bool _success;
public bool Backup(File fileToBackup)
{
// code to do backup omitted
// it will set the value of _success to false if it was unsuccessful
return _success;
}
}
class LocalBackUp : IFileBackupStrategy
{
private bool _success;
public bool Backup(File fileToBackup)
{
// code to do backup omitted
// it will set the value of _success to false if it was unsuccessful
return _success;
}
}
public class FileBackupContext : IFileBackupContext
{
private IEnumerable<IFileBackupStrategy> _backupStrategies
public Context(IEnumerable<IFileBackupStrategy> backupStrategies)
=> _backupStrategies = backupStrategies;
public File ForBackup { set; private get; }
public bool Backup()
{
bool successFlag;
foreach(var strategy in _backupStrategies)
{
successFlag = strategy.Backup(ForBackup);
if(successFlag) break;
}
return successFlag;
}
}
В этом случае все, что клиент должен знать, - это IFileBackupContext, а не стратегия, используемая для сохранения.
public class MyBackupClient
{
private IFileBackupContext _context;
public MyBackupClient(IFileBackupContext context) => _context = context;
void SomeMethodThatInvokesBackingUp()
{
_context.ForBackup = new File(/* */);
if(!_context.Backup())
{
Console.WriteLine("Failed to backup the file");
}
}
}
Прелесть этого дизайна в том, что вы можете добавить больше реализаций IFileBackupStrategy, зарегистрировать их в вашем DI-контейнере и вуаля, чтобы они были мгновенно доступны клиенту без единого изменения кода или необходимости повторного ввода кода.компиляция (хотя это в конечном итоге будет зависеть от того, как вы заполняете свой DI-контейнер)
Шаблон декоратора - это метод соблюдения принципа O в SOLID:
Открыть длярасширение и закрыто для модификации
Это означает, что вы будете использовать шаблон декоратора для декорирования существующего класса, класс, который не должен быть изменен и все же делаетне проявлять поведение требуется.Подсказка заключается в названии шаблона: Декоратор добавляет что-то, но не не меняет ничего.
Шаблон декоратора - это Структурный паттерн , тогда как Стратегический паттерн и то, что вы ищете, это Поведенческий паттерн
Этот примерКонечно, его можно расширить, чтобы сообщить о стратегии, которая в конечном итоге использовалась, а также (если потребуется) обоснование того, почему альтернативных стратегий не было.
Отредактировано: в ответ на комментарий Блинди ниже.Вот парадигма для шаблона декоратора, которая должна продемонстрировать, что он не является правильным шаблоном для этой задачи:
class Image
{
void Render() { /* */ }
}
class FramedImage : Image
{
private Image _originalImage;
public FramedImage(Image original) => _originalImage = original;
new public void Render()
{
/* code to render a frame */
_originalImage.Render();
}
}
Image originalImage = new Image();
Image framedImage = new FramedImage(originalImage);
Image toRender = originalImage;
toRender.Render() // Renders the original image
toRender = framedImage;
toRender.Render(); // Renders the original image in a frame
Следует заметить, что не требуется для присвоения каждого изображения переменной toRender, что делается исключительно для демонстрации того, что декоратор имеет декорацию.
Как вы можете видетьиз этого примера шаблон декоратора добавляет поведение и также вызывает поведение декорированного элемента .
Отредактировано: в дополнение к вопросу, поставленному DSF ниже ,Вот полный список консольного приложения, демонстрирующий, как этого добиться с помощью Unity 5.8.6
. Код использует преимущества нового кортежа из C # 7.0.Я только что использовал генерацию случайных чисел, чтобы определить, удастся ли каждой реализации стратегии выполнить свою задачу.
using System;
using System.Collections.Generic;
using System.IO;
using Unity;
using Unity.Injection;
namespace StrategyPattern
{
public interface IFileBackupContext
{
FileStream ForBackup { set; }
(bool success, string strategy) Backup();
}
public interface IFileBackupStrategy
{
(bool success, string name) Backup(FileStream fileToBackup);
}
internal class LocalBackUp : IFileBackupStrategy
{
private bool _success = false;
public (bool success, string name) Backup(FileStream fileToBackup)
{
// code to do backup omitted
var random = new Random(DateTime.Now.Millisecond);
_success = (random.Next() % 3) == 0;
if(_success) fileToBackup.Close();
// it will set the value of _success to false if it was unsuccessful
return (_success, "LocalBackUp");
}
}
internal class CloudBackUp : IFileBackupStrategy
{
private bool _success = false;
public (bool success, string name) Backup(FileStream fileToBackup)
{
// code to do backup omitted
var random = new Random(DateTime.Now.Millisecond);
_success = (random.Next() % 3) == 0;
if (_success) fileToBackup.Close();
// it will set the value of _success to false if it was unsuccessful
fileToBackup.Close();
return (_success, "CloudBackUp");
}
}
public class FileBackupContext : IFileBackupContext
{
private readonly IEnumerable<IFileBackupStrategy> _backupStrategies;
public FileBackupContext(IEnumerable<IFileBackupStrategy> backupStrategies)
=> _backupStrategies = backupStrategies;
public FileStream ForBackup { set; private get; }
public (bool success, string strategy) Backup()
{
foreach (var strategy in _backupStrategies)
{
var (success, name) = strategy.Backup(ForBackup);
if (success) return (true, name);
}
return (false, "");
}
}
public class MyBackupClient
{
private IFileBackupContext _context;
public MyBackupClient(IFileBackupContext context) => _context = context;
public void BackgUpMyFile()
{
_context.ForBackup = new FileStream("d:\\myfile", FileMode.OpenOrCreate);
(bool success, string strategy) = _context.Backup();
if (!success)
{
Console.WriteLine("Failed to backup the file");
return;
}
Console.WriteLine($"File backed up using [{strategy}] strategy");
}
}
public class Bootstrap
{
private readonly IUnityContainer _container;
public Bootstrap()
{
_container = new UnityContainer();
_container.RegisterType<IFileBackupContext, FileBackupContext>();
_container.RegisterType<IFileBackupStrategy, LocalBackUp>("local");
_container.RegisterType<IFileBackupStrategy, CloudBackUp>("cloud");
_container.RegisterType<MyBackupClient>();
_container.RegisterType<Func<IEnumerable<IFileBackupStrategy>>>(new InjectionFactory(c =>
new Func<IEnumerable<IFileBackupStrategy>>(() =>
new[]
{
c.Resolve<IFileBackupStrategy>("local"),
c.Resolve<IFileBackupStrategy>("cloud")
}
)));
}
public MyBackupClient GetClient() => _container.Resolve<MyBackupClient>();
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press ESC to quit ...");
Console.WriteLine("Press any other key to try again.");
Console.WriteLine();
var client = new Bootstrap().GetClient();
do
{
client.BackgUpMyFile();
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
}