Использование шаблона декоратора для условного замещения поведения вместо расширения - PullRequest
0 голосов
/ 19 сентября 2018

Изначально у меня была такая структура:

interface IFileBackup
{
    void Backup();
}

class BackUpMechanism1 : IFileBackup
{
    void Backup()
    {
        //Back it up
    }
}

class BackUpMechanism2 : IFileBackup
{
    void Backup()
    {
        //Back it up in another way
    }
}

class Client 
{
    //Instantiation of both mechanisms
    //

    try
    {
        backUpMechanism1.Backup();
    }
    catch(Exception ex)
    {
        backupMechanism2.Backup();
    }
}

Мне сказали, что это не очень чистый дизайн, и перепроектировать его, используя шаблон декоратора.Клиент не должен знать о двух механизмах резервного копирования, а просто вызвать резервное копирование, а затем первый механизм должен попытаться выполнить резервное копирование файла, а в случае сбоя использовать механизм 2. Однако я не понимаю, как использовать шаблон декоратора, потому что изв моем понимании это расширяет функциональность, но не заменяет функциональность - это то, что я хочу ... Как мне это заархивировать?Я пробовал следующее:

interface IFileBackup
{
    void Backup();
}

class BackupMechanism1 : IFileBackup
{
    public void Backup()
    {
        try
        {
            Console.WriteLine("Trying to back up to the cloud...");
            throw new Exception();
        }
        catch(Exception ex)
        {
            Console.WriteLine("Oops that failed. We need to back up locally instead...");
        }

    }
}

class BackupMechanism2 : IFileBackup
{
    IFileBackup _fileBackup;
    public BackupMechanism2(IFileBackup fileBackup)
    {
        _filebackup = fileBackup;
    }

    public void Backup()
    {
        //All examples I have seen does this. But doesn't make sense in my case?
        _fileBackup.Backup();

        Console.WriteLine("Backing up locally");
    }
}

//The client does not care about how the backup is done
class Client
{
    static void Main()
    {
        //This is not right, but not sure what I should do in the client.
        BackupMechanism2 localBackup = new BackupMechanism2(new BackupMechanism1());
        localBackup.Backup();

        Console.Read();
    }
}

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

Ответы [ 3 ]

0 голосов
/ 19 сентября 2018

Очень чистым подходом для реализации этого было бы добавление составного IFileBackup, берущего массив IFileBackup объектов и пробующего их один за другим, пока не будет найдено рабочее решение:

class CompositeBackup {
    private readonly IFileBackup[] chain;
    public CompositeBackup(params IFileBackup[] chain) {
        this.chain = chain.ToArray();
    }
    public void Backup() {
        foreach (var backup in chain) {
            try {
                backup.Backup();
                return;
            } catch {
                continue;
            }
        }
        throw new InvalidOperationException();
    }
}

Сейчасклиент просто делает это:

IFileBackup backup = new CompositeBackup(
    new BackupMechanism1()
,   new BackupMechanism2()
);
backup.Backup();

Если позже вы решите добавить BackupMechanism3 и BackupMechanism4, пользователю потребуется добавить еще один объект в цепочку резервных копий.Остальная часть кода останется неизменной.Кроме того, сами механизмы резервного копирования не будут знать о существовании других механизмов, что также упрощает код.

0 голосов
/ 19 сентября 2018

Шаблон декоратора - НЕПРАВИЛЬНЫЙ выбор в этом сценарии.

Проблема, с которой вы здесь сталкиваетесь, это

  • при условии 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);
      }
   }
}
0 голосов
/ 19 сентября 2018

Шаблон декоратора, в этом случае, может использоваться для обеспечения резервных реализаций.Вы можете найти множество очевидных примеров в реализации потоков .Net.

Так что с учетом этого ваш код должен выглядеть примерно так:

class abstract BaseFileBackup
{
  internal BaseFileBackup Fallback;
  internal BaseFileBackup(BaseFileBackup fallback) { Fallback = fallback; }
  internal BaseFileBackup() { }

  internal abstract void DoBackupWork();

  internal void Backup()
  {
    try { DoBackupWork(); }
    catch { if(Fallback != null) Fallback.Backup(); else throw; }
  }
}

class BackUpMechanism1 : BaseFileBackup
{
    internal BackUpMechanism1 (BaseFileBackup fallback): base(fallback) {}
    internal BackUpMechanism1 (): base() {}

    internal void DoBackupWork()
    {
        //Back it up
    }
}

class BackUpMechanism2 : BaseFileBackup
{
    internal BackUpMechanism2 (BaseFileBackup fallback): base(fallback) {}
    internal BackUpMechanism2 (): base() {}

    internal void DoBackupWork()
    {
        //Back it up in another way
    }
}

// and to call it
class Client
{
    static void Main()=>
        new BackupMechanism2(new BackupMechanism1()).Backup();
}
...