| ////////////////////////////////////////// |
| |
| 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. |
| |
| ////////////////////////////////////////// |
| |
| = Adapter Pattern |
| |
| The http://en.wikipedia.org/wiki/Adapter_pattern[Adapter Pattern] (sometimes called the wrapper pattern) allows objects satisfying one interface to be used where another type of interface is expected. There are two typical flavours of the pattern: the __delegation__ flavour and the __inheritance__ flavour. |
| |
| == Delegation Example |
| |
| Suppose we have the following classes: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_delegation_classes,indent=0] |
| ---- |
| |
| We can ask the `RoundHole` class if a `RoundPeg` fits in it, but if we ask the same question for a `SquarePeg`, then it will fail because the `SquarePeg` class doesn't have a `radius` property (i.e. doesn't satisfy the required interface). |
| |
| To get around this problem, we can create an adapter to make it appear to have the correct interface. It would look like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_delegation_code,indent=0] |
| ---- |
| |
| We can use the adapter like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_delegation_usage,indent=0] |
| ---- |
| |
| Which results in the following output: |
| |
| ---- |
| peg SquarePegAdapter with peg width 4 (and notional radius 2.8284271247461903) fits in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with peg width 5 (and notional radius 3.5355339059327378) fits in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with peg width 6 (and notional radius 4.242640687119285) does not fit in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with peg width 7 (and notional radius 4.949747468305833) does not fit in hole RoundHole with radius 4.0 |
| ---- |
| |
| == Inheritance Example |
| |
| Let's consider the same example again using inheritance. First, here are the original classes (unchanged): |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_inheritance_classes,indent=0] |
| ---- |
| |
| An adapter using inheritance: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_inheritance_code,indent=0] |
| ---- |
| |
| Using the adapter: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_inheritance_usage,indent=0] |
| ---- |
| |
| The output: |
| |
| ---- |
| peg SquarePegAdapter with width 4 (and notional radius 2.8284271247461903) fits in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with width 5 (and notional radius 3.5355339059327378) fits in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with width 6 (and notional radius 4.242640687119285) does not fit in hole RoundHole with radius 4.0 |
| peg SquarePegAdapter with width 7 (and notional radius 4.949747468305833) does not fit in hole RoundHole with radius 4.0 |
| ---- |
| |
| == Adapting using Closures |
| |
| As a variation of the previous examples, we could instead define the following interface: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_closure_interface,indent=0] |
| ---- |
| |
| We can then define an adapter as a closure as follows: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_closure_define,indent=0] |
| ---- |
| |
| And use it like this: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_closure_usage,indent=0] |
| ---- |
| |
| == Adapting using the ExpandoMetaClass |
| |
| As of Groovy 1.1, there is a built-in MetaClass which can automatically add properties and methods dynamically. |
| |
| Here is how the example would work using that feature: |
| |
| [source,groovy] |
| ---- |
| include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=adapter_expando_meta_class,indent=0] |
| ---- |
| |
| After you create a peg object, you can simply add a property to it on the fly. No need to change the original class and no need for an adapter class. |