blob: 87cd677cedccc8e180a32cb119e23c03074c6f31 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Chain of Responsibility Pattern
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 some request is also coded into the pattern if none of the implementors respond to the request.
== 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`.
[source,groovy]
----
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility,indent=0]
----
The output will be a list of files (with slightly different format depending on the operating system).
Here is a UML representation:
[plantuml, ChainOfResponsibilityClasses, png]
....
!pragma layout smetana
skinparam nodesep 200
skinparam ObjectBorderColor<<Hidden>> Transparent
skinparam ObjectBackgroundColor<<Hidden>> Transparent
skinparam ObjectFontColor<<Hidden>> Transparent
skinparam ObjectStereotypeFontColor<<Hidden>> Transparent
class UnixLister {
nextInLine : Object
+listFiles(dir)
}
object dummy1<<Hidden>>
class WindowsLister {
nextInLine : Object
+listFiles(dir)
}
object dummy2<<Hidden>>
class DefaultLister {
+listFiles(dir)
}
hide DefaultLister fields
object script
script ..r..> "<<use>>" UnixLister
UnixLister --> "forwardIfRequired" WindowsLister
UnixLister <-[hidden]- dummy1
WindowsLister <-[hidden]- dummy2
WindowsLister --> "forwardIfRequired" DefaultLister
hide <<Hidden>>
....
== 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:
[source,groovy]
----
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_elvis,indent=0]
----
Or Groovy's switch as shown here:
[source,groovy]
----
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_switch,indent=0]
----
Alternatively, for Groovy 3+, consider using streams of lambdas as shown here:
[source,groovy]
----
include::../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::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape,indent=0]
----
<1> instanceof code smell
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::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_multimethods,indent=0]
----
or using more traditional object-oriented style like this:
[source,groovy]
----
include::../test/DesignPatternsTest.groovy[tags=chain_of_responsibility_shape_oo,indent=0]
----
== Going further
Other variations to this pattern:
* 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 (no early return)
* we could decide at some point to not respond and not pass down the chain (pre-emptive abort)
* we could use Groovys meta-programming capabilities to pass unknown methods down the chain, e.g. combine
chain of responsibility with the use of `methodMissing`