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() {