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

message: Добрый день,
Во-первых, данная реализация шаблона нарушает инкапсуляцию, поскольку на самом деле мы отдаём наружу объект с публичными полями/методами доступа к ним, хранящими наши внутренние данные.
Кроме того, кто-либо может вызвать конструктор 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)
thread_title:  Другой вариант шаблона