Shred IT!!!!

IT全般について試したこと・勉強したことを綴ったり、趣味について語るブログ

BitBucket で Git のプライベートリポジトリを 無料で作る方法

Rails で簡単なアプリを作ろうと思っているが、ソースの管理に困る。

ローカルPCでソース管理するのも嫌だし、 サーバを借りているわけでもない。

GitHub でソース管理することも考えたが、 プライベートリポジトリが欲しい...

検索していると下記サイトを発見。
www.find-job.net

比較を見ていて BitBucket が気になった。
bitbucket.org

Atlassian を利用してアジャイル開発するプロジェクトに携わっていたことがあるのだが、
アジャイル開発のタスク管理がしやすい JIRA、
情報共有に特化した高機能ウィキ Confluence、
コードレビューが捗る Crucible、 などなど使い勝手が非常に良かった。

ということで、BitBucket でプライベートリポジトリを作ってみる。

サインアップ

https://bitbucket.org へアクセス
f:id:jetglass:20150516102935p:plain

②フォームへ入力
f:id:jetglass:20150516103324p:plain

③メールが届くので認証しておく
f:id:jetglass:20150516104350p:plain

④②の続き、チュートリアル用の非公開リポジトリを選択
f:id:jetglass:20150516103456p:plain

チュートリアル用非公開リポジトリ作成完了
f:id:jetglass:20150516103518p:plain

⑥git clone して、git の設定をしておく

$ git clone https://jetglass@bitbucket.org/jetglass/tutorial.git
$ git config user.name "jetglass"

⑦ファイルを編集して、コミット&プッシュ

$ cd tutorial
$ vim sample.html

下記、行を追加。

<h2>Hello World!</h2>
$ git add sample.html
$ git commit -m "テスト"
$ git push origin master

https://bitbucket.org を開いてコミットを確認すると、追加分が確認できる。
f:id:jetglass:20150516110705p:plain

まとめ

手順の通り簡単にプライベートリポジトリを作れる。
しかも、作るリポジトリの数に制限がないのはありがたい。

チーム 5人までなら無料で使えるので、スタートアップで利用するのにいいと思う。

Capistrano からの bundle install で rmagick のインストールが失敗するときの対処方法

概要

cap コマンドからの bundle install が失敗する件。

前回記事で Capistrano 2 系で踏み台経由でデプロイって記事を書いたのだけど、 cap コマンド内で実行される bundle install が失敗してしまうので、 今回それを解決する方法を記事にする。
↓前回記事 jetglass.hatenablog.jp

これも関連記事。
jetglass.hatenablog.jp

環境は ローカルPC -> 踏み台 -> テストサーバ。
ちなみにテストサーバに ImageMagick はインストール済み。

エラーの内容と原因

$ bundle exec cap deploy

