25 Mart 2015 Çarşamba

GoF - Memento Örüntüsü - Belki Snapshot Denilebilir

Giriş
Memento kelime anlamı olarak "hatıra/yadigar" manasına geliyor. İngilizce anlamı şöyle
an object that you keep to remember a person, place, or event
Yazılım dünyasında Memento örüntüsü, bir nesnenin iç verisini bilmeden, nesnenin belli bir andaki halini saklayabilmemiz sağlar. Böylece istenilen bir anda, geriye dönebiliriz. Bu tasarım örüntüsü undo veya rollback işlemlerinde kullanılır.

Undo Framework
QT Undo Framework için Memento yerine Command örüntüsünü kullanmış. Memento sınıfı yerine QUndoCommand, Caretaker sınıfı yerine QUndoStack nesnesi kullanılmış.

Örüntüdeki nesneler ve görevleri
- Originator undo veya restore edilmesi gereken domain nesnedir. Memento parametresi alan save() ve restore() metodları sunar.
- Memento ise Originator'ın verisini saklar.
- Caretaker nesnesi Memento'ların listesini saklar.

Şeklen şöyle


Bazı notlar şöyle
- We can make Memento Design Pattern implementation more generic by using Serialization; that will eliminate the requirement of every class having its own Memento class.
- The Memento Design Pattern can also be used with the Command Design Pattern for achieving undo of the commands.
Java
Örnek
Originator şöyle tanımlanır
public class Employee {
  ...    
  public EmployeeMemento createMemento() {
    return new EmployeeMemento(empId, name, designation, salary, department, project);
  }
  public void restore(EmployeeMemento memento) {
    this.empId = memento.empId;
    ...
  }
}
Memento nesnesi sadece veriyi saklar
public class EmployeeMemento {
  ...
  public EmployeeMemento(int empId, String name, String designation, long salary,...) {
    ...
  }
}
Caretake sadece Memento listelerini saklarr
public class EmployeeCaretaker {
  protected Map<Integer, Map<String, EmployeeMemento>> mementoHistory = ...;
  public void addMemento(int empId, String mementoMessage, EmployeeMemento memento) {
    ...
  }
  public EmployeeMemento getMemento(int empId, String mementoMessage) {
    ...
  }
}
Kullanmak için şöyle yaparız
public static void main(String[] args) {
  EmployeeCaretaker caretaker = ...
  Employee rachel = ...
  Employee michael = ...

  //Save
  EmployeeMemento rachelMemento = rachel.createMemento();
  EmployeeMemento michaelMemento = michael.createMemento();
  caretaker.addMemento(racheal.getEmpId(), "...", rachelMemento);
  caretaker.addMementomichael.getEmpId(), "...", michaelMemento);
  
  //Update domain objects
  rachel .setDesignation("Manager").setSalary(120000);
  michael.setProject("Android App");

  //Get saved snapshots
  rachelMemento = caretaker.getMemento(rachel.getEmpId(),"...");
  michaelMemento = caretaker.getMemento(micheal.getEmpId(), "...");

  //Restore
  rachel.restore(rachelMemento);
  michael.restore(michaelMemento);
}
Örnek
Bir projede her Originator için farklı bir Memento sınıfı yazmak yerine şöyle yaptım. Böylece her Originator'a createMemento(), restoreMemento() gibi bir şey yazmaya da gerek kalmadı. Her takeSpanshot() işleminde Memento nesnesi bir isim listede saklandı.
Memento memento = new Memento();
try(ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
    GZIPOutputStream zos = new GZIPOutputStream(baos);
    ObjectOutputStream os = new ObjectOutputStream (zos);
  ) {
    foo.serialize(os);
    bar.seriazlie(os);

    os.close();

    memento.setData(baos.toByteArray();
}  

try(ByteArrayInputStream bais = new ByteArrayInputStream (memento.getData());
    GZIPInputStream zis = new GZIPInputStream(bais);
    ObjectInputStream is = new ObjectInputStream (zis);
  ) {
    Foo foo = (Foo)is.readObject();
    Bar bar = (Bar)is.readObject();

}
Örnek
Originator ve Memento bazen içiçe (nested) sınıflar şeklinde tanımlanırlar.Originator sınıfının save ve restore metodları bulunur. Memento sınıfı Java gibi dillerde verinin deep copy'si ile çalışmalıdır. Bu yüzdenveri clone'lanır. Örnekte Clone kütüphanesi kullanılıyor.
public class Originator {
  private String state;
  private Integer code;
  private Map<String, String> parameters;

  public Memento save() {
    return new Memento(this);
  }

  public void restore(Memento memento) {
    this.state = memento.getState();
    this.code = memento.getCode();
    this.parameters = memento.getParameters();
  }

  /**
   * We use Memento class for making a snapshot of Originator state.
   */
  public class Memento {
    private String state;
    private Integer code;
    private Map<String, String> parameters;

    public Memento(Originator o) {
      Cloner cloner = new Cloner();
      this.state = cloner.deepClone(o.state);
      this.code = cloner.deepClone(o.code);
      this.parameters = cloner.deepClone(o.parameters);
    }
  }
}
Caretaker basit bir liste sınıfıdır.
public class Caretaker {

  List<Originator.Memento> snapshots;

  public Caretaker() {
    this.snapshots = new ArrayList<Originator.Memento>();
  }

  public void addSnapshot(Originator.Memento memento) {
    snapshots.add(memento);
  }
    
  public Originator.Memento find(Criteria criteria, Object key) {
    //...
  }
}
Tüm örüntünün kullanılması
Originator originator = new Originator();

//Save with Memento the state of Originator
Caretaker caretaker = new Caretaker();
caretaker.addSnapshot(originator.save());

//Search with Caretaker
Originator.Memento m = caretaker.find(Criteria.ByCode, 500);

//Restore originator
originator.restore(m);





Hiç yorum yok:

Yorum Gönder