1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

デザインパターンは23個もあって、学ぼうとすると「全部覚えるのか…」と身構えがちです。

でも、いきなり暗記に入る前に「前提」を整理しておくと、パターンが暗記ではなく理解として入ってきます。

この記事では、その前提を5つの層でまとめます。

全体像:前提は「5層」で捉える

なぜ「前提」から入るのか

いきなり23個を名前から覚え始めると、たいてい途中でつらくなります。

似た名前が多いし、どれをいつ使うのかが見えないからです。

遠回りに見えますが、「なぜパターンが要るのか」「何を使って作るのか」「どう使うのか」を先に押さえた方が、結局は速いです。

パターンは、こうした前提があって初めて活きます。前提を飛ばして名前だけ覚えても、なかなか身につきません。

5層モデルの全体像

前提は、次の5つの層で捉えると一本につながります。

image.png

上から「なぜ → 何で → どの機能で → どう使う → どう組む」と降りていくだけです。

一番上が目的、一番下がデザインパターンです。この地図が頭にあると、個別のパターンが「どこの話なのか」で迷わなくなります。

各層は「1つの問い」に答える

この5層で分けると、各層で考える問いが変わります。

目的は「なぜ」、発想は「何で」、機構は「どの機能で」、設計原理は「どう使う」、定石は「どう組む」。

問いが違うから、層同士が混ざりません。ここから1層ずつ降りていきます。

第1層 目的:複雑さの制御と変化への対応

ソフトウェアは育つと頭に入らなくなる

そもそも、なぜ設計を工夫するのか。理由の1つは「複雑さ」です。

ソフトウェアは育つと、全体を一度に頭に入れておけない規模になります。

だから、一度に1か所だけ考えれば済むように、境界で区切って部品に分ける必要が出てきます。

変化は必ず来る

もう1つの理由が「変化」です。仕様は必ず変わります。

このとき設計が悪いと、1か所直しただけで、触っていないはずの機能まで連鎖して壊れます。

image.png

左のように影響が散らばっていると、変更のたびに何か所も直すことになります。

右のように「変わりそうな部分を箱の中に隠して、外には安定した窓口だけ見せる」と、変更が箱の中で止まります。

この「変わるものを隠して影響を局所化する」という考え方は、後の層でも何度も出てきます。

これはどんな設計にも共通する目的

念のためですが、「複雑さと変化への対応」は特定の言語やツールに限った話ではありません。

どんな設計にも共通する、もっと大きな目的です。だからこそ、これが5層の一番上に来ます。

第2層 発想:オブジェクト指向という考え方

複雑さと変化への対応、という目的は決まりました。次は、それにどう立ち向かうかです。

この記事で扱うデザインパターンは、オブジェクト指向という考え方を前提に作られています。だからここでは、オブジェクト指向を土台に置きます。

世界を「オブジェクトの集まり」で捉える

この目的に対して、オブジェクト指向は「状態と振る舞いを持つオブジェクトを単位にし、それらを協調させて解く」という考え方です。

たとえば注文・商品・支払いといったものを、それぞれデータと処理をまとめた「モノ」として扱い、やり取りさせながら全体を動かします。

まずは「捉え方」だと思ってください。

なぜ「オブジェクト」という単位なのか

複雑なものは、意味のある単位に分けると扱いやすくなります。

状態(データ)と振る舞い(処理)をひとまとめにした「オブジェクト」を単位にすると、大きくて複雑なものを、部品同士の協調として見通せるようになります。

これが、複雑さと変化に立ち向かう1つの答え方です(関数型など、別の切り口もあります)。

考え方だけでは動かない

「オブジェクトで捉える」という考え方は分かりました。でも、考え方だけではコードは動きません。

オブジェクトを実際に作ったり、中身を守ったり、組み合わせて動かしたり。それを実現する具体的な仕組みが要ります。

それを次の第3層で見ていきます。

第3層 機構:オブジェクト指向の4本柱

