Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
## 0.14.0
- Add "Memento" conceptual pattern

## 0.13.0
- Add "Observer" pattern from official book, rewritten from Java example

## 0.12.19
- Refactoring: reformatting and minor changes

## 0.12.5
- Put "Shapes" prototype pattern to "Shapes" folder.
- The list of patterns has been updated. Added links to projects.
- Put "Shapes" prototype pattern to "Shapes" folder
- The list of patterns has been updated. Added links to projects

## 0.12.0
- Add "Command" pattern from official book, rewritten from Java example.
- Add "Command" pattern from official book, rewritten from Java example

## 0.11.0
- Add "Chain of Responsibility" pattern from official book, rewritten from Java example
Expand All @@ -32,7 +38,7 @@
- Add bridge pattern. Device remote control

## 0.5.5
- Add example "graphics engine" and "square round conflict" for adapter pattern
- Add example "graphics engine" and "square round conflict" for adapter patter
- Add description to prototype pattern
- Fix class diagram for text graph
- Add description Builder pattern, text formats
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ It contains **Dart** examples for all classic **GoF** design patterns.
- [ ] Interpreter
- [ ] [**Iterator**](https://refactoring.guru/design-patterns/iterator)
- [ ] [**Mediator**](https://refactoring.guru/design-patterns/mediator)
- [ ] [**Memento**](https://refactoring.guru/design-patterns/memento)
- [ ] [**Observer**](https://refactoring.guru/design-patterns/observer)
- [x] [**Memento**](https://refactoring.guru/design-patterns/memento) [[Conceptual](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/memento/conceptual)]
- [x] [**Observer**](https://refactoring.guru/design-patterns/observer) - [[Open-Close Editor Events](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/open_close_editor_events)]
- [ ] [**State**](https://refactoring.guru/design-patterns/state)
- [ ] [**Template Method**](https://refactoring.guru/design-patterns/template-method)
- [ ] [**Visitor**](https://refactoring.guru/design-patterns/visitor)
Expand Down
39 changes: 39 additions & 0 deletions patterns/memento/conceptual/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Memento pattern
Memento is a behavioral design pattern that lets you save and restore the previous state of an
object without revealing the details of its implementation.

Tutorial: [here](https://refactoring.guru/design-patterns/memento).

## Conceptual Editor example
This example uses the Memento pattern alongside the Command pattern for storing snapshots of the
complex text editor’s state and restoring an earlier state from these snapshots when needed.

More detailed explanation on [RefactoringGuru](https://refactoring.guru/design-patterns/memento?#pseudocode).

### Diagram:
![image](https://user-images.githubusercontent.com/8049534/151352367-c97db094-fc87-4eb2-9210-581914c57ced.png)

### Client code:
```dart
void main() {
final editor = Editor('New Document');
final firstState = Command.makeBackup(editor);
editor.text += ' add text';
final secondState = Command.makeBackup(editor);

print('Current state: "${editor.text}"');

firstState.undo();
print('First state: "${editor.text}"');

secondState.undo();
print('Second state: "${editor.text}"');
}
```

**Output:**
```
Current state: "New Document add text"
First state: "New Document"
Second state: "New Document add text"
```
96 changes: 96 additions & 0 deletions patterns/memento/conceptual/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
void main() {
final editor = Editor('New Document');
final firstState = Command.makeBackup(editor);
editor.text += ' add text';
final secondState = Command.makeBackup(editor);

print('Current state: "${editor.text}"');

firstState.undo();
print('First state: "${editor.text}"');

secondState.undo();
print('Second state: "${editor.text}"');
}

/// EN: The originator holds some important data that may change over
/// time. It also defines a method for saving its state inside a
/// memento and another method for restoring the state from it.
///
/// RU: Класс создателя должен иметь специальный метод, который.
/// сохраняет состояние создателя в новом объекте-снимке.
class Editor {
var text = '';
var curX = 0;
var curY = 0;
var selectionWidth = 0;

Editor(this.text);

/// EN: Saves the current state inside a memento.
Snapshot createSnapshot() {
/// EN: Memento is an immutable object; that's why the
/// originator passes its state to the memento's
/// constructor parameters.
///
/// RU: Снимок — неизменяемый объект, поэтому Создатель
/// передаёт все своё состояние через параметры
/// конструктора.
return Snapshot(this, text, curX, curY, selectionWidth);
}
}

/// EN: The memento class stores the past state of the editor.
///
/// RU: Снимок хранит прошлое состояние редактора.
class Snapshot {
final Editor _editor;
final String _text;
final int _curX;
final int _curY;
final int _selectionWidth;

Snapshot(
this._editor,
this._text,
this._curX,
this._curY,
this._selectionWidth,
);

/// EN: At some point, a previous state of the editor can be
/// restored using a memento object.
///
/// RU: В нужный момент владелец снимка может восстановить
/// состояние редактора.
void restore() {
_editor
..text = _text
..curX = _curX
..curY = _curY
..selectionWidth = _selectionWidth;
}
}

/// EN: A command object can act as a caretaker. In that case, the
/// command gets a memento just before it changes the
/// originator's state. When undo is requested, it restores the
/// originator's state from a memento.
///
/// RU: Опекуном может выступать класс команд (см. паттерн Команда).
/// В этом случае команда сохраняет снимок состояния объекта-
/// получателя, перед тем как передать ему своё действие. А в
/// случае отмены команда вернёт объект в прежнее состояние.
class Command {
Snapshot _backup;

Command._(this._backup);

factory Command.makeBackup(Editor editor) {
return Command._(editor.createSnapshot());
}

void undo() {
_backup.restore();
}
}
44 changes: 44 additions & 0 deletions patterns/observer/open_close_editor_events/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Observer pattern
Observer is a behavioral design pattern that lets you define a subscription mechanism to notify
multiple objects about any events that happen to the object they’re observing.

## Editor example
In this example, the Observer pattern lets the text editor object notify other service objects about
changes in its state.

More detailed explanation on [RefactoringGuru](https://refactoring.guru/design-patterns/observer?#pseudocode).

### Origin source code:
This example rewrite from [java example](https://github.com/RefactoringGuru/design-patterns-java/tree/master/src/refactoring_guru/observer/example).

### Diagram:
![image](https://user-images.githubusercontent.com/8049534/150955865-7fbb29f3-ed48-4317-a356-cbb9cc79ed11.png)

### Client code:
```dart
void main() {
final editor = Editor();
editor.events
..subscribe(
'open',
LogOpenListener('log.txt'),
)
..subscribe(
'save',
EmailNotificationListener('admin@example.com'),
);

try {
editor.openFile('test.txt');
editor.saveFile();
} catch (e) {
print(e);
}
}
```

**Output:**
```
Save to log "log.txt": Someone has performed "open" operation with the following file: "test.txt"
Email to "admin@example.com": Someone has performed "save" operation with the following file: "test.txt"
```
22 changes: 22 additions & 0 deletions patterns/observer/open_close_editor_events/editor/editor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'dart:io';

import '../event_manager/event_manager.dart';

class Editor {
final events = EventManager(['open', 'save']);

File? _file;

void openFile(String filePath) {
_file = File(filePath);
events.notify("open", _file!);
}

void saveFile() {
if (_file == null) {
throw Exception('Please open a file first.');
}

events.notify('save', _file!);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'dart:io';

import '../listeners/event_listener.dart';

class EventManager {
final _listeners = <String, List<EventListener>>{};

EventManager(List<String> operations) {
for (final operation in operations) {
_listeners[operation] = [];
}
}

void subscribe(String eventType, EventListener listener) {
_usersBy(eventType).add(listener);
}

void unsubscribe(String eventType, EventListener listener) {
_usersBy(eventType).remove(listener);
}

void notify(String eventType, File file) {
final users = _usersBy(eventType);
for (final listener in users) {
listener.update(eventType, file);
}
}

List<EventListener> _usersBy(String eventType) {
final users = _listeners[eventType];

if (users == null) {
throw UnsupportedError('Event type "$eventType" do not support.');
}

return users;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'dart:io';

import 'event_listener.dart';

class EmailNotificationListener implements EventListener {
String email;

EmailNotificationListener(this.email);

@override
void update(String eventType, File file) {
print('Email to "$email": '
'Someone has performed "$eventType" '
'operation with the following file: "${file.path}"');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'dart:io';

abstract class EventListener {
void update(String eventType, File file);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'dart:io';

import 'event_listener.dart';

class LogOpenListener implements EventListener {
File logFile;

LogOpenListener(String logFileName) : logFile = File(logFileName);

@override
void update(String eventType, File file) {
print('Save to log "${logFile.path}": '
'Someone has performed "$eventType" '
'operation with the following file: "${file.path}"');
}
}
23 changes: 23 additions & 0 deletions patterns/observer/open_close_editor_events/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'editor/editor.dart';
import 'listeners/email_notification_listener.dart';
import 'listeners/log_open_listener.dart';

void main() {
final editor = Editor();
editor.events
..subscribe(
'open',
LogOpenListener('log.txt'),
)
..subscribe(
'save',
EmailNotificationListener('admin@example.com'),
);

try {
editor.openFile('test.txt');
editor.saveFile();
} catch (e) {
print(e);
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: design_patterns_dart
description: Dart examples for all classic GoF design patterns.
version: 0.12.19
version: 0.14.0
homepage: https://refactoring.guru/design-patterns
repository: https://github.com/RefactoringGuru/design-patterns-dart
issue_tracker: https://github.com/RefactoringGuru/design-patterns-dart/issue
Expand Down