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/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); + } +} 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