オブジェクト指向を説明するとき、よく使われる整理が「4本柱」です。ここでは、デザインパターンを読む前提として、この4つを押さえます。

image.png

カプセル化:中身を隠して窓口だけ見せる

データと、それを操作する処理を1つにまとめ、外からは決められた窓口だけを通して扱えるようにする考え方です。

たとえば銀行口座なら、残高を外から勝手に書き換えさせず、「入金」「出金」といった決められた窓口を通してしか触れないようにします。

こうすると「残高がマイナスになる」ような不正を防げます。第1層で見た「変わる中身を隠す」を、そのまま形にした機能です。

抽象化:「何ができるか」だけを先に決める

抽象化は、具体的な細部をいったん脇に置き、「何ができるか」「何として扱うか」を取り出す考え方です。コード上では、インターフェースや抽象クラスのような約束として表れます。

たとえば「支払える」という約束だけ決めておいて、クレジットカードなのか現金なのかは後回しにできます。

使う側は「その約束を満たす何か」に対して書けるので、中身が何かを知らなくて済みます。

継承:共通部分を受け継ぎ、差分を表す

既存のクラスを土台にして、共通する振る舞いを受け継ぎ、変えたい部分だけを上書きする仕組みです。

共通部分を親にまとめられるので便利ですが、親を変えると子まで影響が及びます。

便利ですが「使いすぎ注意」の仕組みです。この注意点は、第4層の原理にもつながります。

ポリモーフィズム:同じ呼び方で違う動きをする

同じメソッド呼び出しでも、相手のオブジェクトによって実際の動きが変わる性質です。

「支払う」と呼んだとき、相手がクレジットカードなら決済処理、現金なら釣り銭計算、というように動きが変わります。

これがあると、「相手の種類ごとに if で分岐する」コードを、「オブジェクトを差し替えるだけ」に置き換えられます。呼ぶ側から種類ごとの分岐を追い出しやすくなります。

この4つを、デザインパターンを読むための道具として押さえておきます。

第4層 設計原理:GoFパターンを読むための3つの考え方

機構(4本柱)は、あくまで「使える道具」です。でも、道具をどう使うべきかは別の話です。

継承はできるけど、何でも継承すればいいわけではない。抽象化はできるけど、何でもインターフェースにすればいいわけでもない。

そこで効いてくるのが設計原理です。設計原理としてよく挙げられるのが、次の3つです。

image.png

この3つは並列ではなく、「やること1つ」と「その実現手段2つ」に分かれます。

変化する部分をカプセル化する

一番根本の原理です。

コードの中で「後で変わりそうな部分」を見つけて、1か所に隔離しておきます。

たとえばECサイトで送料の計算方法がキャンペーンでよく変わるなら、送料計算だけを別の場所に切り出しておきます。

そうすれば、ルールが変わってもその箱だけ直せば済みます。

インターフェースに対してプログラムする

ここでの「インターフェース」は「こういうことができる、という約束(型)」のことです。

具体的なクラスを名指しせず、その約束に対して書きます。

「クレジットカード」という具体ではなく、「支払える何か」という約束に向けて書いておく。

こうしておくと、支払い方法が増えても、使う側を直さずに差し替えられます。

継承よりコンポジションを好む

機能を「親から受け継ぐ(継承)」より、「部品として持たせて組み合わせる(コンポジション)」方を優先しよう、という原理です。

継承だけで機能を足そうとすると、組み合わせのたびにクラスが増えていきます。

たとえばゲームのキャラクターに武器を持たせるとき、継承で表そうとすると「剣を持つ戦士」「弓を持つ戦士」「剣を持つ魔法使い」…と、職業 × 武器の数だけクラスが必要になります。しかも戦闘中に武器を持ち替えられません。

コンポジションなら、キャラクターが「武器」という部品を持つだけです。

剣から弓へ、その場(実行中)で差し替えられます。クラスも増えません。

原理2と3は、どちらも「変わる部分を差し替え可能にする」ための手段になっています。

第5層 定石:デザインパターン

設計原理まで降りてきました。いよいよ一番下の層、デザインパターンです。

