TransWikia.com

Could a getState() method in a Caretaker violates Memento Pattern?

Software Engineering Asked on December 17, 2021

I have looked at several examples for implementations of Momento on the web. I wonder if it is correct to retrieve the status of Originator, since this is much more accurate, up-to-date and reliable?

Is it correct to use a getState() method that allows me to gain a better understanding of the current status, in real-time? In my PHP example, I’ve used the following:

<?php
interface Originator {

    public function save(): Memento;
    public function restore(Memento $memento): void;
}

class OriginatorConcret implements Originator {

    private string $state;

    public function __construct(string $state) {
        $this->state = $state;
    }

    public function setState($state): void {
        $this->state = $state;
    }

    public function getState(): string {
        return $this->state;
    }

    public function save(): Memento { //snapshot
        return new Memento($this->state);
    }

    public function restore(Memento $memento): void {
        $this->state = $memento->getState();
    }

}

class Memento {

    private string $state;

    public function __construct(string $state) {
        $this->state = $state;
    }

    public function getState(): string {
        return $this->state;
    }

}

class Caretaker {

    private SplStack $mementos;
    private Originator $originator;

    public function __construct(Originator $originator) {
        $this->mementos = new SplStack();
        $this->originator = $originator;
    }

    public function persistCurrentState(): void {
        $this->mementos->push($this->originator->save());
    }

    public function undo(): void {
        if (!$this->mementos->count()) {
            return;
        }
        $memento = $this->mementos->pop();
        $this->originator->restore($memento);
    }

}

$originator = new OriginatorConcret("ABC 123");
$caretaker = new Caretaker($originator);

$caretaker->persistCurrentState();
$originator->setState("XYZ 456");

$caretaker->persistCurrentState();
$originator->setState("WSD 999");

$caretaker->persistCurrentState();
$originator->setState("POO 111");

$caretaker->undo();
$caretaker->undo();

echo $originator->getState();

Is this a violation? What would be the real purpose of the Originator state in a real situation, if we cannot access it? Please help. Thanks.

One Answer

In your particular example the memento doesn't really serve any purpose (or, at least, the reasons for having it aren't obvious). If you had a more complicated internal state that you didn't want to expose to clients, but you still needed a way for them to temporarily hold on to that state, then Memento helps. Simultaneously, you can have getters and setters for other, public parts of the state.

"What would be the real purpose of Originator state in a real situation if we can not access it?"

This pattern doesn't make (much1) sense if you are just using the object as a glorified data structure, with other code manipulating it. And don't get me wrong - there are situations where treating an object as a data structure is the way to go.

But if you have something that's more object-like (a thing that has its own behavior), then that code that manipulates state will mostly be in that object's methods, with state mostly being internal (no or few getters/setters). Clients would ask the object to do something, rather then asking it for data so that they themselves can do something with that data.

The methods of the object would maintain invariants (make sure that the state is kept consistent, that the internal rules are followed). If you expose the state, then a number of problems arise. E.g., the object may not be able to maintain as much control; if you allowed setters, if you're not careful, there might be a way to break the invariants. Or, you might not be able to as easily change the internals of the object if the outside world knows what all of those internals are (broken encapsulation).

Anyway, if you are in a situation where you are invested in keeping some part of the state encapsulated, but you need the clients to be able to temporarily hold on to, or save and restore, that state, then Memento is a solution to that.

And there are several ways to go about it. You can return an object that stores the actual state, but only allows the Originator to retrieve that state. Or you can keep the state completely internal (say in an internal array or a dictionary), and just return an index or a key (a opaque "handle"); in other words, a memento object can also represent something that identifies some internal data that enables the state to later be restored.

At the moment, I can't think of a real-world example in PHP, but here's one from .Net/Windows, that will hopefully clarify things. In C#, there's this Graphics class2 that enables you to draw things (lines, shapes, images). It also has the capability to manipulate the "canvas" (the drawing surface) - you can rotate it, scale it, move it around, etc. So, when drawing something, sometimes it's easier to temporarily reorient the canvas (to change the frame of reference, re-scale the view, change some other options), draw what you need, then restore everything back as it was, and continue drawing. One reason you might want to do so is to make some math simpler, say. The Graphics.Save method hands out a memento object that you can't do anything with, other than give it back to the Graphics.Restore method, that then restores things as they were when Graphics.Save was called. Note that you don't know, or need to know, anything about the internals of the Graphics class in order to be able to do this, and that all the logistics of saving and restoring the appropriate data is handled for you. This also enables Microsoft to release an update that changes the internals of the Graphics class, including the way this state is represented, remembered, and restored, and still have all this work without breaking your existing code that uses Save/Restore.


1 There is some benefit to it; if the state you want to save is fairly complicated (e.g., it's not as simple as retrieving a single object), then client code needs know what to save, and needs to keep track of all that data. In that case, a memento may make things simpler.

2 It's part of GDI+ API; see: Graphics class, Graphics.Save, Graphics.Restore

Answered by Filip Milovanović on December 17, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP