1
1
mirror of https://github.com/thma/LtuPatternFactory.git synced 2025-01-05 19:04:46 +03:00

completing template method section

This commit is contained in:
thma 2018-10-19 22:44:09 +02:00
parent 33efee5263
commit 86270ea2dd
3 changed files with 121 additions and 81 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ cabal.project.local~
.ghc.environment.*
.idea
*.iml
cabal.config

200
README.md
View File

@ -503,91 +503,11 @@ TBD: Traversable Demo
### Alternative approaches
http://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/
## Template Method -> Typeclass default functions
> In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses.
> It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
The TemplateMethod pattern is quite similar to the [StrategyPattern](#strategy---functor). The main difference is the level of granularity.
In Strategy a complete block of functionality - the Strategy - can be replaced.
In TemplateMethod the overall layout of an algorithm is predefined and only specific parts of it may be replaced.
In functional programming the answer to this kind of problem is again the usage of higher order functions.
In the following example we come back to the example for the [Adapter](#adapter---function-composition).
The function `addMinutesAdapter` lays out a structure for interfacing to some kind of backend:
1. marshalling the arguments into the backend format
2. apply the backend logic to the marshalled arguments
3. unmarshal the backend result data into the frontend format
```haskell
addMinutesAdapter :: Int -> Minute -> Minute
addMinutesAdapter x = unmarshalWM . addMinutesToWallTime x . marshalMW
```
In this code the backend functionality - `addMinutesToWallTime` - is a hardcoded part of the overall structure.
Let's assume we want to use different kind of backend implementations - for instance a mock replacement.
In this case we would like to keep the overall structure - the template - and would just make a specific part of it flexible.
This sounds like an ideal candidate for the TemplateMethod pattern:
```haskell
addMinutesTemplate :: (Int -> WallTime -> WallTime) -> Int -> Minute -> Minute
addMinutesTemplate f x =
unmarshalWM .
f x .
marshalMW
```
`addMinutesTemplate` has an additional parameter f of type `(Int -> WallTime -> WallTime)`. This parameter may be bound to `addMinutesToWallTime` or alternative implementations:
```haskell
-- implements linear addition (the normal case) even for values > 1440
linearTimeAdd :: Int -> Minute -> Minute
linearTimeAdd = addMinutesTemplate addMinutesToWallTime
-- implements cyclic addition, respecting a 24 hour (1440 Min) cycle
cyclicTimeAdd :: Int -> Minute -> Minute
cyclicTimeAdd = addMinutesTemplate addMinutesToWallTime'
```
where `addMinutesToWallTime'` implements a silly 24 hour cyclic addition:
```haskell
-- a 24 hour (1440 min) cyclic version of addition: 1400 + 100 = 60
addMinutesToWallTime' :: Int -> WallTime -> WallTime
addMinutesToWallTime' x (WallTime (h, m)) =
let (hAdd, mAdd) = x `quotRem` 60
hNew = h + hAdd
mNew = m + mAdd
in if mNew >= 60
then WallTime ((hNew + 1) `rem` 24, mNew-60)
else WallTime (hNew, mNew)
```
And here is how we use it to do actual computations:
```haskell
templateMethodDemo = do
putStrLn $ "linear time: " ++ (show $ linearTimeAdd 100 (Minute 1400))
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
```
### Typeclass minimal implementations as template method
> The template method is used in frameworks, where each implements the invariant parts of a domain's architecture,
> leaving "placeholders" for customization options. This is an example of inversion of control.
> The template method is used for the following reasons:
>
> - Let subclasses implement varying behavior (through method overriding).
> - Avoid duplication in the code: the general workflow structure is implemented once in the abstract class's algorithm,
> and necessary variations are implemented in the subclasses.
> - Control at what point(s) subclassing is allowed. As opposed to a simple polymorphic override, where the base method
> would be entirely rewritten allowing radical change to the workflow, only the specific details of the workflow are
> allowed to change.
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
# Beyond Typeclass patterns
TBD:
- Chain of Responsibility: ADT + fumction pattern matching the ADT (at least the distpatch variant)
- Chain of Responsibility: ADT + pattern matching the ADT (at least the distpatch variant)
- Currying / Partial application
@ -697,6 +617,124 @@ adapterDemo = do
putStrLn ""
```
## Template Method -> Typeclass default functions
> In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses.
> It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
The TemplateMethod pattern is quite similar to the [StrategyPattern](#strategy---functor). The main difference is the level of granularity.
In Strategy a complete block of functionality - the Strategy - can be replaced.
In TemplateMethod the overall layout of an algorithm is predefined and only specific parts of it may be replaced.
In functional programming the answer to this kind of problem is again the usage of higher order functions.
In the following example we come back to the example for the [Adapter](#adapter---function-composition).
The function `addMinutesAdapter` lays out a structure for interfacing to some kind of backend:
1. marshalling the arguments into the backend format
2. apply the backend logic to the marshalled arguments
3. unmarshal the backend result data into the frontend format
```haskell
addMinutesAdapter :: Int -> Minute -> Minute
addMinutesAdapter x = unmarshalWM . addMinutesToWallTime x . marshalMW
```
In this code the backend functionality - `addMinutesToWallTime` - is a hardcoded part of the overall structure.
Let's assume we want to use different kind of backend implementations - for instance a mock replacement.
In this case we would like to keep the overall structure - the template - and would just make a specific part of it flexible.
This sounds like an ideal candidate for the TemplateMethod pattern:
```haskell
addMinutesTemplate :: (Int -> WallTime -> WallTime) -> Int -> Minute -> Minute
addMinutesTemplate f x =
unmarshalWM .
f x .
marshalMW
```
`addMinutesTemplate` has an additional parameter f of type `(Int -> WallTime -> WallTime)`. This parameter may be bound to `addMinutesToWallTime` or alternative implementations:
```haskell
-- implements linear addition (the normal case) even for values > 1440
linearTimeAdd :: Int -> Minute -> Minute
linearTimeAdd = addMinutesTemplate addMinutesToWallTime
-- implements cyclic addition, respecting a 24 hour (1440 Min) cycle
cyclicTimeAdd :: Int -> Minute -> Minute
cyclicTimeAdd = addMinutesTemplate addMinutesToWallTime'
```
where `addMinutesToWallTime'` implements a silly 24 hour cyclic addition:
```haskell
-- a 24 hour (1440 min) cyclic version of addition: 1400 + 100 = 60
addMinutesToWallTime' :: Int -> WallTime -> WallTime
addMinutesToWallTime' x (WallTime (h, m)) =
let (hAdd, mAdd) = x `quotRem` 60
hNew = h + hAdd
mNew = m + mAdd
in if mNew >= 60
then WallTime ((hNew + 1) `rem` 24, mNew-60)
else WallTime (hNew, mNew)
```
And here is how we use it to do actual computations:
```haskell
templateMethodDemo = do
putStrLn $ "linear time: " ++ (show $ linearTimeAdd 100 (Minute 1400))
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
```
### Typeclass minimal implementations as template method
> The template method is used in frameworks, where each implements the invariant parts of a domain's architecture,
> leaving "placeholders" for customization options. This is an example of inversion of control.
> [Quoted from Wikipedia](https://en.wikipedia.org/wiki/Template_method_pattern)
The Typeclasses in Haskells base library apply this template approach frequently to reduce the effort for implementing typeclass instances and to provide a predefined structure with specific 'customization options'.
As an example let's extend the type `WallTime` by an associative binary operation `addWallTimes` to form an instance of the `Monoid` typeclass
```haskell
addWallTimes :: WallTime -> WallTime -> WallTime
addWallTimes a@(WallTime (h,m)) b =
let aMin = h*60 + m
in addMinutesToWallTime aMin b
instance Semigroup WallTime where
(<>) = addWallTimes
instance Monoid WallTime where
mempty = WallTime (0,0)
```
Even though we specified only `mempty` and `(<>)` we can now use the functions `mappend :: Monoid a => a -> a -> a` and `mconcat :: Monoid a => [a] -> a` on WallTime instances:
```haskell
templateMethodDemo = do
let a = WallTime (3,20)
print $ mappend a a
print $ mconcat [a,a,a,a,a,a,a,a,a]
```
By looking at the definition of the `Monoid` typeclass we can see how this 'magic' is made possible:
```haskell
class Semigroup a => Monoid a where
-- | Identity of 'mappend'
mempty :: a
-- | An associative operation
mappend :: a -> a -> a
mappend = (<>)
-- | Fold a list using the monoid.
mconcat :: [a] -> a
mconcat = foldr mappend mempty
```
For `mempty` only a type requirement but no definition is given.
But for `mappend` and `mconcat` default implementations are provided.
So the Monoid typeclass definition forms a *template* where the default implementations define the 'invariant parts' of the typeclass and the part specified by us form the 'customization options'.
(please note that it's generally possible to override the default implementations)
## NullObject -> Maybe Monoid
- also explain how to avoid "staircase of death" with Maybe
http://blog.ploeh.dk/2018/04/23/null-object-as-identity/

View File

@ -42,5 +42,6 @@ templateMethodDemo = do
putStrLn $ "cyclic time: " ++ (show $ cyclicTimeAdd 100 (Minute 1400))
putStrLn ""
let a = WallTime (3,20)
print $ mappend a a
print $ mconcat [a,a,a,a,a,a,a,a,a]
putStrLn ""