デザインパターンは、設計でくり返し出てくる問題への定型的な解き方をまとめたものです。ただ、名前と分類を覚えても「で、何が良くなるのか」が分かりにくいのが正直なところだと思います。
そこで今回は測りました。支払い方法を扱うコードを2通りで書いて、4種類目を追加するときに何箇所直す必要があるかを数えています。結果は3箇所と1箇所でした。
設計でくり返し出てくる問題に、名前と解き方を与えたものです。そのまま貼れるコードではなく、状況に合わせて実装する見取り図にあたります。
デザインパターンは、設計でよく起きる問題に対する定型的な解き方をまとめたものです。あらかじめ用意された設計図のようなもので、自分のコードに合わせて形を変えて使います。
ここで重要なのは、パターンがコードそのものではないことです。ライブラリや既製の関数のように、見つけてきてコピーすれば動く、というものではありません。
パターンはアルゴリズムと混同されがちです。どちらも「よくある問題の典型的な解き方」を指すからです。ただ、アルゴリズムが具体的な手順を定めるのに対し、パターンはもっと上の階層にあります。
料理でたとえると、アルゴリズムはレシピです。手順どおりに進めれば同じものができます。一方パターンは設計図に近く、できあがりの姿は分かるが、作る順番は自分で決めるものです。同じパターンでも、プログラムが違えばコードは違います。
パターンの解説は、たいてい決まった形式で書かれています。何を解決するのか(目的)、なぜその解き方になるのか(動機)、部品どうしがどう関係するか(構造)、そして具体例のコードです。
この形式のおかげで、初めて見るパターンでも「どういう困りごとに効くのか」から読めるようになっています。名前から覚えようとすると難しく感じるので、困りごとの側から入るのがおすすめです。
パターンは20種類以上ありますが、実際によく出会うのは数種類です。しかも名前を覚えることに価値はありません。名前は他の人と話すときの符丁として役に立つだけで、効果を生むのは使いどころの判断だと編集部は考えています。
The pattern is not a specific piece of code, but a general concept for solving a particular problem.原文Refactoring.Guru「What is a design pattern?」 この内容の有効期限2027-02-17
条件分岐で書いた版とパターンで書いた版を用意して、4種類目を追加する作業を比べました。触る箇所は3箇所と1箇所でした。
デザインパターンの効果は、行数の増減では測れません。変更するときに何箇所触るかに出ます。そこで実際に数えました。
題材は支払い方法です。カード・銀行振込・コンビニ払いの3種類があり、それぞれ手数料の計算・画面の表示名・即時反映かどうかが違います。
素直に書くと、関数ごとに支払い方法の分岐が並びます。下のコードは読み飛ばして大丈夫です。見どころは、同じ3種類の分岐が3つの関数にくり返し現れることです。
function feeByIf(method, amount) {
if (method === 'card') return Math.round(amount * 0.036);
if (method === 'bank') return 220;
if (method === 'conveni') return amount < 10000 ? 190 : 300;
}
function labelByIf(method) {
if (method === 'card') return 'クレジットカード';
if (method === 'bank') return '銀行振込';
if (method === 'conveni') return 'コンビニ払い';
}
function isInstantByIf(method) {
if (method === 'card') return true;
if (method === 'bank') return false;
if (method === 'conveni') return false;
}
同じ内容を、支払い方法ごとに1つのまとまりとして書き直します。この形はStrategyパターンと呼ばれます。名前より、1つの支払い方法について知るべきことが1箇所に集まっている点を見てください。
const methods = {
card: { label: 'クレジットカード', instant: true, fee: (a) => Math.round(a * 0.036) },
bank: { label: '銀行振込', instant: false, fee: () => 220 },
conveni: { label: 'コンビニ払い', instant: false, fee: (a) => (a < 10000 ? 190 : 300) },
};
--- 既存3種類での手数料(12,000円の場合)--- クレジットカード: 条件分岐版=432円 / Strategy版=432円 銀行振込: 条件分岐版=220円 / Strategy版=220円 コンビニ払い: 条件分岐版=300円 / Strategy版=300円 --- 4種類目「後払い」を追加するとき、修正が要る箇所を数える --- 条件分岐版: 3箇所(feeByIf, labelByIf, isInstantByIf) Strategy版: 1箇所(methods に1件追加) 追加後の動作確認: 後払い の手数料=350円
差が出ました。3箇所と1箇所です。既存3種類での計算結果はどちらも同じなので、動きは変わっていません。変わったのは追加のときの手間だけです。
条件分岐版で3箇所になるのは、支払い方法の知識が3つの関数に分かれているからです。1箇所でも直し忘れると、手数料は計算できるのに表示名が出ないといった状態になります。
支払い方法が増えるほど差が開く。条件分岐版は関数の数だけ修正箇所が増える。
パターンの解説では、詳細に従って自分のプログラムの実情に合う形で実装することが勧められています。上のコードもクラスを使わないJavaScriptの書き方にしていて、教科書どおりの形ではありません。それで構いません。
You can follow the pattern details and implement a solution that suits the realities of your own program.原文Refactoring.Guru「What is a design pattern?」 この内容の有効期限2027-02-17
入れる合図は、同じ条件分岐が複数の関数に現れたときです。まだ現れていない変更を予想して先回りすると、手間だけが残ります。
デザインパターンで難しいのは、覚えることではなくいつ使うかの判断です。Fowlerも、解き方を知っているだけでなく、いつ使うか・どう育てていくかを理解することが必要だと述べています。
前の節の実験がそのまま合図になっています。同じ種類の条件分岐が、2箇所以上の関数に現れたときです。1箇所だけなら、まだ分岐のままで困りません。
逆に避けたいのは、まだ起きていない変更を見越してパターンを入れることです。間接的な構造が増えるぶん、読むときの負担は確実に増えます。その負担に見合う変更が来なければ、損をしただけになります。
判断に迷ったら、分岐のまま書いておいて構いません。2箇所目が現れた時点で書き直せば、そのときにはどう分けるべきかの情報も増えています。先に決めるより判断が正確になります。
設計の方向性についてはクリーンアーキテクチャの記事やドメイン駆動設計の記事でも扱っています。あちらは全体の構造の話で、こちらは局所的な書き方の話です。
パターン名を使って設計を説明できると、それらしく見えます。ただ実際に効いているのは変更したときに触る箇所が減っているかどうかだけです。名前が出てこなくても、変更が楽になっていればそれで足りると編集部は考えています。
A good knowledge of patterns: not just the solutions but also appreciating when to use them and how to evolve into them.原文Martin Fowler「Is Design Dead?」 この内容の有効期限2027-02-17
同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。
出品の仕組みを見る