Обсуждение:Хранитель (шаблон проектирования)

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску

Другой вариант шаблона[править код]

Добрый день,

Во-первых, данная реализация шаблона нарушает инкапсуляцию, поскольку на самом деле мы отдаём наружу объект с публичными полями/методами доступа к ним, хранящими наши внутренние данные. Кроме того, кто-либо может вызвать конструктор Memento, передав ему свои данные, в итоге позволяется изменить состояние объекта Originator на совершенно произвольное. Это можно исправить в C++, сделав Originator friend-классом Memento, а конструктор закрыть и все методы доступа закрыть. В C# и Java такого способа, на сколько я себе представляю нет (разве только если сделать класс Originator внутренним для Memento(статическим внутренним для Java)).

Во-вторых, в случае, если в системе присутствует более одного объекта типа Originator, имеющих при этом разные интерфейсы, в данном варианте шаблона для каждого типа Originator придётся создать свой тип Memento и свой класс Caretaker:

public interface IShape
{
   void Draw();
}

public class CircleMemento
{
   public readonly double r;

   public CircleMemento(double r)
   {
      this.r = r;
   }
}

public class CircleOriginator : IShape
{
   double r;

   public void CircleOriginator(double r)
   {
      this.r = r;
   }

   public void Draw()
   {
      Console.WriteLine("Circle {0}", r);
   }

   public CircleMemento GetState()
   {
      return new CircleMemento(r);
   }

   public void SetState(CircleMemento m)
   {
      r = m.r;
   }
}

public class RectMemento
{
   public readonly double w;
   public readonly double h;

   public RectMemento(double w, double h)
   {
      this.w = w;
      this.h = h;
   }
}

public class RectOriginator : IShape
{
   double w;
   double h;

   public RectOriginator(double w, double h)
   {
      this.w = w;
      this.h = h;
   }

   public void Draw()
   {
      Console.WriteLine("Rectangle {0}x{1}", w, h);
   }

   public RectMemento GetState()
   {
      return new RectMemento(w, h);
   }

   public void SetState(RectMemento m)
   {
      w = m.w;
      h = m.h;
   }
}

Всё ухудшается, если требуется сохранить состояние сразу нескольких объектов системы. Логичным выглядело бы использовать шаблон компоновщик для снятия снимка сразу всей группы объектов, следовательно интерфейс у всех объектов Originator отличаться не должен:

public interface IOriginator
{
   ??? GetState();
   void SetState(??? state);
}

Как видно, в этом случае требуется либо каким-либо образом привести Memento к одному интерфейсу. Плохой вариант совсем убрать указание типа.

public interface IOriginator
{
   object GetState();
   void SetState(object state);
}

но в этом случае внутри появляется приведение типов.

Поэтому я предлагаю иную структуру шаблона Caretaker/Memento. Пусть восстановление состояния объекта перейдёт к объекту Memento, тогда всё будет выглядеть несколько иначе:

public interface IShape
{
   void Draw();
}

public interface IMemento
{
   void RestoreState();
}

public interface IOriginator
{
   IMemento GetState();
}

public class CircleOriginator : IShape, IOriginator
{
   private class CircleMemento : IMemento
   {
      private readonly double r;
      private readonly CircleOriginator originator;

      public CircleMemento(CircleOriginator originator)
      {
         this.originator = originator;
         r = originator.r;
      }

      public void Restore()
      {
         originator.r = r;
      }
   }

   double r;

   public void CircleOriginator(double r)
   {
      this.r = r;
   }

   public void Draw()
   {
      Console.WriteLine("Circle {0}", r);
   }

   public CircleMemento GetState()
   {
      return new CircleMemento(this);
   }
}

public class RectOriginator : IShape, IOriginator
{
   private class RectMemento : IMemento
   {
      private readonly double w;
      private readonly double h;
      private readonly RectOriginator originator;

      public RectMemento(RectOriginator originator)
      {
         this.originator = originator;
         w = originator.w;
         h = originator.h;
      }

      public void Restore()
      {
         originator.w = w;
         originator.h = h;
      }
   }

   double w;
   double h;

   public void RectOriginator(double w, double h)
   {
      this.w = w;
      this.h = h;
   }

   public void Draw()
   {
      Console.WriteLine("Rectangle {0}x{1}", w, h);
   }

   public IMemento GetState()
   {
      return new RectMemento(this);
   }
}

Этот подход позволяет сохранять состояние очень сложных систем. В частности можно очень легко сохранить состояние составной фигуры, которая состоит из нескольких IShape (правда придётся слить воедино два интерфейса IShape и IOriginator). В этом случае запрос состояния составной фигуры вернёт Memento, состоящий из нескольких Memento внутренних объектов и восстанавливающий структуру базового объекта:

public interface IShape
{
   void Draw();
   void RestoreState();
}

public class CompoundOriginator : IShape
{
   private class CompoundMemento : IMemento
   {
      private readonly CompoundMemento originator;
      private readonly List<IShape> shapes;
      private readonly List<IMemento> mementos;

      public CompoundMemento(CompoundOriginator originator)
      {
         this.originator = originator;
         shapes = new List<IShape>(originator.shapes);
         mementos = new List<IMemento>();
         foreach(var shape in shapes)
         {
            mementos.Add(shape.GetState());
         }
      }

      public void Restore()
      {
         foreach(var memento in mementos)
         {
            memento.Restore();
         }
         originator.shapes = shapes;
      }
   }

   List<IShape> shapes;

   public void CompoundOriginator()
   {
      shapes = new List<IShape>();
   }

   public void Draw()
   {
      Console.WriteLine("Compound shape:");
      foreach(var shape in shapes)
      {
         shape.Draw();
      }
   }

   public IMemento GetState()
   {
      return new CompoundMemento(this);
   }
}
Герыч 12:54, 21 апреля 2012 (UTC)[ответить]