Snapshot
The Snapshot pattern captures the internal state of an object at a particular point in time, allowing it to be restored to that state later. This is achieved by creating a “snapshot” or “memento” of the object’s state, which is then stored by a separate “caretaker” object. The originator can then recreate itself from the snapshot if required.
This pattern is particularly useful when implementing features like undo/redo functionality, transaction management, or version control. It allows for state recovery without violating encapsulation, as the snapshot holds the internal state without exposing it directly to the caretaker. This isolation preserves the originator’s control over its data.
Usage
The Snapshot pattern is widely used in scenarios requiring state persistence and recovery:
- Undo/Redo functionality: Text editors, image manipulation software, and game engines use this pattern to enable users to undo or redo actions. Each action creates a snapshot of the application’s state before the action is applied.
- Transaction Management: Databases and financial systems utilize snapshots to ensure that transactions can be rolled back to a consistent state in case of failure.
- Version Control Systems: The core concept behind version control systems like Git is to maintain snapshots of files and directories over time, allowing users to revert to previous versions.
- Game Saving: Games often use snapshots to store the player’s progress, including the game world state, player statistics, and inventory.
Examples
-
Git: Git fundamentally relies on the Snapshot pattern. Every commit represents a snapshot of the entire project’s state at that moment. Git efficiently stores these snapshots by only saving the differences between versions. The
.gitdirectory acts as the caretaker, holding the history of snapshots. -
Redux (JavaScript Library): Redux uses a single immutable state tree. Actions trigger state changes, and a reducer function calculates the new state. Before each action, the current state is effectively a snapshot. The store maintains the history of these snapshots, allowing for time-travel debugging and implementing undo/redo features. The
combineReducersfunction and middleware like Redux Thunk leverage this snapshotting capability. -
Word/Google Docs: These applications use snapshots to automatically save documents at intervals, and to facilitate the undo/redo functionality. The application (Originator) creates a snapshot of the document’s state, and a background service (Caretaker) stores these snapshots.
Specimens
15 implementationsThe Snapshot pattern captures and restores the state of an object or system, allowing for rollbacks or efficient re-initialization. This is achieved by serializing the object’s state to a persistent storage (like a file) and then deserializing it to recreate that state. In Dart, this is naturally implemented using the encode and decode methods from the dart:convert library, often with JSON as the serialization format. The example demonstrates saving and restoring a simple Counter object’s value. Using JSON is idiomatic Dart for data serialization due to its readability and ease of use with Dart’s built-in data structures.
import 'dart:convert';
import 'dart:io';
class Counter {
int value;
Counter(this.value);
@override
String toString() => 'Counter(value: $value)';
Map<String, dynamic> toJson() => {'value': value};
factory Counter.fromJson(Map<String, dynamic> json) = Counter._;
Counter._(this.value);
}
Future<void> saveSnapshot(Counter counter, String filePath) async {
final json = counter.toJson();
final file = File(filePath);
await file.writeAsString(jsonEncode(json));
print('Snapshot saved to $filePath');
}
Future<Counter> loadSnapshot(String filePath) async {
final file = File(filePath);
final contents = await file.readAsString();
final json = jsonDecode(contents) as Map<String, dynamic>;
final counter = Counter.fromJson(json);
print('Snapshot loaded from $filePath');
return counter;
}
void main() async {
var counter = Counter(10);
print('Initial counter: $counter');
const filePath = 'counter_snapshot.json';
await saveSnapshot(counter, filePath);
counter = Counter(0); // Reset counter
print('Counter reset: $counter');
counter = await loadSnapshot(filePath);
print('Counter restored: $counter');
}
The Snapshot pattern captures the state of an object or data structure at a specific point in time without affecting the original. This allows for consistent reads, especially in concurrent environments, or for implementing undo/redo functionality. This Scala example uses immutable data structures (case classes) to achieve the snapshot. A takeSnapshot method creates a copy of the object’s state, and subsequent operations work on the snapshot, leaving the original untouched. Scala’s emphasis on immutability makes this pattern a natural fit, promoting thread safety and simplifying reasoning about state changes.
case class Data(value: Int)
object Snapshot {
def takeSnapshot(data: Data): Data = {
data.copy() // Creates a new Data instance with the same value
}
def main(args: Array[String]): Unit = {
var originalData = Data(10)
println(s"Original Data: $originalData")
val snapshot = takeSnapshot(originalData)
println(s"Snapshot: $snapshot")
originalData = originalData.copy(value = 20) // Modify the original
println(s"Modified Original Data: $originalData")
println(s"Snapshot (unchanged): $snapshot") // Snapshot remains at original state
}
}
The Snapshot pattern allows capturing and restoring the internal state of an object. This is useful for implementing features like undo/redo, transaction rollback, or saving game progress. The code defines a Report class representing the object whose state needs to be saved. A ReportSnapshot class stores the state of a Report at a specific point in time. The Report class provides a method to create snapshots and a method to restore from a snapshot. This implementation uses PHP’s object serialization to simplify snapshot storage and retrieval, which is a common and efficient approach in PHP.
<?php
class Report {
public string $title;
public string $content;
public int $version;
public function __construct(string $title, string $content) {
$this->title = $title;
$this->content = $content;
$this->version = 0;
}
public function update(string $newContent): void {
$this->content = $newContent;
$this->version++;
}
public function createSnapshot(): ReportSnapshot {
return new ReportSnapshot($this);
}
public function restore(ReportSnapshot $snapshot): void {
$this->title = $snapshot->title;
$this->content = $snapshot->content;
$this->version = $snapshot->version;
}
}
class ReportSnapshot {
public string $title;
public string $content;
public int $version;
public function __construct(Report $report) {
$this->title = $report->title;
$this->content = $report->content;
$this->version = $report->version;
}
}
// Example Usage
$report = new Report("Initial Report", "This is the first version.");
$snapshot1 = $report->createSnapshot();
$report->update("Updated content for version 2.");
$snapshot2 = $report->createSnapshot();
// Restore to the first snapshot
$report->restore($snapshot1);
echo "Restored Report Title: " . $report->title . "\n";
echo "Restored Report Content: " . $report->content . "\n";
echo "Restored Report Version: " . $report->version . "\n";
?>
The Snapshot pattern allows saving and restoring the internal state of an object. This is useful for implementing features like undo/redo, checkpoints, or transaction rollback. The core idea is to encapsulate the object’s state into a separate snapshot object, enabling reverting to that saved state without affecting the original object’s class definition. This Ruby implementation uses a simple hash to store the object’s state and provides methods to take and apply snapshots. Ruby’s dynamic nature and hash-based data structures make this a concise and natural fit.
# frozen_string_literal: true
class Account
attr_accessor :balance, :transactions
def initialize(balance = 0)
@balance = balance
@transactions = []
end
def deposit(amount)
@balance += amount
@transactions << amount
end
def withdraw(amount)
raise "Insufficient funds" if @balance < amount
@balance -= amount
@transactions << -amount
end
def take_snapshot
Snapshot.new(balance: @balance, transactions: @transactions.dup) # Deep copy transactions
end
def restore_snapshot(snapshot)
@balance = snapshot.balance
@transactions = snapshot.transactions.dup # Deep copy transactions
end
def to_s
"Balance: #{@balance}, Transactions: #{@transactions}"
end
end
class Snapshot
attr_reader :balance, :transactions
def initialize(balance:, transactions:)
@balance = balance
@transactions = transactions
end
end
# Example Usage
account = Account.new(100)
puts "Initial Account: #{account}"
snapshot = account.take_snapshot
account.deposit(50)
puts "After Deposit: #{account}"
account.withdraw(25)
puts "After Withdrawal: #{account}"
account.restore_snapshot(snapshot)
puts "Restored Account: #{account}"
The Snapshot pattern captures and restores the state of an object, allowing for easy undo/redo functionality or saving/loading of progress. It’s implemented here using Swift’s Codable protocol to serialize the object’s state into a snapshot (a dictionary in this case). The takeSnapshot() method creates a Codable representation of the object, and restoreSnapshot() applies the saved state. This approach leverages Swift’s built-in serialization capabilities, making the code concise and readable. Using a dictionary for the snapshot provides flexibility for future state expansion. The pattern is particularly well-suited for Swift due to its strong typing and protocol-oriented programming features.
import Foundation
class Account: Codable {
var balance: Double
var transactionHistory: [String]
init(balance: Double, transactionHistory: [String]) {
self.balance = balance
self.transactionHistory = transactionHistory
}
func deposit(amount: Double) {
balance += amount
transactionHistory.append("Deposited: \(amount)")
}
func withdraw(amount: Double) {
if balance >= amount {
balance -= amount
transactionHistory.append("Withdrawn: \(amount)")
} else {
transactionHistory.append("Withdrawal failed: Insufficient funds")
}
}
func takeSnapshot() -> [String: Any] {
let encoder = JSONEncoder()
if let data = try? encoder.encode(self) {
if let dictionary = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] {
return dictionary
}
}
return [:] // Return an empty dictionary if snapshotting fails
}
func restoreSnapshot(snapshot: [String: Any]) {
let decoder = JSONDecoder()
if let data = try? decoder.encode(snapshot), let account = try? decoder.decode(Account.self, from: data) {
self.balance = account.balance
self.transactionHistory = account.transactionHistory
}
}
}
// Example Usage
let account = Account(balance: 100.0, transactionHistory: ["Initial balance: 100.0"])
let snapshot = account.takeSnapshot()
account.deposit(amount: 50.0)
print("After deposit:")
print("Balance: \(account.balance)")
print("Transaction History: \(account.transactionHistory)")
account.restoreSnapshot(snapshot: snapshot)
print("\nAfter restoring snapshot:")
print("Balance: \(account.balance)")
print("Transaction History: \(account.transactionHistory)")
The Snapshot pattern captures the state of an object at a specific point in time without affecting the original object. This allows for operations like undo/redo, transaction rollback, or simply observing a past state. In this Kotlin example, we use data classes and copy() to create immutable snapshots of a SystemState object. The takeSnapshot() function returns a new SystemState instance with the current values, leaving the original unchanged. Kotlin’s data classes and immutability features make this pattern concise and safe, aligning with its functional programming aspects and emphasis on avoiding side effects.
data class SystemState(val temperature: Int, val pressure: Int, val humidity: Int)
class SystemController(private var state: SystemState) {
fun takeSnapshot(): SystemState {
return state.copy()
}
fun adjustTemperature(delta: Int) {
state = state.copy(temperature = state.temperature + delta)
}
fun getCurrentState(): SystemState {
return state
}
}
fun main() {
val controller = SystemController(SystemState(25, 1013, 60))
val snapshot1 = controller.takeSnapshot()
println("Snapshot 1: $snapshot1")
controller.adjustTemperature(5)
println("Current State after adjustment: ${controller.getCurrentState()}")
val snapshot2 = controller.takeSnapshot()
println("Snapshot 2: $snapshot2")
controller.adjustTemperature(-10)
println("Current State after further adjustment: ${controller.getCurrentState()}")
println("Restoring to Snapshot 1: ${SystemController(snapshot1).getCurrentState()}")
}
The Snapshot pattern captures and stores the internal state of an object at a specific point in time, allowing for rollback to that state. This is useful for implementing undo/redo functionality, checkpoints, or transaction management. The Rust implementation uses the Clone trait to create copies of the object’s state. A Snapshot struct holds this cloned state. The restore() method then replaces the current state with the saved snapshot. This approach leverages Rust’s ownership and borrowing rules to ensure memory safety during cloning and restoration, and the Clone trait is a natural fit for creating immutable copies of the object’s data.
#[derive(Clone)]
struct Document {
content: String,
version: u32,
}
impl Document {
fn new(initial_content: String) -> Self {
Document {
content: initial_content,
version: 1,
}
}
fn edit(&mut self, new_content: String) {
self.content = new_content;
self.version += 1;
}
fn take_snapshot(&self) -> Snapshot {
Snapshot {
document: self.clone(),
}
}
fn restore(&mut self, snapshot: &Snapshot) {
self.content = snapshot.document.content.clone();
self.version = snapshot.document.version;
}
}
struct Snapshot {
document: Document,
}
fn main() {
let mut doc = Document::new("Initial content".to_string());
println!("Original content: {}", doc.content);
doc.edit("First edit".to_string());
println!("After first edit: {}", doc.content);
let snapshot = doc.take_snapshot();
doc.edit("Second edit".to_string());
println!("After second edit: {}", doc.content);
doc.restore(&snapshot);
println!("Restored content: {}", doc.content);
}
The Snapshot pattern captures and restores the state of an object, allowing for easy rollbacks or saving/loading of progress. It’s particularly useful in scenarios like undo/redo functionality, game save states, or transaction management. In this Go example, we define a Report struct representing the object whose state needs to be captured. The Snapshot struct holds a copy of the Report’s required fields. The NewReportSnapshot function creates a snapshot, and the Restore function applies the snapshot to the original Report. This implementation leverages Go’s struct embedding and direct field copying for a concise and efficient snapshot mechanism.
package main
import "fmt"
// Report represents the object whose state we want to snapshot.
type Report struct {
Title string
Content string
Status string
}
// Snapshot represents the captured state of a Report.
type Snapshot struct {
Title string
Content string
Status string
}
// NewReportSnapshot creates a new snapshot of the Report.
func NewReportSnapshot(r Report) Snapshot {
return Snapshot{
Title: r.Title,
Content: r.Content,
Status: r.Status,
}
}
// Restore applies the snapshot to the original Report.
func (r *Report) Restore(s Snapshot) {
r.Title = s.Title
r.Content = s.Content
r.Status = s.Status
}
func main() {
report := Report{
Title: "Initial Report",
Content: "This is the initial content.",
Status: "Draft",
}
snapshot := NewReportSnapshot(report)
report.Content = "Content has been modified."
report.Status = "In Review"
fmt.Println("Report after modification:")
fmt.Printf("%+v\n", report)
report.Restore(snapshot)
fmt.Println("\nReport after restoring snapshot:")
fmt.Printf("%+v\n", report)
}
The Snapshot pattern captures and stores the internal state of an object at a specific point in time, allowing for restoration to that state later. This is useful for implementing undo/redo functionality, checkpoints, or transaction rollback. The C implementation uses a struct to hold the object’s state and functions to take and apply snapshots. It’s idiomatic C because it relies on explicit memory management and struct-based data representation, common practices in the language, avoiding complex object hierarchies or dynamic dispatch where unnecessary. The take_snapshot function copies the relevant data into a new snapshot struct, and restore_snapshot copies the data back from the snapshot to the original object.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Original object
typedef struct {
int value;
char *data;
} MyObject;
// Snapshot struct
typedef struct {
int value;
char *data;
} MyObjectSnapshot;
// Function to create a new MyObject
MyObject *create_my_object(int initial_value, const char *initial_data) {
MyObject *obj = (MyObject *)malloc(sizeof(MyObject));
if (obj == NULL) {
perror("Failed to allocate memory for MyObject");
exit(EXIT_FAILURE);
}
obj->value = initial_value;
obj->data = (char *)strdup(initial_data); // Duplicate the string
if (obj->data == NULL) {
perror("Failed to duplicate data string");
free(obj);
exit(EXIT_FAILURE);
}
return obj;
}
// Function to take a snapshot of the object
MyObjectSnapshot *take_snapshot(const MyObject *obj) {
MyObjectSnapshot *snapshot = (MyObjectSnapshot *)malloc(sizeof(MyObjectSnapshot));
if (snapshot == NULL) {
perror("Failed to allocate memory for MyObjectSnapshot");
exit(EXIT_FAILURE);
}
snapshot->value = obj->value;
snapshot->data = (char *)strdup(obj->data); // Duplicate the string
if (snapshot->data == NULL) {
perror("Failed to duplicate data string");
free(snapshot);
exit(EXIT_FAILURE);
}
return snapshot;
}
// Function to restore the object from a snapshot
void restore_snapshot(MyObject *obj, const MyObjectSnapshot *snapshot) {
obj->value = snapshot->value;
free(obj->data); // Free existing data
obj->data = (char *)strdup(snapshot->data); // Duplicate snapshot data
if (obj->data == NULL) {
perror("Failed to duplicate data string");
exit(EXIT_FAILURE);
}
}
// Function to free the object and its snapshot
void free_my_object(MyObject *obj) {
free(obj->data);
free(obj);
}
void free_my_object_snapshot(MyObjectSnapshot *snapshot) {
free(snapshot->data);
free(snapshot);
}
int main() {
MyObject *obj = create_my_object(10, "Initial Data");
printf("Original Object: value = %d, data = %s\n", obj->value, obj->data);
MyObjectSnapshot *snapshot = take_snapshot(obj);
obj->value = 20;
obj->data = "Modified Data";
printf("Modified Object: value = %d, data = %s\n", obj->value, obj->data);
restore_snapshot(obj, snapshot);
printf("Restored Object: value = %d, data = %s\n", obj->value, obj->data);
free_my_object_snapshot(snapshot);
free_my_object(obj);
return 0;
}
The Snapshot pattern allows capturing and restoring the internal state of an object. It’s useful for implementing features like undo/redo, transaction rollback, or saving game states. This implementation uses a separate Snapshot class to hold the state, and a Memento class to provide a controlled interface for accessing it. The Originator class creates snapshots and restores from them. This approach adheres to C++’s emphasis on encapsulation and separation of concerns, using classes to manage state and access. Copying the state within the snapshot ensures immutability, crucial for reliable restoration.
#include <iostream>
#include <vector>
#include <algorithm>
// Memento interface
class Memento {
public:
virtual ~Memento() = default;
virtual std::vector<int> getState() const = 0;
};
// Concrete Memento (Snapshot)
class Snapshot : public Memento {
private:
std::vector<int> state_;
public:
Snapshot(const std::vector<int>& state) : state_(state) {}
std::vector<int> getState() const override { return state_; }
};
// Originator
class Originator {
private:
std::vector<int> data_;
public:
void setData(const std::vector<int>& data) { data_ = data; }
std::vector<int> getData() const { return data_; }
Memento createSnapshot() const {
return Snapshot(data_);
}
void restoreFromSnapshot(const Memento& memento) {
data_ = memento.getState();
}
};
// Caretaker
class Caretaker {
private:
std::vector<Memento*> snapshots_;
public:
void addSnapshot(Memento* snapshot) {
snapshots_.push_back(snapshot);
}
Memento* getSnapshot(int index) const {
if (index >= 0 && index < snapshots_.size()) {
return snapshots_[index];
}
return nullptr;
}
};
int main() {
Originator originator;
Caretaker caretaker;
originator.setData({1, 2, 3});
std::cout << "Original state: ";
for (int val : originator.getData()) {
std::cout << val << " ";
}
std::cout << std::endl;
caretaker.addSnapshot(originator.createSnapshot());
originator.setData({4, 5, 6});
std::cout << "Modified state: ";
for (int val : originator.getData()) {
std::cout << val << " ";
}
std::cout << std::endl;
Memento* snapshot = caretaker.getSnapshot(0);
if (snapshot) {
originator.restoreFromSnapshot(*snapshot);
std::cout << "Restored state: ";
for (int val : originator.getData()) {
std::cout << val << " ";
}
std::cout << std::endl;
}
return 0;
}
The Snapshot pattern captures the internal state of an object and allows restoring it to that state later. This is useful for implementing undo/redo functionality, save/restore features, or creating checkpoints in a complex process. The code defines a Snapshot class to hold the state and a Document class that can create and revert to snapshots. It utilizes immutable data for the state to ensure consistency. This implementation is idiomatic C# due to its use of classes, properties, and the clear separation of concerns between the origin object and its snapshots. The use of a list to store snapshots is a common and efficient approach in C#.
// Snapshot.cs
public class Snapshot
{
public string Text { get; }
public int CursorPosition { get; }
public Snapshot(string text, int cursorPosition)
{
Text = text;
CursorPosition = cursorPosition;
}
}
// Document.cs
public class Document
{
private string text = "";
private int cursorPosition = 0;
private List<Snapshot> snapshots = new List<Snapshot>();
public string Text
{
get { return text; }
set { text = value; }
}
public int CursorPosition
{
get { return cursorPosition; }
set { cursorPosition = value; }
}
public void Type(char c)
{
text += c;
cursorPosition++;
}
public void Delete()
{
if (cursorPosition > 0)
{
text = text.Remove(cursorPosition - 1, 1);
cursorPosition--;
}
}
public Snapshot CreateSnapshot()
{
snapshots.Add(new Snapshot(text, cursorPosition));
return new Snapshot(text, cursorPosition);
}
public void RevertToSnapshot(Snapshot snapshot)
{
text = snapshot.Text;
cursorPosition = snapshot.CursorPosition;
}
public void PrintDocument()
{
Console.WriteLine($"Text: {text}, Cursor: {cursorPosition}");
}
}
// Example Usage (can be in a separate Program.cs)
public class Example
{
public static void Main(string[] args)
{
Document doc = new Document();
doc.Type('H');
doc.Type('e');
doc.Type('l');
doc.PrintDocument(); // Output: Text: Hel, Cursor: 3
Snapshot snapshot1 = doc.CreateSnapshot();
doc.Type('l');
doc.Type('o');
doc.PrintDocument(); // Output: Text: Hello, Cursor: 5
doc.RevertToSnapshot(snapshot1);
doc.PrintDocument(); // Output: Text: Hel, Cursor: 3
}
}
The Snapshot pattern captures and stores the complete state of an object at a specific point in time, allowing for rollback or comparison to previous states. This implementation uses a simple class to hold the object’s data and a separate class to manage snapshots. Each snapshot stores a deep copy of the object’s state. TypeScript’s strong typing and class structure lend themselves well to this pattern, ensuring type safety during state restoration. The use of a dedicated SnapshotManager promotes separation of concerns and provides a clear interface for snapshot operations. Deep copying is achieved using JSON.parse(JSON.stringify()) for simplicity, though more performant alternatives exist for complex objects.
class DataObject {
private data: { name: string; value: number };
constructor(name: string, value: number) {
this.data = { name, value };
}
getData() {
return this.data;
}
setData(name: string, value: number) {
this.data = { name, value };
}
}
class Snapshot<T> {
private state: T;
constructor(state: T) {
this.state = state;
}
getState(): T {
return this.state;
}
}
class SnapshotManager<T> {
private snapshots: Snapshot<T>[] = [];
private currentObject: T;
constructor(object: T) {
this.currentObject = object;
this.takeSnapshot(); // Initial snapshot
}
takeSnapshot(): void {
const state = JSON.parse(JSON.stringify(this.currentObject)); // Deep copy
this.snapshots.push(new Snapshot(state));
}
rollback(): void {
if (this.snapshots.length > 1) {
this.snapshots.pop(); // Remove the latest snapshot
const previousState = this.snapshots[this.snapshots.length - 1].getState();
Object.assign(this.currentObject, previousState); // Restore state
} else {
console.warn("No previous snapshot to rollback to.");
}
}
}
// Example Usage:
const dataObject = new DataObject("Initial Name", 10);
const snapshotManager = new SnapshotManager(dataObject);
console.log("Initial State:", dataObject.getData()); // { name: 'Initial Name', value: 10 }
dataObject.setData("Updated Name", 20);
console.log("Updated State:", dataObject.getData()); // { name: 'Updated Name', value: 20 }
snapshotManager.takeSnapshot();
dataObject.setData("Further Updated", 30);
console.log("Further Updated State:", dataObject.getData()); // { name: 'Further Updated', value: 30 }
snapshotManager.rollback();
console.log("Rolled Back State:", dataObject.getData()); // { name: 'Updated Name', value: 20 }
snapshotManager.rollback();
console.log("Rolled Back to Initial State:", dataObject.getData()); // { name: 'Initial Name', value: 10 }
The Snapshot pattern captures and restores the state of an object at a specific point in time. This allows reverting to previous states, implementing undo/redo functionality, or creating backups. The JavaScript implementation uses a simple object cloning approach to create snapshots. Each snapshot stores a copy of the object’s properties. The Snapshot class manages the snapshots, providing methods to save the current state and restore to a specific snapshot. This approach leverages JavaScript’s object mutability and the spread syntax for concise cloning, fitting the language’s flexible nature.
class Snapshot {
constructor(obj) {
this.snapshots = [];
this.current = this.save(obj);
}
save(obj) {
const snapshot = { ...obj }; // Shallow copy using spread syntax
this.snapshots.push(snapshot);
return snapshot;
}
restore(index) {
if (index >= 0 && index < this.snapshots.length) {
this.current = this.snapshots[index];
}
}
getCurrent() {
return this.current;
}
}
// Example Usage:
const myObject = { a: 1, b: "hello", c: { d: 2 } };
const snapshotManager = new Snapshot(myObject);
// Modify the object
myObject.a = 10;
myObject.b = "world";
myObject.c.d = 20;
// Save a snapshot after modification
snapshotManager.save(myObject);
// Restore to the first snapshot (original state)
snapshotManager.restore(0);
console.log(snapshotManager.getCurrent()); // Output: { a: 1, b: "hello", c: { d: 2 } }
// Restore to the second snapshot (modified state)
snapshotManager.restore(1);
console.log(snapshotManager.getCurrent()); // Output: { a: 10, b: "world", c: { d: 20 } }
The Snapshot pattern allows capturing and restoring the internal state of an object. This is useful for implementing features like undo/redo, transaction rollback, or saving game progress. The core idea is to separate the object’s state from how it’s presented or used, enabling state serialization and deserialization.
The Python implementation uses a separate Snapshot class to hold the object’s state at a specific point in time. The Originator class manages the state and provides methods to create and revert to snapshots. This approach leverages Python’s data classes for concise state representation and its flexibility to handle object state effectively. The use of dictionaries to store state is common in Python for its adaptability.
from dataclasses import dataclass, field
from typing import Dict
@dataclass
class Snapshot:
state: Dict
class Originator:
def __init__(self, initial_state: Dict):
self._state = initial_state
def create_snapshot(self) -> Snapshot:
return Snapshot(self._state.copy())
def restore_snapshot(self, snapshot: Snapshot):
self._state = snapshot.state
def get_state(self) -> Dict:
return self._state
def set_value(self, key: str, value):
self._state[key] = value
# Example Usage
if __name__ == "__main__":
originator = Originator({"value": 10})
snapshot1 = originator.create_snapshot()
print(f"Initial state: {originator.get_state()}")
originator.set_value("value", 20)
print(f"State after modification: {originator.get_state()}")
originator.restore_snapshot(snapshot1)
print(f"State after restoring snapshot 1: {originator.get_state()}")
snapshot2 = originator.create_snapshot()
originator.set_value("value", 30)
print(f"State after another modification: {originator.get_state()}")
originator.restore_snapshot(snapshot2)
print(f"State after restoring snapshot 2: {originator.get_state()}")
The Snapshot pattern captures the internal state of an object at a certain point in time, allowing it to be restored later. This is useful for implementing features like undo/redo, transaction rollback, or saving game states. The code defines a Snapshot class holding the object’s state and an Originator class that creates snapshots and restores from them. The Caretaker simply stores snapshots without knowing the Originator’s internals. This implementation uses simple getters and setters for state, making it straightforward and readable, fitting Java’s object-oriented style. Immutability of the Snapshot class is also a good practice.
import java.util.ArrayList;
import java.util.List;
// Originator
class TextEditor {
private String text;
public TextEditor(String initialText) {
this.text = initialText;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Snapshot createSnapshot() {
return new Snapshot(this.text);
}
public void restore(Snapshot snapshot) {
this.text = snapshot.getText();
}
}
// Snapshot
class Snapshot {
private final String text;
public Snapshot(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
// Caretaker
class History {
private List<Snapshot> snapshots = new ArrayList<>();
public void addSnapshot(Snapshot snapshot) {
snapshots.add(snapshot);
}
public Snapshot getSnapshot(int index) {
if (index >= 0 && index < snapshots.size()) {
return snapshots.get(index);
}
return null; // Or throw an exception
}
}
// Example Usage
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor("Initial text.");
History history = new History();
history.addSnapshot(editor.createSnapshot()); // Save initial state
editor.setText("More text added.");
history.addSnapshot(editor.createSnapshot()); // Save after addition
System.out.println("Current text: " + editor.getText());
Snapshot snapshot = history.getSnapshot(0);
if (snapshot != null) {
editor.restore(snapshot);
System.out.println("Restored text: " + editor.getText());
}
}
}