From f7886f062e381c257b10d6464bb5a3332ce875b4 Mon Sep 17 00:00:00 2001 From: ilopX Date: Tue, 25 Jan 2022 14:07:04 +0200 Subject: [PATCH 1/5] Add "Observer" pattern. Official example from java. --- .../open_close_editor_events/README.md | 44 +++++++++++++++++++ .../editor/editor.dart | 22 ++++++++++ .../event_manager/event_manager.dart | 38 ++++++++++++++++ .../email_notification_listener.dart | 16 +++++++ .../listeners/event_listener.dart | 5 +++ .../listeners/log_open_listener.dart | 16 +++++++ .../open_close_editor_events/main.dart | 23 ++++++++++ 7 files changed, 164 insertions(+) create mode 100644 patterns/observer/open_close_editor_events/README.md create mode 100644 patterns/observer/open_close_editor_events/editor/editor.dart create mode 100644 patterns/observer/open_close_editor_events/event_manager/event_manager.dart create mode 100644 patterns/observer/open_close_editor_events/listeners/email_notification_listener.dart create mode 100644 patterns/observer/open_close_editor_events/listeners/event_listener.dart create mode 100644 patterns/observer/open_close_editor_events/listeners/log_open_listener.dart create mode 100644 patterns/observer/open_close_editor_events/main.dart diff --git a/patterns/observer/open_close_editor_events/README.md b/patterns/observer/open_close_editor_events/README.md new file mode 100644 index 0000000..fa69f69 --- /dev/null +++ b/patterns/observer/open_close_editor_events/README.md @@ -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" +``` diff --git a/patterns/observer/open_close_editor_events/editor/editor.dart b/patterns/observer/open_close_editor_events/editor/editor.dart new file mode 100644 index 0000000..3411655 --- /dev/null +++ b/patterns/observer/open_close_editor_events/editor/editor.dart @@ -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!); + } +} diff --git a/patterns/observer/open_close_editor_events/event_manager/event_manager.dart b/patterns/observer/open_close_editor_events/event_manager/event_manager.dart new file mode 100644 index 0000000..50d2371 --- /dev/null +++ b/patterns/observer/open_close_editor_events/event_manager/event_manager.dart @@ -0,0 +1,38 @@ +import 'dart:io'; + +import '../listeners/event_listener.dart'; + +class EventManager { + final _listeners = >{}; + + EventManager(List 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 _usersBy(String eventType) { + final users = _listeners[eventType]; + + if (users == null) { + throw UnsupportedError('Event type "$eventType" do not support.'); + } + + return users; + } +} diff --git a/patterns/observer/open_close_editor_events/listeners/email_notification_listener.dart b/patterns/observer/open_close_editor_events/listeners/email_notification_listener.dart new file mode 100644 index 0000000..0936e4f --- /dev/null +++ b/patterns/observer/open_close_editor_events/listeners/email_notification_listener.dart @@ -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}"'); + } +} diff --git a/patterns/observer/open_close_editor_events/listeners/event_listener.dart b/patterns/observer/open_close_editor_events/listeners/event_listener.dart new file mode 100644 index 0000000..252d64a --- /dev/null +++ b/patterns/observer/open_close_editor_events/listeners/event_listener.dart @@ -0,0 +1,5 @@ +import 'dart:io'; + +abstract class EventListener { + void update(String eventType, File file); +} diff --git a/patterns/observer/open_close_editor_events/listeners/log_open_listener.dart b/patterns/observer/open_close_editor_events/listeners/log_open_listener.dart new file mode 100644 index 0000000..e44b1ae --- /dev/null +++ b/patterns/observer/open_close_editor_events/listeners/log_open_listener.dart @@ -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}"'); + } +} diff --git a/patterns/observer/open_close_editor_events/main.dart b/patterns/observer/open_close_editor_events/main.dart new file mode 100644 index 0000000..9951d3c --- /dev/null +++ b/patterns/observer/open_close_editor_events/main.dart @@ -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); + } +} From e80a57b93a97f1cfc822c4d3a44a0f9bdf89675e Mon Sep 17 00:00:00 2001 From: ilopX Date: Tue, 25 Jan 2022 14:10:42 +0200 Subject: [PATCH 2/5] Bump version 0.13.0. --- CHANGELOG.md | 3 +++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b058cf..4ab2f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.13.0 +- Add "Observer" pattern from official book, rewritten from Java example. + ## 0.12.19 - Refactoring: reformatting and minor changes diff --git a/README.md b/README.md index 0e906c4..b28a31c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ It contains **Dart** examples for all classic **GoF** design patterns. - [ ] [**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] [**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) diff --git a/pubspec.yaml b/pubspec.yaml index 6822ac1..662aa49 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: design_patterns_dart description: Dart examples for all classic GoF design patterns. -version: 0.12.19 +version: 0.13.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 From d36abb86023cd2e70cc7ef9c170c0b95a66d9921 Mon Sep 17 00:00:00 2001 From: ilopX Date: Thu, 27 Jan 2022 12:15:05 +0200 Subject: [PATCH 3/5] Add conceptual "Memento" pattern example. --- patterns/memento/conceptual/main.dart | 96 +++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 patterns/memento/conceptual/main.dart diff --git a/patterns/memento/conceptual/main.dart b/patterns/memento/conceptual/main.dart new file mode 100644 index 0000000..4590559 --- /dev/null +++ b/patterns/memento/conceptual/main.dart @@ -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(); + } +} From 0b54d26b8d6c0615b1265d781fd332600732cf41 Mon Sep 17 00:00:00 2001 From: ilopX Date: Thu, 27 Jan 2022 13:45:40 +0200 Subject: [PATCH 4/5] Add README to "Memento" conceptual example. --- patterns/memento/conceptual/README.md | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 patterns/memento/conceptual/README.md diff --git a/patterns/memento/conceptual/README.md b/patterns/memento/conceptual/README.md new file mode 100644 index 0000000..45811db --- /dev/null +++ b/patterns/memento/conceptual/README.md @@ -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" +``` From 6e22bca748726dfb62c174505dd79a73f0e12a72 Mon Sep 17 00:00:00 2001 From: ilopX Date: Thu, 27 Jan 2022 13:49:17 +0200 Subject: [PATCH 5/5] Bump version 0.14.0 --- CHANGELOG.md | 13 ++++++++----- README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab2f1e..203262a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,18 @@ +## 0.14.0 +- Add "Memento" conceptual pattern + ## 0.13.0 -- Add "Observer" pattern from official book, rewritten from Java example. +- 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 @@ -35,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 diff --git a/README.md b/README.md index b28a31c..8f0052c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ 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) + - [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) diff --git a/pubspec.yaml b/pubspec.yaml index 662aa49..26fbabd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: design_patterns_dart description: Dart examples for all classic GoF design patterns. -version: 0.13.0 +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