ここまで来ると、デザインパターンの見え方が少し変わります。

なぜ「作る・組む・動かす」の3分類なのか

23個は、3つのカテゴリに分けられています。

image.png

パターンは「目的(そのパターンが何をするか)」で分類されています。そして、オブジェクトを扱ううえでやることは、大きく「作る・組む・動かす」の3つに整理できます。だから3分類になっています。

  • 生成(作る):オブジェクトを作る過程に関するパターン。「どう作るか」を柔軟にする
  • 構造(組む):クラスやオブジェクトを組み合わせて、大きな構造を作る方法に関するパターン
  • 振る舞い(動かす):オブジェクト同士がどうやり取りし、責任を分担するかに関するパターン

「23個がのっぺり並んでいる」のではなく、「作る・組む・動かすの3つの棚に分かれている」と見えるだけで、だいぶ気が楽になります。

パターンは「設計原理の応用」として読める

デザインパターンの多くは、第4層の設計原理を、よくある問題ごとに形にした「定石」として読むことができます。バラバラの暗記項目ではありません。

面白いのは、いきなりパターン名から入らなくても、設計原理を問題に当てていくと、有名なパターンに自然と近づける点です。

1つ具体的にたどってみます。「支払い方法(クレジットカード・PayPay・現金)を後から増やしたい」という、よくある場面です。

素朴に書くと、注文処理の中にこんな分岐が並びます。

if 支払い方法 == クレカ:
    ...クレカの処理...
elif 支払い方法 == PayPay:
    ...PayPayの処理...
elif 支払い方法 == 現金:
    ...現金の処理...

支払い方法が増えるたびに、この分岐(=注文処理そのもの)を触ることになります。変化に弱い状態です。

ここに、第4層の設計原理を順番に当てていきます。

  1. 変わる部分(支払い方法)を、注文処理から切り出して別の箱にする(原理1)
  2. 「支払える何か」という約束に対して書く(原理2)
  3. 注文が支払い方法を部品として「持つ」ようにする(原理3)

すると、こういう形になります。

このように、「変わるアルゴリズムを部品として切り出し、差し替えられるようにする」形が Strategy パターンです。

新しい支払い方法を足しても、注文処理の本体には手を入れずに、部品を差し替える形にできます。

Strategy の定義は、こうです。

アルゴリズムの一群を定義し、それぞれをカプセル化して、交換可能にする。Strategyは、アルゴリズムを、それを使う側から独立して変えられるようにする。

よく読むと、この定義は設計原理の言い換えになっています。「アルゴリズムの一群を定義し、それぞれをカプセル化」=変わる部分を切り出して閉じ込める(原理1)。「交換可能にする」=具体クラスではなく約束に対して扱う(原理2)。「使う側から独立して変えられる」=注文が支払い方法を部品として持ち、差し替えられるようにする(原理3)。

だから、パターンは名前を覚えるものではありません。パターンで得られるのは「コードの再利用」ではなく「経験の再利用」だ、とよく言われます。先人が繰り返し出会った設計上の問題と解き方に、名前を付けて共有できるようにしたものがパターンです。

読むときも、「どの原理を、どう当てた結果なのか」で読むと、仕組みが見えてきます。

まとめ:これから暗記でなく応用として学べる

最後に5層をもう一度。

  • 第1層 目的:複雑さと変化への対応
  • 第2層 発想:オブジェクト指向
  • 第3層 機構:4本柱
  • 第4層 設計原理:GoFパターンを読むための3つの考え方
  • 第5層 定石:デザインパターン

この地図があると、次に個別パターンを学ぶときの読み方が変わります。

名前から丸暗記するのではなく、こう問いながら読むと理解になります。

  • このパターンは、何の変化に備えているのか?
  • 何を隠している(隔離している)のか?
  • どの具体への依存を切っているのか?
  • 継承ではなく組み合わせで、何を差し替えられるようにしているのか?

前提を組み直せば、これからの学習は暗記ではなく応用になります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?