上記を実行で下記エラー。

 ** [out :: test_server] Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.
 ** [out :: test_server]
 ** [out :: test_server] /usr/local/bin/ruby extconf.rb
 ** [out :: test_server] checking for Ruby version >= 1.8.5... yes
 ** [out :: test_server] checking for gcc... yes
 ** [out :: test_server] checking for Magick-config... yes
 ** [out :: test_server]
 ** [out :: test_server] Warning: Found a partial ImageMagick installation. Your operating system likely has some built-in ImageMagick libraries but not all of ImageMagick. This will most likely cause problems at both compile and runtime.
 ** [out :: test_server] Found partial installation at: /usr/local
 ** [out :: test_server] checking for ImageMagick version >= 6.4.9... yes
 ** [out :: test_server] Package MagickCore was not found in the pkg-config search path.
 ** [out :: test_server] Perhaps you should add the directory containing `MagickCore.pc'
 ** [out :: test_server] to the PKG_CONFIG_PATH environment variable
 ** [out :: test_server] No package 'MagickCore' found
 ** [out :: test_server] Package MagickCore was not found in the pkg-config search path.
 ** [out :: test_server] Perhaps you should add the directory containing `MagickCore.pc'
 ** [out :: test_server] to the PKG_CONFIG_PATH environment variable
 ** [out :: test_server] No package 'MagickCore' found
 ** [out :: test_server] Package MagickCore was not found in the pkg-config search path.
 ** [out :: test_server] Perhaps you should add the directory containing `MagickCore.pc'
 ** [out :: test_server] to the PKG_CONFIG_PATH environment variable
 ** [out :: test_server] No package 'MagickCore' found
 ** [out :: test_server] Package MagickCore was not found in the pkg-config search path.
 ** [out :: test_server] Perhaps you should add the directory containing `MagickCore.pc'
 ** [out :: test_server] to the PKG_CONFIG_PATH environment variable
 ** [out :: test_server] No package 'MagickCore' found
 ** [out :: test_server] checking for stdint.h... yes
 ** [out :: test_server] checking for sys/types.h... yes
 ** [out :: test_server] checking for wand/MagickWand.h... no
 ** [out :: test_server]
 ** [out :: test_server] Can't install RMagick 0.0.0. Can't find MagickWand.h.
 ** [out :: test_server] *** extconf.rb failed ***
 ** [out :: test_server] Could not create Makefile due to some reason, probably lack of
 ** [out :: test_server] necessary libraries and/or headers.  Check the mkmf.log file for more
 ** [out :: test_server] details.  You may need configuration options.
 ** [out :: test_server]
 ** [out :: test_server] Provided configuration options:
 ** [out :: test_server] --with-opt-dir
 ** [out :: test_server] --without-opt-dir
 ** [out :: test_server] --with-opt-include
 ** [out :: test_server] --without-opt-include=${opt-dir}/include
 ** [out :: test_server] --with-opt-lib
 ** [out :: test_server] --without-opt-lib=${opt-dir}/lib
 ** [out :: test_server] --with-make-prog
 ** [out :: test_server] --without-make-prog
 ** [out :: test_server] --srcdir=.
 ** [out :: test_server] --curdir
 ** [out :: test_server] --ruby=/usr/local/bin/ruby
 ** [out :: test_server]
 ** [out :: test_server]
 ** [out :: test_server] Gem files will remain installed in /home/deployer/qupio_web_admin/shared/bundle/ru
 ** [out :: test_server] by/1.9.1/gems/rmagick-2.13.3 for inspection.
 ** [out :: test_server] Results logged to /home/deployer/qupio_web_admin/shared/bundle/ruby/1.9.1/gems/rmagick-2.13.3/ext/RMagick/gem_make.out
 ** [out :: test_server] An error occurred while installing rmagick (2.13.3), and Bundler cannot
 ** [out :: test_server] continue.
 ** [out :: test_server] Make sure that `gem install rmagick -v '2.13.3'` succeeds before bundling.

qiita.com

同じように苦しんだ人の記事を発見。

エラーログに出ている通り、PKG_CONFIG_PATH の環境変数の設定がおかしいようだ。

前回設定した deployer ユーザで ssh して調べる。

$ ssh test_server
$ env | grep PKG_CONFIG_PATH
PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/local/lib/pkgconfig

普通に設定されてる。
となると cap コマンド経由で環境変数が設定されていないのが問題か。

cap コマンド実行時の環境変数の調べ方、 Capistrano: Can I set an environment variable for the whole cap session? - Stack Overflow
↑を参考にコマンド実行。

$ bundle exec cap COMMAND=printenv invoke

PKG_CONFIG_PATH は設定されてない。

解決方法:環境変数を追加

deploy.rb に下記を追加。

set :default_environment, { 
  'PKG_CONFIG_PATH' => '/usr/lib/pkgconfig:/usr/local/lib/pkgconfig'
}

もう一度、環境変数を確認。

$ bundle exec cap COMMAND=printenv invoke

PKG_CONFIG_PATH が追加されていることを確認できた。

cap コマンド実行。

$ bundle exec cap deploy

今度こそ成功!

まとめ

環境変数が問題で cap に失敗する場合があるかもしれない。

cap 実行時の環境変数は下記で確認

$ bundle exec cap COMMAND=printenv invoke

cap 実行時の環境変数を追加したい場合は deploy.rb に下記を追加

set :default_environment, { 
  'HOGE_PATH' => '/hoge_path'
}

Capistrano 2 系で踏み台サーバ経由でデプロイする方法

Capistrano 2 系の記事を今さら書くのも微妙だが、作業メモ。
3 系でも同じような感じで使えるはず。

概要

Capistrano 2 系で踏み台サーバを経由してデプロイする方法。

状況は下記。
ローカルマシン -> 踏み台サーバ -> テストサーバ

jetglass.hatenablog.jp

前に書いた↑これ(~/.ssh/config)を利用すれば簡単に実現できた。

実現方法

~/.ssh/config の設定

まずは ~/.ssh/config の設定をする。
なぜ設定するかというと、
cap コマンドを叩くユーザの ~/.ssh/config を有効にできるため。

# Mac(ローカルマシン)の ~/.ssh/config
Host bastion  # ホスト名(任意)
  User  tsuyacchi  # ユーザ名
  HostName  xxx.xxx.xxx.xxx  # IP or ホスト名
  Port  2222  # ポート番号
  IdentityFile  ~/.ssh/tsuyacchi_id_rsa  # 秘密鍵ファイルパス

Host test_server  # ホスト名(任意) 
  User  deployer  # ユーザ名
  HostName  zzz.zzz.zzz.zzz  # IP or ホスト名
  IdentityFile  ~/.ssh/deployer_id_rsa  # 秘密鍵ファイルパス
  ProxyCommand  ssh bastion -W %h:%p  # SSHトンネル(bastion 経由)

前に書いた記事の通りだが、$ ssh test_serverでログインできるようにしておくこと。

deploy.rb の設定

次に Capistrano 2系での設定。

...
set :user, "deployer"
ssh_options[:forward_agent] = true # ~/.ssh/config の設定を利用してくれるようになる
...
role :web, "test_server" # ~/.ssh/config に設定した通り、踏み台経由でアクセスしてくれる
...

cap コマンド実行でエラー

$ bundle exec cap deploy
...
connection failed for: test_server (Net::SSH::AuthenticationFailed: Authentication failed for user deployer@zzz.zzz.zzz.zzz)

この環境だとこんなエラーが出た。
踏み台サーバ -> テストサーバ への接続でパスフレーズを聞かれるのが原因。

CapistranoでSSHのパスワード入力プロンプトが出ずにNet::SSH::AuthenticationFailedエラーとなる問題の解決法あれこれ - Qiita

↑解決方法が2つ記載してあったけど、状況に合わないので別の方法を検討。

ssh-agent & ssh-add による秘密鍵登録

ssh-agentの使い方 - Qiita

↑を参考に問題解決できそう、やってみる。

$ eval `ssh-agent`
$ ssh-add ~/.ssh/deployer_id_rsa

秘密鍵を登録して、

$ bundle exec cap deploy

成功。
ただし、cap コマンド越しの bundle install 失敗、 これはまた別の記事にしよう。

補足:パスフレーズ聞かれる問題

公開鍵 + 秘密鍵の設定なのに、なぜか test_server へのアクセスは毎回パスフレーズを要求される。
色々調べたら下記記事を発見。

公開鍵認証のssh設定のはずが突然パスワードを聞かれるようになった | hello-world.jp.net

そして、ドンピシャで /home/deployer のパーミッションが 0755 になっていた。

/home/hoge や /home/hoge/.ssh 配下のパーミッションが適切でないと、 パスフレーズを毎回聞かれるらしいので注意。

補足:deploy.rb のみで踏み台サーバ経由でデプロイ

~/.ssh/config を利用しないで踏み台サーバを経由する方法もある。

deploy.rbに下記を書く。

set :gateway, 'bastion'

https://groups.google.com/forum/#!topic/capistrano/egnmyhYw93k

capistranoでssh越しのサーバーに設置する場合 - odeの開発メモ日記

ただ、上記に書いてある通り、踏み台サーバを経由する場合に ポート番号・鍵ファイル・パスフレーズの指定を、 踏み台サーバとデプロイ先サーバでそれぞれ設定できないようなので、 複雑な設定が必要な場合は無理ぽ。

Capistrano 3 系であれば、

Capistrano 3で多段sshしたい | BLOG.QuelLENcode

ssh_options で複雑な設定ができるようなので、無理すれば deploy.rb で解決できそう。
ただし、上記記事でも ~/.ssh/config 利用のが簡単だって結論。

Ubuntu12.04 で RMagick Imagemagick をインストールする

微妙な環境だけど、作業メモ。

  • bundler (1.7.11)
  • rails (3.2.19)
  • rmagick (2.13.3) など。

Gemfile に下記のように指定。

...
gem 'rmagick', '2.13.3'
...
bundle install

実行した結果、下記のようなエラー

Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

        /usr/share/ruby-rvm/rubies/ruby-1.9.3-p484/bin/ruby extconf.rb
checking for Ruby version >= 1.8.5... yes
checking for gcc... yes
checking for Magick-config... no
Can't install RMagick 0.0.0. Can't find Magick-config in /usr/share/ruby-rvm/gems/ruby-1.9.3-p484/bin:/usr/share/ruby-rvm/gems/ruby-1.9.3-p484@global/bin:/usr/share/ruby-rvm/rubies/ruby-1.9.3-p484/bin:/usr/share/ruby-rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of
necessary libraries and/or headers.  Check the mkmf.log file for more
details.  You may need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/usr/share/ruby-rvm/rubies/ruby-1.9.3-p484/bin/ruby


Gem files will remain installed in /tmp/bundler20150427-16923-1xvc14n/rmagick-2.13.3/gems/rmagick-2.13.3 for inspection.
Results logged to /tmp/bundler20150427-16923-1xvc14n/rmagick-2.13.3/gems/rmagick-2.13.3/ext/RMagick/gem_make.out
An error occurred while installing rmagick (2.13.3), and Bundler cannot continue.
Make sure that `gem install rmagick -v '2.13.3'` succeeds before bundling.

Imagemagick がインストールされていないってことで、インストール。

sudo aptitude install imagemagick libmagick++-dev

なかなか処理が始まらないと思ってたらタイムアウト
そういえばこの環境ではプロキシ設定が必要だった!

sudo vim /etc/apt/apt.conf.d/00proxy
# 下記追加
Acquire::http::Proxy "http://hoge:80";
Acquire::ftp::Proxy "http://hoge:80";

もう一度、実行

sudo aptitude install imagemagick libmagick++-dev
The following NEW packages will be installed:
  gir1.2-gtk-2.0{a} libcairo2-dev{a} libfontconfig1-dev{a} libfreetype6-dev{a} libgtk-3-0{a} libgtk-3-bin{a}
  libgtk-3-common{a} libgtk2.0-0{a} libgtk2.0-bin{a} libgtk2.0-common{a} libgtk2.0-dev{a} libjasper-dev{a}
  libmagick++-dev libmagickcore-dev{a} libmagickwand-dev{a} libpango1.0-dev{a} librsvg2-bin{a} librsvg2-common{a}
  librsvg2-dev{a} libtiff4-dev{a} libtiffxx0c2{a} libwmf-dev{a} libxcomposite-dev{a} libxcursor-dev{a}
  libxdamage-dev{a} libxfixes-dev{a} libxft-dev{a} libxi-dev{a} libxrandr-dev{a} libxrandr2{a} libxrender-dev{a}
0 packages upgraded, 31 newly installed, 0 to remove and 82 not upgraded.
Need to get 1,939 kB/15.4 MB of archives. After unpacking 59.1 MB will be used.
Do you want to continue? [Y/n/?] Y
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libxrandr2 amd64 2:1.3.2-2ubuntu0.2
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libtiffxx0c2 amd64 3.9.5-2ubuntu1.6
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libfreetype6-dev amd64 2.4.8-1ubuntu2.1
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libxrender-dev amd64 1:0.9.6-2ubuntu0.1
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libxfixes-dev amd64 1:5.0-4ubuntu4.3
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libxi-dev amd64 2:1.7.1.901-1ubuntu1~precise2
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libxrandr-dev amd64 2:1.3.2-2ubuntu0.2
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libjasper-dev amd64 1.900.1-13ubuntu0.1
  404  Not Found
Err http://old-releases.archive.ubuntu.com/ubuntu/ precise-updates/main libtiff4-dev amd64 3.9.5-2ubuntu1.6
  404  Not Found
0% [Working]E: Failed to fetch http://old-releases.archive.ubuntu.com/ubuntu/pool/main/libx/libxrandr/libxrandr2_1.3.2-2ubuntu0.2_amd64.deb: 404  Not Found

E: Failed to fetch http://old-releases.archive.ubuntu.com/ubuntu/pool/main/libx/libxrandr/libxrandr2_1.3.2-2ubuntu0.2_amd64.deb: 404  Not Found

よくわからんけど、404 が出まくっている。
一度、ログアウトして、再ログインすると convertが使えるようになっていたため、 bundle installしてみるもエラー。

なので、一度、imagemagick のみを削除して、ソースから入れ直す。

sudo aptitude remove imagemagick
sudo su - 
cd /usr/local/src
wget ftp://ftp.kddlabs.co.jp/graphics/ImageMagick/releases/ImageMagick-6.8.9-10.tar.gz
tar zxfv ImageMagick-6.8.9-10.tar.gz
cd ImageMagick-6.8.9-10
./configure
make && make check

結果

============================================================================
Testsuite summary for ImageMagick 6.8.9
============================================================================
# TOTAL: 76
# PASS:  76
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0

テストが通ったので、インストール。

make install

インストールも完了し、

bundle install

で無事インストール完了。

4. Rubyによるデザインパターン【Factory Method】

Rubyによるデザインパターン

4回目のデザインパターンは Factory Method(ファクトリーメソッド) パターンの記事を書いてみる。

Template Method と関連性のあるパターン。

実装については簡単だけど、メリットが理解しづらい。
いろいろなサイトを参考にして、3つのメリットを理解したので記事の中で書いていく。


おなじみ、下記ソウルを忘れないこと。
参考:programming - Rubyによるデザインパターン5原則 - Qiita

  • 変わるものを変わらないものから分離する
  • プログラムはインターフェイスに対して行う(実装に対して行わない)
  • 継承より集約
  • 委譲、委譲、委譲
  • 必要になるまで作るな(YAGNI

調査

Factory Method パターン - Wikipedia

Factory Method パターンは、他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする。

Wikiの図の説明だが、 まず Creator という抽象クラスの定義について、 anOperation メソッドfactoryMethod メソッドが定義されているが、 anOperation メソッド内で factoryMethod が呼ばれるものとする。

factoryMethod メソッドは Product クラス型のインスタンスを生成して返却する。

ConcreteCreator クラスは Creator クラスを継承して定義。
factoryMethod をオーバーライドし、Product クラスを継承した ConcreteProduct クラスのインスタンスを生成して返却する。

概念

博士 # Creator
  魔物を呼び出す # anOperation
    魔物を作る # factoryMethod
    魔物が標的を選ぶ
    魔物が標的を襲う
  魔物を作る # factoryMethod(オーバライドされる前提

魔物 # Product
  標的を選ぶ
  標的を襲う

ビバルディ < 博士 # ConcreteCreator
  魔物を作る # factoryMethod のオーバーライド
    魔人プウを生成

魔人プウ < 魔物 # ConcreteProduct
  標的を選ぶ
  標的を襲う
    標的をガムにする

ドクターゲボ < 博士
  魔物を作る
    人造怪人を生成

人造怪人 < 魔物
  標的を選ぶ
  標的を襲う
    標的の脂肪を吸引する

魔王
  人を襲わせる
    ビバルディを生成.魔物を呼び出させる
    ドクターゲボを生成.魔物を呼び出させる

ただ、言葉にしてみただけ感・・・

博士 = Creator
魔物 = Product
ビバルディ = ドクターゲボ = ConcreteCreator
魔人プウ = 人造怪人 = ConrcreteProduct
魔王 = 博士を利用するクラス

上記のような割当となる。

魔王視点で考えるならば、魔物を意識せずに標的を襲わせることができる。
つまり、いちいち魔王が魔物の操作をしなくて済むし、博士に魔物を呼び出せと命令するだけでいい。
これが Factory Method の1つ目のメリットだと考える。(Template Method のメリットとも言えるw)

博士の魔物を呼び出すメソッドが Template Method になっていて、 魔物を作るメソッドが Factory Method になっている。
以下、魔物を作るメソッドに該当するものをファクトリーメソッドと呼ぶ。

博士クラスの魔物を呼び出すメソッドが「魔物を生成して標的を襲うまで」のアルゴリズムを持っているので、 博士クラスを継承するビバルディ・ドクターゲボはこの Template Method の恩恵にあずかることができる。

実装

class Dr
  def call_monster
    monster = create_monster
    monster.select_target
    monster.attack_to_target
  end
  def create_monster;end
end

class Monster
  attr_accessor :target
  def select_target;end
  def attack_to_target;end
end

class DrGebo < Dr
  def create_monster
     MysteryMan.new
  end
end

class MysteryMan < Monster
  def select_target
    search_by_radar
  end
  def attack_to_target
    p "attack to #{@target}"
  end
  private
  def search_by_radar
    @target = "ririkun"
  end
end

class Vivaldi < Dr
  def create_monster
    Puu.new
  end
end

class Puu < Monster
  def select_target
    search_by_smell
  end
  def attack_to_target
    p "attack to #{@target}"
  end
  private
  def search_by_smell
    @target = "Mr. Zatan"
  end
end

class Maou
  def self.attack
    DrGebo.new.call_monster
    Vivaldi.new.call_monster
  end
end

Maou.attack
# 実行結果
# "attack to ririkun"
# "attack to Mr. Zatan"

実装を通して見えてくる2つ目のメリットは、 create_monster メソッド(ファクトリーメソッド)がサブクラスへ切り出されているおかげで、 開発が進めやすいというところ。
違う言い方をするなら、結合度が低くなり独立性が高まる。

Dr のサブクラス担当者はこのインスタンスをモックに置き換えて実装しておくことで、 Maou クラス(利用側)担当者の実装を滞らせることはなくなるし、 Monster のサブクラスの実装を分離して進められる。

複数人で共通ブランチを弄る場合、モック置き換えが活きる。
開発中のバグがある実装をコミットしても、モックのおかげで誰の実装にも影響を与えなくて済む。
テストを書いて実装が終わったなら、モックを本来のインスタンス生成に置き換えれば、これまた誰の実装に影響もなく済む。



そして、わかりづらい3つ目のメリットは、 Dr, Monster クラスの担当者がサブクラスを全く気にせずに実装を進められる。
つまり、フレームワーク側を作る側にメリットがある。

↑のサイトを何十回も読み直し・・・

Factory Method パターンの親クラスにあたる実装をフレームワークとして捉えた場合、 サブクラスの具象名や実装を全く気にしないで実装を進めることができる。

またフレームワークを提供するに辺り、 サブクラス実装者にインスタンスの生成を委ねられることが、 フレームワークとしてのあり方でもある。

でもわざわざ Factory Method にしなくても、利用側でインスタンス生成して渡せばいいのでは感もある。 つまり、Strategy パターンを適用した場合どうなるのか。

class Dr
  def initialize(monster)
    @monster = monster
  end
  def attack
    @monster.attack # monster に委譲
  end
end

class Monster
  def attack; end; # インターフェースの統一
end

class MysteryMan < Monster
  attr_accessor :target
  def attack # インターフェースの実装
    search_by_radar
    attack_to_target
  end
  def search_by_radar
    @target = "ririkun"
  end
  def attack_to_target
    p "attack to #{@target}"
  end
end

class Maou
  def self.attack
    Dr.new(MysteryMan.new).attack
  end
end

Maou.attack
# 実行結果
# "attack to ririkun"

実装のキモが MysteryMan (委譲先)に寄り過ぎてフレームワークとしてあまり価値のない状態になった。
Strategy パターンはやはり、アルゴリズムの選択に意味があるのであって、実装にルールを作るフレームワークの要件に合っていない。

Template Method パターンならどうなのか。

# 生成に関する Dr は必要なくなるので実装しない
class Monster
  def attack
    select_target
    attack_to_target
  end
  def select_target; end
  def attack_to_target; end
end

class MysteryMan < Monster
  def select_target
   ...

まず、Template Method は継承なので結合度が高い。
Factory Method だと、インスタンスを生成して、それに対して処理を委譲するので結合度は低くなる。

Template Method だと実装が増えるにつれて結合度が増していき、管理しづらいコードになっていくと予想される。
(コード量が多くないなら気にならないが。)

では、Template Method パターン内で処理を委譲するインスタンス生成をさせたらいいのでは。

class MysteryMan < Monster
  def select_target
     hoge = Hoge.new
     ...

処理を Hoge インスタンスに委譲できたとしても、 インスタンス生成がベタ書きになるので結合度は高く、 変更に弱いし、並列開発しづらい状態になる。

ファクトリーメソッド の改造

長いけど、さらに補足。
wiki には下記のようなことが書いてあった。

factoryMethodは、デフォルトの動作を含んだ具象メソッドである場合もある。パラメータを取り、それによって生成するクラスを変えることもある。

これはどういうことなのかというと、

class Vivaldi < Dr
  def create_monster(name)
    if name == "puu"
      Puu.new
    else
      Ruu.new
    end
  end
end

生成するインスタンスの種類が増えているが、 ファクトリーメソッドによって生成するインスタンスをサブクラスが選んでいる。

利用する側はインスタンス生成に関する分岐を意識しなくて済むことがメリットになる。

ruby っぽく書くなら下記。

class Vivaldi < Dr
  def create_monster(name)
    Object.const_get(name.capitalize).new rescue nil
  end
end

まとめ

サブクラスにファクトリーメソッドをオーバーライドさせてインスタンス生成させときゃいいんだろって、 言葉の上でもソース上でも簡単そうな Factory Method パターン。

実際はどういう角度でこのパターンを見るかによって、 メリットや使いどころが大きく変わるので理解が難しい。

ただ単にインスタンス生成に無理矢理このパターンを使っても全くメリットがないし、 むしろ、ソースが読みづらくなるだけのデメリットしかない。

使いどころ

個人的な使いどころの見解としては、

インスタンス生成すべき種類が多い場合には、 ConcreteCreator + ConcreteProduct を増やすのではなく、 ConcreteCreator のファクトリーメソッド内に分岐を作り、 ConcreteProduct を増やして インスタンスを作り分ける実装をするといい。

おさらい

もっと単純にまとめよう。
Factory Method は下記のとき使う。

次回

次回は Abstract Factory やな。