diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb36d0..16286c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.22.0 +- Add visitor pattern: "Shape Xml Export". + ## 0.21.0 - Add strategy pattern: "Reservation cargo spaces". diff --git a/README.md b/README.md index 2bcd1de..69a5674 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ It contains **Dart** examples for all classic **GoF** design patterns. - [x] **Observer** - [[Open-Close Editor Events](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/observer/open_close_editor_events)] [[AppObserver](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/observer/app_observer)] [[Subscriber Flutter Widget](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/observer/subscriber_flutter_widget)] - [ ] **State** - [ ] **Template Method** - - [ ] **Visitor** - - [X] **Strategy** [[Reservation cargo spaces](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/strategy/reservation_cargo_spaces)] + - [X] **Visitor** [[Shape XML Exporter](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/visitor/shapes_exporter)] + - [X] **Strategy** [[Reservation Cargo Spaces](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/strategy/reservation_cargo_spaces)] - [ ] **Structural** - [x] **Adapter** - [[Text Graphics](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/adapter/text_graphics)] [[Square Round conflict](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/adapter/square_round_conflict)] [[Flutter Adapter](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/adapter/flutter_adapter)] - [x] **Bridge** - [[Remote Device Control](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/bridge/devices_remote_control)] [[Clock](https://github.com/RefactoringGuru/design-patterns-dart/tree/main/patterns/bridge/clock)] diff --git a/patterns/visitor/shapes_exporter/README.md b/patterns/visitor/shapes_exporter/README.md new file mode 100644 index 0000000..11ebdc4 --- /dev/null +++ b/patterns/visitor/shapes_exporter/README.md @@ -0,0 +1,78 @@ +# Visitor pattern +Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which +they operate. + +Tutorial: [here](https://refactoring.guru/design-patterns/visitor). + +### About example: Shape XML Exporter. +In this example, the Visitor pattern adds XML export support to the class hierarchy of geometric +shapes [More info](https://refactoring.guru/design-patterns/visitor#pseudocode). + +This example rewrite from original source code [java example](https://github.com/RefactoringGuru/design-patterns-java/tree/main/src/refactoring_guru/visitor/example) + +### Diagram: +![image](https://user-images.githubusercontent.com/8049534/167304227-04237030-879e-4d7f-be32-4c815a3e1cbf.png) + +### Client code: +```dart + final compoundShape = CompoundShape( + x: 30, + y: 45, + children: [ + Rectangle(x: 10, y: 10, width: 100, height: 100), + Circle(xCenter: 300, yCenter: 20, radius: 35), + Dot(x: 60, y: 60), + CompoundShape( + x: 5, + y: 5, + children: [ + Dot(x: 15, y: 15), + Dot(x: 20, y: 20), + ], + ), + ], + ); + + final xml = XMLExportVisitor().export(compoundShape); + print(xml); +``` + +### Output: +```xml + + + 30 + 45 + + + 10 + 10 + 100 + 100 + + + 300 + 20 + 35 + + + 60 + 60 + + + 5 + 5 + + + 15 + 15 + + + 20 + 20 + + + + + +``` diff --git a/patterns/visitor/shapes_exporter/main.dart b/patterns/visitor/shapes_exporter/main.dart new file mode 100644 index 0000000..bf0c2df --- /dev/null +++ b/patterns/visitor/shapes_exporter/main.dart @@ -0,0 +1,28 @@ +import 'shapes/circle.dart'; +import 'shapes/compound_shape.dart'; +import 'shapes/dot.dart'; +import 'shapes/rectangle.dart'; +import 'visitor/xml_export_visitor.dart'; + +void main() { + final compoundShape = CompoundShape( + x: 30, + y: 45, + children: [ + Rectangle(x: 10, y: 10, width: 100, height: 100), + Circle(xCenter: 300, yCenter: 20, radius: 35), + Dot(x: 60, y: 60), + CompoundShape( + x: 5, + y: 5, + children: [ + Dot(x: 15, y: 15), + Dot(x: 20, y: 20), + ], + ), + ], + ); + + final xml = XMLExportVisitor().export(compoundShape); + print(xml); +} diff --git a/patterns/visitor/shapes_exporter/shapes/circle.dart b/patterns/visitor/shapes_exporter/shapes/circle.dart new file mode 100644 index 0000000..739c99e --- /dev/null +++ b/patterns/visitor/shapes_exporter/shapes/circle.dart @@ -0,0 +1,24 @@ +import '../visitor/visitor.dart'; +import 'shape.dart'; + +class Circle implements Shape { + final int xCenter; + final int yCenter; + final int radius; + + Circle({ + required this.xCenter, + required this.yCenter, + required this.radius, + }); + + @override + void accept(Visitor visitor) { + visitor.visitCircle(this); + } + + @override + void draw() { + // ... + } +} diff --git a/patterns/visitor/shapes_exporter/shapes/compound_shape.dart b/patterns/visitor/shapes_exporter/shapes/compound_shape.dart new file mode 100644 index 0000000..d35338e --- /dev/null +++ b/patterns/visitor/shapes_exporter/shapes/compound_shape.dart @@ -0,0 +1,24 @@ +import '../visitor/visitor.dart'; +import 'shape.dart'; + +class CompoundShape implements Shape { + final int x; + final int y; + final List children; + + CompoundShape({ + required this.x, + required this.y, + required this.children, + }); + + @override + void accept(Visitor visitor) { + visitor.visitCompoundShape(this); + } + + @override + void draw() { + // ... + } +} diff --git a/patterns/visitor/shapes_exporter/shapes/dot.dart b/patterns/visitor/shapes_exporter/shapes/dot.dart new file mode 100644 index 0000000..5d5811a --- /dev/null +++ b/patterns/visitor/shapes_exporter/shapes/dot.dart @@ -0,0 +1,22 @@ +import '../visitor/visitor.dart'; +import 'shape.dart'; + +class Dot implements Shape { + final int x; + final int y; + + Dot({ + required this.x, + required this.y, + }); + + @override + void accept(Visitor visitor) { + visitor.visitDot(this); + } + + @override + void draw() { + // ... + } +} diff --git a/patterns/visitor/shapes_exporter/shapes/rectangle.dart b/patterns/visitor/shapes_exporter/shapes/rectangle.dart new file mode 100644 index 0000000..bd0bb4e --- /dev/null +++ b/patterns/visitor/shapes_exporter/shapes/rectangle.dart @@ -0,0 +1,26 @@ +import '../visitor/visitor.dart'; +import 'shape.dart'; + +class Rectangle implements Shape { + final int x; + final int y; + final int width; + final int height; + + Rectangle({ + required this.x, + required this.y, + required this.width, + required this.height, + }); + + @override + void accept(Visitor visitor) { + visitor.visitRectangle(this); + } + + @override + void draw() { + // ... + } +} diff --git a/patterns/visitor/shapes_exporter/shapes/shape.dart b/patterns/visitor/shapes_exporter/shapes/shape.dart new file mode 100644 index 0000000..aecdf09 --- /dev/null +++ b/patterns/visitor/shapes_exporter/shapes/shape.dart @@ -0,0 +1,7 @@ +import '../visitor/visitor.dart'; + +abstract class Shape { + void accept(Visitor visitor); + + void draw(); +} diff --git a/patterns/visitor/shapes_exporter/visitor/visitor.dart b/patterns/visitor/shapes_exporter/visitor/visitor.dart new file mode 100644 index 0000000..3d6ba3b --- /dev/null +++ b/patterns/visitor/shapes_exporter/visitor/visitor.dart @@ -0,0 +1,14 @@ +import '../shapes/circle.dart'; +import '../shapes/compound_shape.dart'; +import '../shapes/dot.dart'; +import '../shapes/rectangle.dart'; + +abstract class Visitor { + void visitCompoundShape(CompoundShape compound); + + void visitDot(Dot dot); + + void visitCircle(Circle circle); + + void visitRectangle(Rectangle rectangle); +} diff --git a/patterns/visitor/shapes_exporter/visitor/xml_export_visitor.dart b/patterns/visitor/shapes_exporter/visitor/xml_export_visitor.dart new file mode 100644 index 0000000..be6acd3 --- /dev/null +++ b/patterns/visitor/shapes_exporter/visitor/xml_export_visitor.dart @@ -0,0 +1,73 @@ +import '../shapes/compound_shape.dart'; +import '../shapes/dot.dart'; +import '../shapes/rectangle.dart'; +import '../shapes/shape.dart'; +import 'visitor.dart'; + +class XMLExportVisitor implements Visitor { + final buff = StringBuffer(); + + String export(Shape shape) { + buff.clear(); + _write('?'); + shape.accept(this); + return buff.toString().trim(); + } + + @override + void visitCompoundShape(CompoundShape compound) { + _write('', openTag: true); + _write('${compound.x}'); + _write('${compound.y}'); + _write('', openTag: true); + + for (final shape in compound.children) { + shape.accept(this); + } + + _write('', closeTag: true); + _write('', closeTag: true); + } + + @override + void visitCircle(circle) { + _write('', openTag: true); + _write('${circle.xCenter}'); + _write('${circle.yCenter}'); + _write('${circle.radius}'); + _write('', closeTag: true); + } + + @override + void visitDot(Dot dot) { + _write('', openTag: true); + _write('${dot.x}'); + _write('${dot.y}'); + _write('', closeTag: true); + } + + @override + void visitRectangle(Rectangle rectangle) { + _write('', openTag: true); + _write('${rectangle.x}'); + _write('${rectangle.y}'); + _write('${rectangle.width}'); + _write('${rectangle.height}'); + _write('', closeTag: true); + } + + int _openTags = 0; + + void _write(String str, {openTag = false, closeTag = false}) { + if (closeTag) { + _openTags--; + } + + final tab = ' ' * _openTags; + buff.writeln(tab + str); + + if (openTag) { + _openTags++; + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 8dc319c..6ac14f2 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.21.0 +version: 0.22.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