23個の道は遥か
どうも色々と考え違いが出てくるなぁ。
まぁ、良いか。
- Adapter
これはいかばかりか一般的過ぎるアイデアなのではなかろうか?
既存の実装を用いて必要とするインターフェースを実装する、
というのをAdapterパターンということにしておこう。
class Banner{ public: Banner(string s); void showWithParen(){ cout<<'('<
これが例で、そのままHaskellで書くと、
class Print p where printWeek :: p -> IO () printStrong :: p -> IO () data Banner = Banner String showWithParen (Banner s) = putStrLn $ "("++s++")" showWithAster (Banner s) = putStrLn $ "*"++s++"*" data PrintBanner1 = PB1 Banner instance Print PrintBanner1 where printWeek (PB1 s) = showWithParen s printStrong (PB1 s) = showWithAster s data PrintBanner2 = PB2 Banner instance Print PrintBanner2 where printWeek (PB2 s) = showWithAster s printStrong (PB2 s) = showWithParen s newBanner1 = PB1 . Banner newBanner2 = PB2 . Banner main = do let pb1 = newBanner1 "Hello" let pb2 = newBanner2 "Hello" putStrLn "with PB1" weekAndStrong pb1 putStrLn "with PB2" weekAndStrong pb2
PrintBannerを二通り実装してみた。
それぞれ逆の挙動をするものを作ってみた。
実装が複数有る場合は別だが、
「一皮かぶせて再利用」なときにはそうでないケースも多いと思うので、
そういう場合Haskellならモジュールを使って適当に実装できる。
-- メイン import PrintBanner main = do let pb = newPrintBanner "Hello" printWeek pb printStrong pb -- PrintBanner module PrintBanner( PrintBanner, newPrintBanner, printWeek, printStrong, ) where import Banner type PrintBanner = Banner newPrintBanner = newBanner printWeek = showWithParen printSTrong = showWithAster -- Banner (略)
まぁ、なんとでもなりますわなぁ。
メインがBannerを一切参照していないというのがミソなのだろう。
さらに、もっと違った形での一般化としても実装できる。
参照する領域を限定することが出来るのであれば、
単純に関数を渡すだけでも。
foo printWeek printStrong = ... main = foo showWithAster showWithParen
やはりまぁ、なんとでもなりますわなぁ。
- Template Method
まず、例から。
class AbstractDisplay{ public: void open()=0; void close()=0; void print()=0; void display(){ open(); for (i=0;i<5;i++) print(); close(); } };
display()を呼ぶとopen()一回、print()五回、close()一回を順に呼び出すようなクラス。
それぞれの挙動はサブクラスで実装。
class CharDisplay : public{ public: CharDisplay(char c); void open() { cout<<'('; } void close() { cout<<')'<
こんなのとか。
いつものように、ひとまずこれを素直にHaskellで実装してみる。
class AbstractDisplay a where open :: a -> IO () print :: a -> IO () close :: a -> IO () display :: a -> IO () display a = do open a replicateM_ 5 $ print a close a data CharDisplay = CharDisplay Char instance AbstractDisplay CharDisplay where open _ = putStr "<<" print (CharDisplay c) = putChar c close _ = putStrLn ">>" data StringDisplay = StringDisplay String instance AbstractDisplay StringDisplay where open = printLine print (StringDisplay s) = putStrLn $ "|"++s++"|" close = printLine printLine (StringDisplay s) = do putChar '+' replicateM_ (length s) $ putChar '-' putStrLn "+" main = do display (CharDisplay 'H') display (StringDisplay "Hello, World.")
上では(めんどくさかったので)書いてないStringDisplayも実装。
今回はとてもすんなり書けた。
しかし、Haskellならばわざわざクラスを使わんでも、
display o p c d = do o d replicateM_ 5 $ p d c d charDisplay = display open print close where open _ = putStr "<<" print = putChar close _ = putStrLn ">>" stringDisplay = display open print close where open = printLine print s = putStrLn $ "|"++s++"|" close = printLine printLine s = do putChar '+' replicateM_ (length s) $ putChar '-' putStrLn "+" main = do charDisplay 'H' stringDisplay "Hello, World."
適当にこれでもいいだろう。
こういうのは何というか、templateメソッドというか、
単なる共通部分の抽出だし、普通の関数型プログラマなら
(そういうことを行うのがいたって自然なので)常に行っていることではなかろうか。
- Factory Method
Template Methodのファクトリー版。
C++版みたいなのを書くのがめんどくさい…
ので、Haskellだけでご勘弁。
class Product p where use :: p -> IO () getOwner :: p -> String class Factory f where createProduct :: Product p => f p -> String -> IO p registerProduct :: Product p => f p -> p -> f p create :: Product p => f p -> String -> IO (p,f p) create f owner = do p <- createProduct f owner let g = registerProduct f p return (p,g) data IDCard = IDCard String instance Product IDCard where use (IDCard s) = putStrLn $ "use "++s++"'s card." getOwner (IDCard s) = s newIDCard s = do putStrLn $ "make "++s++"'s card." return $ IDCard s data IDCardFactory a = IDCF [String] (String -> IO a) instance Factory IDCardFactory where createProduct (IDCF _ c) s = c s registerProduct (IDCF ow c) p = IDCF (getOwner p:ow) c newIDCardFactory = IDCF [] newIDCard main = test newIDCardFactory test :: (Factory f,Product p) => f p -> IO () test f = do (p1,f) <- create f "hoge" (p2,f) <- create f "huga" (p3,f) <- create f "fugo" use p1 use p2 use p3
testのようなものを記述できるというのが
FactoryMethodの本質なのだろうが、
これも普通に関数で記述できるはずである。
まぁ、でも、こういうのを書いててちょっと面白いような気もする。