Rubyを用いてデザインパターンを学ぶ
デザインパターンとは
Gang Of Four(GoF)が定義したもの Javaで書かれた本がある。 「オブジェクト指向における再利用のためのデザインパターン」 1999/10 出版
- プログラミングにおいて繰り返し現れる問題に対する、適切解のパターン
- 無駄なく設計されたオブジェクト指向プログラムの実現をサポート
- 諸刃の剣
パターンとしてカタログ化されていることで 車輪の再発明を防ぐ
デザインパターンに重要な5つの考え
- 変わるものを変わらないものから分離する
- プログラムはインターフェースに対して行う (実装に対しては行わない)
- 継承より集約
- 委譲、委譲、委譲
- 必要になるまで作るな (YAGNI)
変わるものを変わらないものから分離する
理想的なシステムは、すべての変更が局所的であるべき。 ソフトウェアに完璧は存在しない。 変わるものを変わらないものから分離することによって、 将来起こりうる「新たな変更」に対して柔軟に対応できるようにしておく。
プログラムはインターフェースに対して行う (実装に対しては行わない)
可能な限り「一般的・抽象的なもの」に対してプログラミングすること。 (ここで言うインターフェイスは、Javaの組み込み構文としてのインターフェイスではなく、 より広いレベルで、「抽象度を高めたもの」を意味する。)
Bad
具象性が高く、密結合なコード
if is_car my_car = Car.new my_car.drive(200) else my_car = get_vehicle my_vehicle.travel(200) end
Good
抽象度が高く、疎結合なコード
my_vehicle = get_vehicle
my_vehicle.travel(200)
抽象度を高くすることによりコードにより変更に強くなる
継承より集約
継承はよろしくない繋がりをつくってしまう
class Vehicle def start_engine # エンジンスタートの処理 end def stop_engine # エンジンストップの処理 end end class Car < Vehicle def drive(distance) start_engine() # driving.... stop_engine() end end
- Carからエンジンの実装が丸見え
- エンジンを使用しない乗り物を作りたい場合には大きな変更を加える必要が出てくる
-> 変わりやすい部分(Engine)を変わりにくい部分(Vehicle)から分離できていない
じゃあどうするのか?
集約を使い解決する。 オブジェクトに「他のオブジェクトに対する参照」を持たせる。 オブジェクトが何かの一種である(is-a-kind-of)関係(継承)を避け、 何かを持っている(has-a)関係(集約)にする
依存オブジェクトの注入
class Engine def start # エンジンスタートの処理 end def stop # エンジンストップの処理 end end class Car def initialize @engine = Engine.new() # 依存オブジェクトの注入 end def drive(distance) @engine.start() # distanceだけ走る処理 @engine.stop() end end
これでEngineをVehicleから分離し、カプセル化された 依存オブジェクトの取り扱いには注意する必要がある。
委譲、委譲、委譲
委譲(delegation)
class Car def initialize @engine = GasolineEngine.new end def drive @engine.start # driving.. @engine.stop end def switch_to_diesel @engine = DieselEngine.new end def start_engine @eigine.start # Engineクラスに任せる end def stop_engine @eigine.stop # Engineクラスに任せる end end
集約と委譲の組み合わせは、強力かつ柔軟な継承代替手段となる。
必要になるまで作るな (YAGNI)
You Ain't Gonna Need It
「将来使うかも」は、大抵は使わない
これはデザインパターンに限った話ではなくアプリケーション開発全般においても言えること。 不要な処理は書かない。 必要になった時に必要な分だけの実装を行い無駄のないシステムを作る。
Special Thanks
オブジェクト指向設計 実践ガイド 読んだ
「オブジェクト指向設計 実践ガイド 」を一通り読み終えました。
オブジェクト指向設計実践ガイド ?Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 作者: Sandi Metz
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/02
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
結構読むのに時間がかかってしまったのですがとても良い内容でした! オブジェクト指向についてはなんとなく知っているけれども、しっかり実践で活用しきれていない中級プログラマーの方が読むといいかもしれません。
よくオブジェクト指向関連の本に出てくるワード、「ポリモーフィズム」「ダックタイピング」「単一責任原則」「デザインパターン」など。
この本では実際にそれらのワードについてソースコードのリファクタリングのような形(before|after)で分かりやすく説明してくれています。
なので、読んでいると「あぁ、これってこういう風に使うと便利なのか!」という気づきが多くありました。
実際の現場でオブジェクト指向で組まれたプログラムを見たことがあるのですが、実践ガイドを読む前と、読んだ後ではコードの見方が大きく変わりました。
他の方も書評で書かれていたのですが、文章が少し抽象的な表現が多かったかなと。 読んでてこれってどういうこと?みたいなところが所々ありました。 読み手の理解力の無さが原因でもあるのですが(笑)、自分は読み解くために2、3回時間を空けて読み直すことで理解を深めていきました。
今までオブジェクト指向関連は、ネットの記事や先輩の説明などでなんとくなく理解していたつもりでいました。 今回本を用いて体系的に学ぶことで型を学ぶのは大事だなと改めて思いました。
特に印象に残ったところを簡単にメモ書きしておきます。
メモ
- 実用的な設計とは、未来を予測するものではなく、動くための余地を設計者に残すものです。
- 彫刻家がノミとやすりを持つように、オブジェクト指向の設計者も道具を持つ。それが、原則とパターン
- 設計者の目標は、機能あたりのコストが最も低い方法でソフトウェアを書くこと
- 実践とは配られたカードでベストを尽くすことの積み重ねなのです
- 設計とはアプリケーションの可変性を保つために技巧を凝らすことであり、完璧を目指す行為ではありません
- 見通しが良い(Transparent)・合理的(Reaspnable)・利用性が高い(Usable)・模範的(Examplary) TRUEなコードを書く
- メソッドも単一責任にし、なるべくコードを単純化する
- オブジェクト指向アプリケーションは「クラスから成り立つ」が、メッセージによって「定義される」
- パブリックインターフェースとプライベートインターフェースの使い分けで不要な関連づけを防ぐ
- 依存オブジェクトの注入・ダックタイピング・テンプレートメソッドパターン・ファクトリーパターン・コンポジション・共通モジュール
12/16(金) 追記
Qiitaでjoker1007さんが「オブジェクト思考設計実践ガイド」を読め。と言及していました。
俺が悪かった。素直に間違いを認めるから、もうサービスクラスとか作るのは止めてくれ
Railsに用いるserviceクラスの問題点について述べられています。 後半にはオブジェクト指向設計に関する重要な要点を完結にまとめてくださっています。感謝 学習のメモとして引用させてもらいます。
- オープン・クローズドの原則
- 単一責任の原則
- デメテルの法則
- クラス同士の依存関係の管理
- 実装ではなくインターフェースに依存する
- ダックタイプとクラスの中に内在するインターフェース
- 集約クラス
オープンクローズドの原則
先に述べた様に、変更が発生した場合に自分以外の外に影響がもれずに閉じている状態が望ましい。 でないと、ある処理を変えた時に連鎖的に調査範囲が拡大して変更をコントロールできず死ぬ。 もしサービスクラスが、モデルレイヤーのメソッドをいくつも直接呼び出しているとすると、モデルにちょっとした変更が入ったらぶっ壊れるかもしれない。 そんなのがいくつもあると辛みしかない。
単一責任の原則
一つのクラスが担う責務は一つにすること。 ActiveRecordを元にしたクラスはRDBという根幹になるデータ構造と紐付いているため、複数の役割を担うことになる可能性が高い。しかし、それを別のクラスに分割することはできる。 責任範囲でクラスを分けることを意識しないと半端に元のクラスと結合した扱いづらいクラスが生まれることになる。 モジュールで分けても本質的には抱え込んでいる責務を分けたことにはならない。 どういった責任をどういった名前を持ったクラスに与えるべきなのかを考えて、クラスを作っていくことが大事だ。
デメテルの法則
あるクラスが触っていいのは、自分が直接知ってるクラスだけという状態を維持しなければならない。 これを守ろうとしないと、あるクラスがあちこちのクラスに依存し、処理の詳細まで抱え込んだ結果、変更が発生した時の影響範囲が拡大し過ぎて爆死することになる。 また、デメテルの法則に反したコードは、隣人クラスに問い合わせして取得した結果を元に自分で判断する様なコードになることが多い。 こういったコードは前述した様にテスタビリティが悪い。
クラス間の依存関係
自分んおクラスが直接持っている情報以外を触るということは、外部の何かの知識に依存するということであり、それが存在していないと処理が完結しないことを意味する。 自分以外のクラス名や、それが持つメソッド名、メソッド名、メソッドの引数、メソッドの返り値、外部クラスの処理結果を知れば知る程依存関係が強くなり、変更が辛くなる。 依存が少なくなる様な構造を考える必要がある。 また、依存関係は一方向に保つこと。相互依存するような関係はオブジェクトの生成プロセスや処理の流れを激しく複雑にする
メソッド名の抽象化、引数の取り扱い、依存オブジェクトの抽出 etc... 少し意識を変えて書くだけで依存関係を適切な形に変更できる。
インターフェースに対して依存する
あるクラスが知っておくべき知識をできるだけ抽象化しておいた方が良い。 具体的な処理内容はとても変わりやすく、その内容に依存しているとすぐ壊れる。 そして単純に読みにくさが増す(実装を行ったり来たりしないと何が起きてるか把握できない)。 ポリモーフィスムの力を活かす上でもインターフェースを意識したコードを書くことは重要。
例: 抽象化 carやbikeではなくvehicleとする
ダックタイプ
Rubyという言語は何も記述しなくても、メソッドの定義だけを元に対象が何者であるかを判断する。 しかし、それはインターフェースが消えて無くなったわけではない プログラマが逐一書かなくてもいいようになっているだけで、その処理に利用される各クラスはインターフェースの実装としての姿を持っている必要がある。 どの様にインターフェースを見出してクラス間の依存関係をどう表現するかは、プログラマの脳内にある業務領域とクラス構造のマッピングにかかっている。 プログラマが楽できる分、その知識に信頼性が求められている。
集約クラス
クラスが持つ責任範囲には境界線がありグルーピングできることが多い。 ここから先の詳細は全部こいつに任せる、後は知らなくていい、そういうクラス同士の関わり方の地図を作っておかなければいけない。 そうでないと、複雑さが組み合わせによって無限に増えて行く。 Ruby/Railsでは、それを言語レベルで強制する手段は無いので、プログラマの責任として守らなければいけない。 V字型により詳細を知っているクラスにブレイクダウンしていく動きを想像して欲しい。 モデル層の中にも階層構造は存在するし、処理の大枠だけを管理するコントローラーの様な存在はある。
MySQL5.7 JSON型の使い方メモ
MySQL5.7から実装されたJSON型 使い方メモ
JSON型のテーブルを作成する
CREATE TABLE `blocks` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(120) NOT NULL, `content` varchar(200) NOT NULL, `meta` json DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
JSON型のデータを追加する
INSERT INTO `blocks` (`title`, `content`, `meta`) VALUES ( 'qiita', 'ruby', '{"ruby":2.3,"rails":5,}' );
引数のデータを確認する
SELECT JSON_TYPE('["ruby", "rails", "jquery"]'); return ARRAY
オブジェクトに変換し返す
SELECT JSON_OBJECT('ruby', 2.3, 'rails', 5); return {"ruby": 2.3, "rails": 5}
データの結合を行う
SELECT JSON_MERGE('["ruby", 2.3]', '{"rails": "5"}'); return ["ruby", 2.3, {"rails": "5"}]
値を検索する
$.key名で値を探し、値が存在しない場合にはNULLが返ってきます。
SELECT JSON_EXTRACT('{"ruby": 2.3, "rails": "5"}', '$.rails'); return "5" SELECT JSON_EXTRACT('{"ruby": 2.3, "rails": "5"}', '$.php'); return NULL
WHERE句でJSON_EXTRACTを使うことでJSON型の中身で一致たした結果のレコード返す
LIKE文も使えるので便利
metaカラム全体にたいしてLIKE検索をかけることももちろんできる
SELECT * FROM blocks WHERE JSON_EXTRACT(meta, '$.ruby') = "2.3"; SELECT COUNT(id) FROM blocks WHERE JSON_EXTRACT(meta, '$.key') LIKE "%value%"; SELECT id, meta, created_at FROM blocks WHERE meta LIKE "%rai%" ORDER BY created_at DESC;
JSON型は使えるようになれば便利ではあるなと思いました。
ReactのVirtualDOM componentDidMount
React.jsを使っていてVirtualDOMに対してライブラリを適用させたい場面がありました
VirtualDOMなのでライブラリを適用させるタイミングに肝心のDOMがまだレンダリングされていないという状況に遭遇。
Reactの動きは崩さず、VirtualDOM群がレンダリングを終えたタイミングで発火させたい。
ReactのcomponentDidMountと呼ばれる関数を使えば解決できました。
componentWillMount()
componentWillMount()とは、
ComponentがDOMツリーに追加された状態で呼ばれるのでDOMに関わる初期化処理を行いたい時に便利です。
componentWillMountと違いserver-side renderingの時には呼ばれません。
DOMを扱う処理のほか、AjaxリクエストやsetIntervalの登録などのserver-side rendering時には必要ない初期化処理についてはこの中でやることになります。
React.jsのComponent Lifecycle - Qiita
「ComponentがDOMツリーに追加された状態で呼ばれる」ものなので、ここに呼び出し後のDOMに対して当てたい処理を定義すればOK!
var Box = React.createClass({ getInitialState() { return { windowWidth: window.innerWidth }; }, handleResize(e) { this.setState({windowWidth: window.innerWidth}); }, componentDidMount() { // 描画が成功して、DOMにアクセス可能になる }, componentWillUnmount() { // 描画が行われる直前に呼ばれる }, render() { return <div>Current window width: {this.state.windowWidth}</div>; } }); React.render(<Box />, mountNode);
すごくわかりやすい例が載っているのがこちら。
qiita.com
Rubyの配列展開 *[a, b, c]
Rubyの配列展開
array = ["a", "b", "c"] *array # => a # => b # => c
arrayのオブジェクトが入った変数の先頭に*(アスタリスク)をつけることでarray[0] array[1] array[2] の出力をしてくれる。
Rubyの中でも地味な機能ではあるけれども便利。筆者はこの機能で救われた。
動的に変わる引数に対してActiveRecordでLIKE検索を行いたい時に役に立った。
CSV.foreach(path) do |keywords| keywords.join(", ").split.each do |keyword| query << 'title LIKE ?' words << "'%#{keyword}%'" end item_count = Item.where(query.join(" AND "), *words).count end
プレースホルダーにするのはSQLインジェクションなどを考慮してです。
上記では"title LIKE ?" のクエリがkeywordsの数動的に生成され、*wordsで配列展開が行われクエリの引数がkeywordsの値分出力されてSQLになります。
まだまだRubyへの理解が足りないな。。。。
追記
メソッドの引数に*をつけると可変長になるらしい。
: **とアスタリスクを2つ並べると配列ではなくハッシュ{}形式の可変長になるそうだ!!
hash = { a: 1, b: 2, c: 3 } p *hash #=> [:a, 1] #=> [:b, 2] #=> [:c, 3] p **hash #=> {:a=>1, :b=>2, :c=>3}
: *が配列展開 **がハッシュ展開
Railsの画像遅延ロードにはlayzr.jsがいいぞ
Webサービスのパフォーマンスを向上させるために表示する画像の遅延ロードを実現したい!
そんなときにオススメなlayzr.jsを紹介します。筆者は実際にプロダクトに実装しました。
画像の遅延ロードをすることで得することは?
読み込み速度が速くなったり通信量も制限できてサーバー代なども微量ながら抑えることができるかなと思います。
layzr.js
紹介サイト
http://callmecavs.com/layzr.js/callmecavs.com
A small, fast, and modern library for lazy loading images.
画像遅延ロードに関するライブラリはいくつか存在しますが、
layzr.jsは速さと軽さに重点をおいた非jQueryのライブラリです。
CDN
<script src="https://cdn.jsdelivr.net/layzr.js/2.0.2/layzr.min.js"></script>
npm
npm install layzr.js --save
layzr-rails
今回Railsアプリに実装するにあたってlayzr-railsというGemをinstallします。
layzr-rails自体にはJS周りのコードは含まれていません。
これはViewHelperのimage_tagを拡張してくれるGemで、
以下のようにlazy: trueにすることによって遅延ロードを可能にしてくれます。
Rails5でも問題なく動作することを確認しています。
<%= image_tag "kittenz.png", alt: "OMG a cat!", lazy: true %>
Equals:
<img alt="OMG a cat!" data-normal="/images/kittenz.png" src="/assets/some-default-image.png">
実装
layzr.js
import Layzr from 'layzr.js' const instance = Layzr(); instance.on('src:after', element => { if(!element.hasAttribute('data-layzr-bg')) return; element.style.backgroundImage = `url("${ element.getAttribute('data-normal') }")` element.removeAttribute('src'); element.removeAttribute('data-layzr-bg'); }) document.addEventListener('DOMContentLoaded', () => { instance.update().check().handlers(true) }, false); View
import Layzr from 'layzr.js'
ここでライブラリを読み込みます。
instanceを作成
optionがいくつか設けられています。
src:before や src:afterにコールバックを定義できます。
instance.on('src:after', element => { // ... }) .on('src:after', image => { // ... })
上記ではsrc:afterに処理を定義してCSSのbackgroundImageをsrcの読み込みの後に画像がセットされるようにしています。
src:afterとあるようにinstanceの処理が終わった後に実行される処理です。
[注意]
他にもいくつかLayzr.jsに関する紹介記事がGoogle検索をすると見つかりますが、
紹介されたlayzr.jsのバージョンが古く2.0.0以降で廃止された機能なども存在するので気をつけて下さい。
・data-layzr-bg
・data-layzr-hidden
・data-layzr
このような属性は2016/10現在では削除された機能です。
GitHubのissueなどを覗くと廃止された機能の現在での対応方法が紹介されていたりするのでそこを見に行きましょう。
git cherry-pick is 良い
学びをメモ:
Gitを使っていて特定のブランチの特定のコミットのみを新しく作ったブランチに反映させたいなという場面がありました。
git rebaseもあるけどそれだと全てが過去のコミットとして反映されるから嫌だな〜、何か良い方法はないかな〜、と探していた時にgit cherry-pickを見つけました。
git cherry-pickの使い方は超簡単で、コミットを反映させたい特定のブランチで
cherry-pick コミットID
のように反映させたいコミットIDを指定すればそのコミットの履歴が反映させられます。
また必ず使う機会がくると思うの今度は迷わずcherry-pickを使いたいと思います。
そして最近思うのはどんなに技術を知っていても実際に使う場面に遭遇して、実際に使ってみないと身に染みて覚えないなと。
ゲームでもリアルでも経験値ってやっぱ大事だよね。
これでまた1つgitのレベルが上がった気が.......する。
gitは猿でもわかるんやで
www.backlog.jp