You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2020/08/24 23:31:20 UTC
[groovy] 02/02: doco: flesh out alternative examples for chain of
responsibility
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 628ac088fe5fa023044d8f739f0cfdb2bd53cf8b
Author: Paul King <pa...@asert.com.au>
AuthorDate: Tue Aug 25 09:30:58 2020 +1000
doco: flesh out alternative examples for chain of responsibility
---
...ent_design-pattern-chain-of-responsibility.adoc | 46 +++++++-
src/spec/test/DesignPatternsTest.groovy | 130 ++++++++++++++++++++-
2 files changed, 170 insertions(+), 6 deletions(-)
diff --git a/src/spec/doc/fragment_design-pattern-chain-of-responsibility.adoc b/src/spec/doc/fragment_design-pattern-chain-of-responsibility.adoc
index 9cd2ca2..abf3241 100644
--- a/src/spec/doc/fragment_design-pattern-chain-of-responsibility.adoc
+++ b/src/spec/doc/fragment_design-pattern-chain-of-responsibility.adoc
@@ -23,7 +23,7 @@
In the Chain of Responsibility Pattern, objects using and implementing an interface (one or more methods) are intentionally loosely coupled. A set of objects that __implement__ the interface are organised in a list (or in rare cases a tree). Objects using the interface make requests from the first __implementor__ object. It will decide whether to perform any action itself and whether to pass the request further down the line in the list (or tree). Sometimes a default implementation for s [...]
-== Example
+== Example using traditional classes
In this example, the script sends requests to the `lister` object. The `lister` points to a `UnixLister` object. If it can't handle the request, it sends the request to the `WindowsLister`. If it can't handle the request, it sends the request to the `DefaultLister`.
@@ -62,6 +62,8 @@ WindowsLister <-[hidden]- dummy2
WindowsLister --> "forwardIfRequired" DefaultLister
....
+== Example using simplifying strategies
+
For simple cases, consider simplifying your code by not requiring the chain of classes.
Instead, use Groovy truth and the elvis operator as shown here:
@@ -70,6 +72,13 @@ Instead, use Groovy truth and the elvis operator as shown here:
include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_elvis,indent=0]
----
+Or Groovy's switch as shown here:
+
+[source,groovy]
+----
+include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_switch,indent=0]
+----
+
Alternatively, for Groovy 3+, consider using streams of lambdas as shown here:
[source,groovy]
@@ -77,10 +86,37 @@ Alternatively, for Groovy 3+, consider using streams of lambdas as shown here:
include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_lambda,indent=0]
----
+== When not to use
+
+If your use of chain of responsibility involves frequent use of the `instanceof` operator, like here:
+
+[source,groovy]
+----
+include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape,indent=0]
+----
+
+It could indicate that instead of using the chain of responsibility pattern, you might consider
+using richer types, perhaps in combination with Groovy's multimethods. For example, perhaps this:
+
+[source,groovy]
+----
+include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_multimethods,indent=0]
+----
+
+or using more traditional object-oriented style like this:
+
+[source,groovy]
+----
+include::{includedir}/../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_oo,indent=0]
+----
+
+== Going further
+
Other variations to this pattern:
-* we could have an explicit interface, e.g. `Lister`, to statically type the implementations but because of _duck-typing_ this is optional
+* we could have an explicit interface in the traditional example, e.g. `Lister`, to statically type the implementations but because of _duck-typing_ this is optional
* we could use a chain tree instead of a list, e.g. `if (animal.hasBackbone())` delegate to `VertebrateHandler` else delegate to `InvertebrateHandler`
-* we could always pass down the chain even if we processed a request
-* we could decide at some point to not respond and not pass down the chain
-* we could use Groovy’s meta-programming capabilities to pass unknown methods down the chain
\ No newline at end of file
+* we could always pass down the chain even if we processed a request (no early return)
+* we could decide at some point to not respond and not pass down the chain (pre-emptive abort)
+* we could use Groovy’s meta-programming capabilities to pass unknown methods down the chain, e.g. combine
+chain of responsibility with the use of `methodMissing`
\ No newline at end of file
diff --git a/src/spec/test/DesignPatternsTest.groovy b/src/spec/test/DesignPatternsTest.groovy
index 1e57023..10004e9 100644
--- a/src/spec/test/DesignPatternsTest.groovy
+++ b/src/spec/test/DesignPatternsTest.groovy
@@ -385,6 +385,22 @@ class DesignPatternsTest extends CompilableTestSupport {
// end::chain_of_responsibility_elvis[]
'''
shouldCompile '''
+ // tag::chain_of_responsibility_switch[]
+ String listFiles(dir) {
+ switch(dir) {
+ case { System.getProperty('os.name') == 'Linux' }:
+ return "ls $dir".execute().text
+ case { System.getProperty('os.name').startsWith('Windows') }:
+ return "cmd.exe /c dir $dir".execute().text
+ default:
+ new File(dir).listFiles().collect{ f -> f.name }.join('\\n')
+ }
+ }
+
+ println listFiles('Downloads')
+ // end::chain_of_responsibility_switch[]
+ '''
+ shouldCompile '''
// tag::chain_of_responsibility_lambda[]
Optional<String> unixListFiles(String dir) {
Optional.ofNullable(dir)
@@ -403,7 +419,7 @@ class DesignPatternsTest extends CompilableTestSupport {
.map(d -> new File(d).listFiles().collect{ f -> f.name }.join('\\n'))
}
- def dir = 'subprojects\'
+ def dir = 'Downloads'
def handlers = [this::unixListFiles, this::windowsListFiles, this::defaultListFiles]
println handlers.stream()
.map(f -> f(dir))
@@ -413,6 +429,118 @@ class DesignPatternsTest extends CompilableTestSupport {
.get()
// end::chain_of_responsibility_lambda[]
'''
+ shouldCompile '''
+ // tag::chain_of_responsibility_shape[]
+ import static Math.PI as π
+ abstract class Shape {
+ String name
+ }
+ class Polygon extends Shape {
+ String name
+ double lengthSide
+ int numSides
+ }
+ class Circle extends Shape {
+ double radius
+ }
+
+ class CircleAreaCalculator {
+ def next
+ def area(shape) {
+ if (shape instanceof Circle) {
+ return shape.radius ** 2 * π
+ } else {
+ next.area(shape)
+ }
+ }
+ }
+ class SquareAreaCalculator {
+ def next
+ def area(shape) {
+ if (shape instanceof Polygon && shape.numSides == 4) {
+ return shape.lengthSide ** 2
+ } else {
+ next.area(shape)
+ }
+ }
+ }
+ class DefaultAreaCalculator {
+ def area(shape) {
+ throw new IllegalArgumentException("Don't know how to calculate area for $shape")
+ }
+ }
+
+ def chain = new CircleAreaCalculator(next: new SquareAreaCalculator(next: new DefaultAreaCalculator()))
+ def shapes = [
+ new Circle(name: 'Circle', radius: 5.0),
+ new Polygon(name: 'Square', lengthSide: 10.0, numSides: 4)
+ ]
+ shapes.each { println chain.area(it) }
+ // end::chain_of_responsibility_shape[]
+ '''
+ shouldCompile '''
+ import static Math.PI as π
+ abstract class Shape {
+ }
+ class Polygon extends Shape {
+ double lengthSide
+ int numSides
+ }
+ class Circle extends Shape {
+ double radius
+ }
+ // tag::chain_of_responsibility_shape_multimethods[]
+ // ...
+ class Square extends Polygon {
+ // ...
+ }
+
+ double area(Circle c) {
+ c.radius ** 2 * π
+ }
+
+ double area(Square s) {
+ s.lengthSide ** 2
+ }
+
+ def shapes = [
+ new Circle(radius: 5.0),
+ new Square(lengthSide: 10.0, numSides: 4)
+ ]
+ shapes.each { println area(it) }
+ // end::chain_of_responsibility_shape_multimethods[]
+ '''
+ shouldCompile '''
+ // tag::chain_of_responsibility_shape_oo[]
+ import static Math.PI as π
+ interface Shape {
+ double area()
+ }
+ abstract class Polygon implements Shape {
+ double lengthSide
+ int numSides
+ abstract double area()
+ }
+ class Circle implements Shape {
+ double radius
+ double area() {
+ radius ** 2 * π
+ }
+ }
+ class Square extends Polygon {
+ // ...
+ double area() {
+ lengthSide ** 2
+ }
+ }
+
+ def shapes = [
+ new Circle(radius: 5.0),
+ new Square(lengthSide: 10.0, numSides: 4)
+ ]
+ shapes.each { println it.area() }
+ // end::chain_of_responsibility_shape_oo[]
+ '''
}
void testCompositeCode() {