File History is a behavioral design pattern:
š¤š¤š¤š¤š¤
What is File History (Memento)?
File History is a design pattern that allows an object to save and restore its internal state without exposing its implementation details. It can be used to implement undo/redo functionality, checkpoints, snapshots, etc.
Why use File History?
File History has the following trade-offs:
Advantages:
- It preserves the encapsulation of the originator object by not exposing its internal state.
- It simplifies the originator object by delegating the state management to other objects.
- It allows multiple files to be stored and restored at different times.
Disadvantages:
- It may consume a lot of memory if the file history are large or numerous.
- It may introduce complexity and dependencies between the originator(Photoshop), file_history and file objects.
Question: I hate the File History, what are the alternatives?
Answer: You can use a simpler solution such as storing the state in a public variable or a database, but you may lose the benefits of encapsulation, abstraction and undo/redo functionality.
When to use File History?
Question: When should I use File History?
Answer: You should use File History when you need to save and restore the state of an object without violating its encapsulation, and when you need to support multiple undo/redo operations.
You are working on a Photoshop file class that can undo and redo different file histories and perform actions. You want to save and restore the state of the file at each point in time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Photoshop:
def __init__(self):
self._content = ""
# Make the content attribute read-only
@property
def content(self):
return self._content
# Edit the image by adding some text
def edit(self, text):
self._content += text
# Save the current state of the image inside a file
def save(self):
return File(self.content)
# Load the previous state of the image from a file
def load(self, file):
self._content = file.content
# Define how the photoshop object is printed
def __str__(self):
return self.content
|
Expected Output:
You can save and restore the state of the file using File History.
1
2
3
|
Current: RedGreenBlue
Undo: RedGreen
Undo: Red
|
How to implement File History?
Non-File History implementation:
A simple solution is to store the state of the file in a public variable or a database, but:
-
It violates the Single Responsibility Principle:
which states that a class should have only one reason to change.
In this case, the Photoshop class is responsible for both editing and undoing the content, which means that it has two reasons to change.
This makes the class more complex and harder to maintain.
-
Another possible disadvantage is that it violates the Open-Closed Principle:
which states that a class should be open for extension but closed for modification.
In this case, the Photoshop class is not open for extension because it hard-codes the logic for undoing the edits.
If we want to add more features such as redoing or saving multiple versions of the content, we would have to modify the class and risk breaking the existing functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
class File:
def __init__(self, content):
self._content = content
@property
def content(self):
return self._content
def __str__(self):
return self.content
class Photoshop:
def __init__(self):
self._content = ""
self._undo_stack = [] # keep track of previous contents for undoing
@property
def content(self):
return self._content
def edit(self, text):
self._undo_stack.append(self._content) # push the current content to the undo stack
self._content += text # update the content with the new text
def save(self):
return File(self.content)
def load(self, file):
self._content = file.content
def undo(self): # undo the last edit
if self._undo_stack: # check if there is anything to undo
self._content = self._undo_stack.pop() # pop the last content from the undo stack and set it as the current content
else:
print("Nothing to undo.")
def __str__(self):
return self.content
if __name__ == "__main__":
photoshop = Photoshop()
photoshop.edit("Red")
photoshop.save()
photoshop.edit("Green")
photoshop.save()
photoshop.edit("Blue")
photoshop.save()
print(f"Current: {photoshop}")
photoshop.undo()
print(f"Undo: {photoshop}")
photoshop.undo()
print(f"Undo: {photoshop}")
|
File History implementation:
A better solution would be to use the File History design pattern,
which involves creating three classes: Originator (Photoshop), Memento (File), and Caretaker (SSDStorage).
This pattern allows an object to save and restore its state without exposing its internal details. This way, we can separate the editing and undoing responsibilities into different classes and make them more cohesive and modular.
- The Photoshop class is the originator that can create and restore files of its state.
- The File class is a memento that stores the state of the Photoshop object.
- The SSDStorage class is a caretaker that manages (saves and loads) files."
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
# File class for saving the file data
class File:
def __init__(self, content):
self._content = content
# Make the content attribute read-only
@property
def content(self):
return self._content
# Define how the file object is printed
def __str__(self):
return self.content
# Photoshop class for editing images
class Photoshop:
def __init__(self):
self._content = ""
# Make the content attribute read-only
@property
def content(self):
return self._content
# Edit the image by adding some text
def edit(self, text):
self._content += text
# Save the current state of the image inside a file
def save(self):
return File(self.content)
# Load the previous state of the image from a file
def load(self, file):
self._content = file.content
# Define how the photoshop object is printed
def __str__(self):
return self.content
# SSDStorage class for managing files
class SSDStorage:
def __init__(self):
self.file_history = []
# Save a file to the history list
def save(self, file):
self.file_history.append(file)
# Get the last file from the history list and remove it
def pop_previous_file(self):
try:
return self.file_history.pop()
except IndexError:
print("No more files to pop.")
return None
if __name__ == "__main__":
ssd_storage = SSDStorage()
photoshop = Photoshop()
photoshop.edit("Red")
_file = photoshop.save()
ssd_storage.save(_file)
photoshop.edit("Green")
_file = photoshop.save()
ssd_storage.save(_file)
photoshop.edit("Blue")
photoshop.save()
print(f"Current: {photoshop}") # Current: RedGreenBlue
_file = ssd_storage.pop_previous_file()
photoshop.load(_file)
print(f"Undo: {photoshop}") # Undo: RedGreen
_file = ssd_storage.pop_previous_file()
photoshop.load(_file)
print(f"Undo: {photoshop}") # Undo: Red
|
This way, the user can undo and redo their edits without knowing how the Photoshop object works internally.
Source Code