Memento Pattern oder Data Transfer Object

Oder: Die Odyssee meines verwirrten Geistes.

Als Thomas Mentzel das Memento Pattern in seinem Blog beschrieb,
hab ich mir das Ding mal näher angeschaut, denn ich muss ehrlich zugeben,
ich habe es noch nie verwendet. Noch ein Blick auf das structural Sample bei DoFactory, und ich
schrieb mein eigenes Beispiel (learning by doing).

   public class Person

   {

      public string Name { get; set; }

 

      public DateTime BirthDate { get; set; }

 

      public Memento SaveMemento()

      {

         return new Memento(Name, BirthDate);

      }

 

      public void RestoreMemento(Memento memento)

      {

         Name = memento.Name;

         BirthDate = memento.BirthDate;

      }

   }

 

   public class Memento

   {

      public Memento(string name, DateTime birthDate)

      {

         Name = name;

         BirthDate = birthDate;

      }

 

      public string Name { get; private set; }

 

      public DateTime BirthDate { get; private set; }

   }

 

   public class PersonMemory

   {

      public Memento @Memento { get; set; }

   }


      [TestMethod]

      public void MementoTest()

      {

         var repo = new PersonMemory();

         var person = TestData.StaticPerson;

 

         repo.Memento = person.SaveMemento();

         person = new Person(); // loose content

         person.RestoreMemento(repo.Memento);

 

         person.BirthDate.Should().Be.EqualTo(TestData.StaticPerson.BirthDate);

         person.Name.Should().Be.EqualTo(TestData.StaticPerson.Name);

      }

Gleich kam mir der Gedanke: Im Prinzip handelt es sich hier doch um ein
Data Transfer Object (DTO) mit einem Repository.
Person ist ein DTO, PersonMemory ist die Repository (in-Memory) und das Memento
ist eine Art Kontextklasse zwischen den beiden.

Das sagt Martin Fowler über DTOs:
An object that carries data between processes in order to reduce the number of method calls.
und
When you're working with a remote interface, such as Remote Facade (388), each call to it is expensive. ...
The solution is to create a Data Transfer Object that can hold all the data for the call.
It needs to be serializable to go across the connection. ...

Nun wollte ich den Unterschied sehen und schrieb das Beispiel von oben um - zunächst in einer Form,
die näher an dem liegt, was ich im Kopf hatte – aber noch nicht so weit entfernt dass man die Verwandschaft womöglich nicht mehr erkennt.

using System;

using System.Runtime.Serialization;

using DotNetExpansions.IO;

 

namespace DtoAndRepo

{

   /// <summary>

   /// PersonDTO

   /// </summary>

   public class Person

   {

      public string Name { get; set; }

 

      public DateTime BirthDate { get; set; }

 

      public Memento SaveMemento()

      {

         return new Memento(Name, BirthDate);

      }

 

      public void RestoreMemento(Memento memento)

      {

         Name = memento.Name;

         BirthDate = memento.BirthDate;

      }

   }

 

   /// <summary>

   /// XmlDataContext

   /// </summary>

   [DataContract]

   public class Memento

   {

      public Memento(string name, DateTime birthDate)

      {

         BirthDate = birthDate;

         Name = name;

      }

 

      [DataMember]

      public string Name { get; set; }

 

      [DataMember]

      public DateTime BirthDate { get; set; }

   }

 

   /// <summary>

   /// XmlRepo

   /// </summary>

   public class PersonMemory

   {

      private const string RELATIVE_PATH = "../../DtoAndRepoSampleData.xml";

      private readonly Lazy<XmlIo> _io;

 

      public PersonMemory()

      {

         _io = new Lazy<XmlIo>(() => new XmlIo(RELATIVE_PATH));

      }

 

      /// <summary>

      /// XmlDataContext

      /// </summary>

      public Memento @Memento

      {

         get { return _io.Value.Load<Memento>(); }

         set { _io.Value.Save(value); }

      }

   }

}

Anmerkung: Ich verwende hier meine selbstentwickelte XmlIo Library.

Man kann hier schon erkennen wo das ganze münden soll. Mir war nur nicht klar, welchen Sinn diese Memento-Klasse hat.
Warum liegt sie dazwischen? In der nächsten Variante des Codes existiert sie nicht mehr.

using System;

using DotNetExpansions.IO;

 

namespace DtoAndRepo

{

   /// <summary>

   /// Ich wäre ja selber nicht auf die Idee gekommen,

   /// noch ein Objekt zwischen Entity und Repo zu hängen (Memento).

   /// Darum hier mal meine Variante.

   /// </summary>

   [Serializable]

   public class PersonDto

   {

      private Repository<PersonDto> repo;

 

      public PersonDto()

      {

         repo = new Repository<PersonDto>();

      }

 

      public string Name { get; set; }

 

      public DateTime BirthDate { get; set; }

 

      public void Save()

      {

         repo.SaveEntity(this);

      }

 

      public void Restore()

      {

         var personDto = repo.LoadEntity();

         Name = personDto.Name;

         BirthDate = personDto.BirthDate;

      }

   }

 

   /* Warum habe ich diese Funktionalität nicht auch gleich ins DTO geschrieben?

    * Ich stelle mir vor, es gibt mehrere (viele) DTOs im Projekt.

    * So vermeide ich redundanten Code. */

   public class Repository<T> where T : class

   {

      private const string RELATIVE_PATH = "../../DtoAndRepoSampleData.xml";

      private readonly Lazy<XmlIo> _io;

 

      public Repository()

      {

         _io = new Lazy<XmlIo>(() => new XmlIo(RELATIVE_PATH));

      }

 

      public T LoadEntity()

      {

         return _io.Value.Load<T>();

      }

 

      public void SaveEntity(T entity)

      {

         _io.Value.Save(entity);

      }

   }

}


      [TestMethod]

      public void MyVariantTest()

      {

         var personDto = TestData.StaticPersonDto;

 

         personDto.Save();

         personDto = new PersonDto(); // loose content

         personDto.Restore();

 

         personDto.BirthDate.Should().Be.EqualTo(TestData.StaticPerson.BirthDate);

         personDto.Name.Should().Be.EqualTo(TestData.StaticPerson.Name);

      }

Nachdem ich so weit war, schaute ich nochmal In den Blog-Artikel von Thomas Mentzel.
Dort steht gleich am Anfang

Im Memento-Pattern geht es darum, den internen Status eines Objektes zu speichern und wieder herstellen zu können.

Auch die Gang of four beschreibt es so:

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

Tja, statt mich direkt auf den Code zu stürzen, hätte ich besser erst
einmal die Beschreibung lesen sollen! Offenbar geht es bei dem Memento
Pattern nicht darum, Domain-Objekte dauerhaft zu persistieren. Der Sinn ist wohl eher
die temporäre Zwischenablage - in welchem Kontext auch immer.

kopftisch

DotNetKicks-DE Image
Published Samstag, 29. Januar 2011 18:19 von Rainer Hilmer
Abgelegt unter:

Kommentare

# re: Memento Pattern oder Data Transfer Object

Sonntag, 30. Januar 2011 12:42 von Christian Glowinski

Eine gute Darstellung. Die Idee, wie im 2. Beispiel, die Memento-Klasse mit einem DataContract auszustatten ist gut. Dadurch kann man es als temporäre Ablage benutzen, zum Beispiel für ein Undo. Gleichzeitig könnte man aber auch zum Beispiel die Undo-Liste persistieren und für den nächsten Programmstart verfügbar machen, sodass ein Undo auch nach einem Programm neustart möglich wäre. Ich denke, dass dies ein guter Mittelweg ist!

Weiter so...

# Memento Pattern oder Data Transfer Object

Sonntag, 30. Januar 2011 13:16 von dotnet-kicks.de

Sie wurden gekickt (eine gute Sache) - Trackback von dotnet-kicks.de

# re: Memento Pattern oder Data Transfer Object

Sonntag, 30. Januar 2011 13:23 von Rainer Hilmer

Hallo Christian,

Danke für deinen Kommentar. Mir ist das heute Nacht auch noch einmal durch den Kopf gegangen. Ein persistentes Memento hat was. :-)

Die DataContract- und DataMember-Attribute haben sich zwangsläufig ergeben (war gar nicht geplant) weil meine XMLio Library das bei solchen Objekten verlangt. *pfeif*

# re: Memento Pattern oder Data Transfer Object

Sonntag, 30. Januar 2011 15:42 von Mario

Ich verstehe nur Bahnhof : )

# re: Memento Pattern oder Data Transfer Object

Sonntag, 30. Januar 2011 15:47 von Thomas Mentzel

Es ist immer interessant andere Gedanken zu Design Pattern zu lesen. Hier mal meine Gedanken zu den Deinen ;)

Reduziert man eine Klasse auf ein DTO entspricht das Memento dem DTO. Das Memento ist nicht mehr als eine auf die Daten reduzierte Klasse. Die Idee hinter Memento ist -wie du festgestellt hast- nicht das Persistieren von Änderungen sondern eher im Sinne von transienten Transaktionen.

Ich hatte aber schon überlegt ob es möglich ist, eine Transaktion über eine Transaction-Klasse bereitzustellen. Diese erstellt die Mementos automatisch... aber das ist im Moment noch eine unreife Idee.

Weiter so und immer daran denken: Wer nicht hinfällt, der kann nicht gestärkt wieder aufstehen.

Kommentar abgeben

(verpflichtend) 
(verpflichtend) 
(optional)
(verpflichtend)