[{"data":1,"prerenderedAt":4377},["ShallowReactive",2],{"blog-\u002Fblog\u002F003-design-patterns":3,"blog-all":4288,"timeline-design-patterns":4310},{"id":4,"title":5,"body":6,"date":4274,"description":4275,"draft":4276,"extension":4277,"image":4278,"meta":4279,"navigation":482,"no":473,"path":4280,"pinned":4276,"seo":4281,"stem":4282,"tags":4283,"updated":4278,"__hash__":4287},"blog\u002Fblog\u002F003-design-patterns.md","デザインパターン再入門 ― GoF の23パターンを TypeScript で読み直す",{"type":7,"value":8,"toc":4257},"minimark",[9,22,25,30,37,47,54,57,79,84,87,104,107,111,114,188,191,388,391,395,410,416,975,982,1142,1146,1151,1156,1594,1599,1610,1633,1637,1642,1647,2175,2183,2197,2201,2210,2215,2624,2630,2634,2639,2644,3283,3302,3315,3319,3324,3329,3963,3973,3977,3984,4007,4010,4025,4029,4036,4039,4160,4170,4174,4184,4187,4209,4213,4220,4225,4228,4253],[10,11,12,13,17,18,21],"p",{},"「この処理、前にも似たような形で書いたな」と感じたことはないでしょうか。設計の悩みの多くは、実は何度も繰り返し現れる ",[14,15,16],"strong",{},"よくある問題"," です。それに対する定番の解決策に名前を付けて共有できるようにしたものが ",[14,19,20],{},"デザインパターン"," です。",[10,23,24],{},"この記事では、1994年に出版された GoF（Gang of Four）の『デザインパターン』の23パターンを整理したうえで、現代の TypeScript でもよく使う6つを、動くコードとともに読み直します。",[26,27,29],"h2",{"id":28},"_1-デザインパターンとは","1. デザインパターンとは",[10,31,32,33,36],{},"デザインパターンは、",[14,34,35],{},"ある状況で繰り返し現れる設計上の問題と、その解決策の組"," に名前を付けたものです。",[38,39,40],"definition",{"title":20},[10,41,42,43,46],{},"特定の状況で繰り返し現れる設計上の問題に対して、何度も使える解決策の骨組みを、",[14,44,45],{},"名前・問題・解決策・結果（トレードオフ）"," の4つで記述したもの。",[10,48,49,50,53],{},"大事なのは、パターンが ",[14,51,52],{},"コードそのものではなく、設計の考え方"," だという点です。同じ Strategy パターンでも、言語やプロジェクトによって書き方は変わります。",[10,55,56],{},"パターンを知っていると、次のような利点があります。",[58,59,60,67,73],"ul",{},[61,62,63,66],"li",{},[14,64,65],{},"会話が短くなる","：「ここは Strategy にしよう」の一言で、構造と意図が伝わります。",[61,68,69,72],{},[14,70,71],{},"設計の選択肢が増える","：問題に出会ったとき、検討できる定番の解決策を思い出せます。",[61,74,75,78],{},[14,76,77],{},"既存コードが読みやすくなる","：フレームワークの中に隠れたパターンに気づけます。",[80,81,83],"h3",{"id":82},"gof-の2つの原則","GoF の2つの原則",[10,85,86],{},"GoF の本には、個々のパターンより大事とも言える2つの原則が書かれています。",[88,89,91],"key-equation",{"title":90},"GoF の設計原則",[92,93,94,99],"ol",{},[61,95,96],{},[14,97,98],{},"実装ではなく、インターフェースに対してプログラムせよ",[61,100,101],{},[14,102,103],{},"クラスの継承より、オブジェクトのコンポジション（組み合わせ）を優先せよ",[10,105,106],{},"後で紹介する Strategy、Adapter、Decorator は、どれもこの2つの原則を具体化したものと見ることができます。",[26,108,110],{"id":109},"_2-23パターンの全体像","2. 23パターンの全体像",[10,112,113],{},"GoF は、パターンを目的によって3つに分類しています。",[115,116,117,133],"table",{},[118,119,120],"thead",{},[121,122,123,127,130],"tr",{},[124,125,126],"th",{},"分類",[124,128,129],{},"目的",[124,131,132],{},"パターン",[134,135,136,154,171],"tbody",{},[121,137,138,144,151],{},[139,140,141],"td",{},[14,142,143],{},"生成",[139,145,146,147,150],{},"オブジェクトの ",[14,148,149],{},"作り方"," を切り離す",[139,152,153],{},"Abstract Factory、Builder、Factory Method、Prototype、Singleton",[121,155,156,161,168],{},[139,157,158],{},[14,159,160],{},"構造",[139,162,163,164,167],{},"クラスやオブジェクトの ",[14,165,166],{},"組み合わせ方"," を整える",[139,169,170],{},"Adapter、Bridge、Composite、Decorator、Facade、Flyweight、Proxy",[121,172,173,178,185],{},[139,174,175],{},[14,176,177],{},"振る舞い",[139,179,180,181,184],{},"オブジェクト間の ",[14,182,183],{},"責任の分担と連携"," を整理する",[139,186,187],{},"Chain of Responsibility、Command、Interpreter、Iterator、Mediator、Memento、Observer、State、Strategy、Template Method、Visitor",[10,189,190],{},"それぞれを一言で表すと、次のようになります。",[115,192,193,202],{},[118,194,195],{},[121,196,197,199],{},[124,198,132],{},[124,200,201],{},"一言でいうと",[134,203,204,212,220,228,236,244,252,260,268,276,284,292,300,308,316,324,332,340,348,356,364,372,380],{},[121,205,206,209],{},[139,207,208],{},"Abstract Factory",[139,210,211],{},"関連する部品の「一式」をまとめて作る窓口を用意する",[121,213,214,217],{},[139,215,216],{},"Builder",[139,218,219],{},"複雑なオブジェクトを、段階的に組み立てる",[121,221,222,225],{},[139,223,224],{},"Factory Method",[139,226,227],{},"どのクラスを作るかを、サブクラスに決めさせる",[121,229,230,233],{},[139,231,232],{},"Prototype",[139,234,235],{},"既存のオブジェクトを複製して新しいものを作る",[121,237,238,241],{},[139,239,240],{},"Singleton",[139,242,243],{},"インスタンスが1つだけであることを保証する",[121,245,246,249],{},[139,247,248],{},"Adapter",[139,250,251],{},"合わないインターフェースを、期待する形に変換する",[121,253,254,257],{},[139,255,256],{},"Bridge",[139,258,259],{},"「抽象」と「実装」を別々に拡張できるよう分離する",[121,261,262,265],{},[139,263,264],{},"Composite",[139,266,267],{},"部分と全体（木構造）を同じように扱う",[121,269,270,273],{},[139,271,272],{},"Decorator",[139,274,275],{},"元のオブジェクトを包んで、機能を後から重ねる",[121,277,278,281],{},[139,279,280],{},"Facade",[139,282,283],{},"複雑なサブシステムに、簡単な窓口を1つ用意する",[121,285,286,289],{},[139,287,288],{},"Flyweight",[139,290,291],{},"共有できる状態を共有して、大量のオブジェクトを軽くする",[121,293,294,297],{},[139,295,296],{},"Proxy",[139,298,299],{},"本物の代わりに立つ代理を置き、アクセスを制御する",[121,301,302,305],{},[139,303,304],{},"Chain of Responsibility",[139,306,307],{},"処理できる者が見つかるまで、要求を順に渡していく",[121,309,310,313],{},[139,311,312],{},"Command",[139,314,315],{},"「操作」をオブジェクトにして、記録・取り消し・遅延実行できるようにする",[121,317,318,321],{},[139,319,320],{},"Interpreter",[139,322,323],{},"簡単な言語の文法を、クラスの構造で表して解釈する",[121,325,326,329],{},[139,327,328],{},"Iterator",[139,330,331],{},"内部構造を見せずに、要素を順にたどる",[121,333,334,337],{},[139,335,336],{},"Mediator",[139,338,339],{},"オブジェクト同士の直接のやり取りを、仲介役に集める",[121,341,342,345],{},[139,343,344],{},"Memento",[139,346,347],{},"内部状態を保存し、後で元に戻せるようにする",[121,349,350,353],{},[139,351,352],{},"Observer",[139,354,355],{},"状態の変化を、登録された相手全員に通知する",[121,357,358,361],{},[139,359,360],{},"State",[139,362,363],{},"状態ごとに振る舞いをクラスに分け、状態の切り替えで振る舞いを変える",[121,365,366,369],{},[139,367,368],{},"Strategy",[139,370,371],{},"アルゴリズムを部品にして、差し替えられるようにする",[121,373,374,377],{},[139,375,376],{},"Template Method",[139,378,379],{},"処理の流れを親クラスで決め、細部をサブクラスに任せる",[121,381,382,385],{},[139,383,384],{},"Visitor",[139,386,387],{},"データ構造を変えずに、それに対する新しい処理を追加する",[10,389,390],{},"全部を覚える必要はありません。以下では、現代の TypeScript でも出番の多い6つを詳しく見ていきます。",[26,392,394],{"id":393},"_3-strategyアルゴリズムを差し替える","3. Strategy：アルゴリズムを差し替える",[10,396,397,400,401,405,406,409],{},[14,398,399],{},"問題","：割引の計算方法が「割引なし」「10%引き」「500円引き」…と増えるたびに、",[402,403,404],"code",{},"if"," や ",[402,407,408],{},"switch"," の分岐が膨らんでいく。",[10,411,412,415],{},[14,413,414],{},"解決策","：計算方法を共通のインターフェースを持つ部品（戦略）に切り出し、使う側は「どの戦略か」を知らずに呼び出す。",[417,418,424],"pre",{"className":419,"code":420,"filename":421,"language":422,"meta":423,"style":423},"language-ts shiki shiki-themes github-light github-dark","interface DiscountStrategy {\n  apply(price: number): number\n}\n\nconst noDiscount: DiscountStrategy = { apply: (price) => price }\nconst percentOff = (rate: number): DiscountStrategy => ({\n  apply: (price) => Math.round(price * (1 - rate)),\n})\nconst amountOff = (amount: number): DiscountStrategy => ({\n  apply: (price) => Math.max(0, price - amount),\n})\n\nclass Checkout {\n  constructor(private strategy: DiscountStrategy = noDiscount) {}\n\n  setStrategy(strategy: DiscountStrategy) {\n    this.strategy = strategy\n  }\n\n  total(prices: number[]): number {\n    const subtotal = prices.reduce((sum, p) => sum + p, 0)\n    return this.strategy.apply(subtotal)\n  }\n}\n\nconst checkout = new Checkout()\ncheckout.total([1200, 800])          \u002F\u002F 2000\ncheckout.setStrategy(percentOff(0.1))\ncheckout.total([1200, 800])          \u002F\u002F 1800\ncheckout.setStrategy(amountOff(500))\ncheckout.total([1200, 800])          \u002F\u002F 1500\n","strategy.ts","ts","",[402,425,426,443,471,477,484,520,552,588,594,623,655,660,665,676,699,704,722,737,743,748,772,818,835,840,845,850,868,895,916,936,955],{"__ignoreMap":423},[427,428,431,435,439],"span",{"class":429,"line":430},"line",1,[427,432,434],{"class":433},"szBVR","interface",[427,436,438],{"class":437},"sScJk"," DiscountStrategy",[427,440,442],{"class":441},"sVt8B"," {\n",[427,444,446,449,452,456,459,463,466,468],{"class":429,"line":445},2,[427,447,448],{"class":437},"  apply",[427,450,451],{"class":441},"(",[427,453,455],{"class":454},"s4XuR","price",[427,457,458],{"class":433},":",[427,460,462],{"class":461},"sj4cs"," number",[427,464,465],{"class":441},")",[427,467,458],{"class":433},[427,469,470],{"class":461}," number\n",[427,472,474],{"class":429,"line":473},3,[427,475,476],{"class":441},"}\n",[427,478,480],{"class":429,"line":479},4,[427,481,483],{"emptyLinePlaceholder":482},true,"\n",[427,485,487,490,493,495,497,500,503,506,509,511,514,517],{"class":429,"line":486},5,[427,488,489],{"class":433},"const",[427,491,492],{"class":461}," noDiscount",[427,494,458],{"class":433},[427,496,438],{"class":437},[427,498,499],{"class":433}," =",[427,501,502],{"class":441}," { ",[427,504,505],{"class":437},"apply",[427,507,508],{"class":441},": (",[427,510,455],{"class":454},[427,512,513],{"class":441},") ",[427,515,516],{"class":433},"=>",[427,518,519],{"class":441}," price }\n",[427,521,523,525,528,530,533,536,538,540,542,544,546,549],{"class":429,"line":522},6,[427,524,489],{"class":433},[427,526,527],{"class":437}," percentOff",[427,529,499],{"class":433},[427,531,532],{"class":441}," (",[427,534,535],{"class":454},"rate",[427,537,458],{"class":433},[427,539,462],{"class":461},[427,541,465],{"class":441},[427,543,458],{"class":433},[427,545,438],{"class":437},[427,547,548],{"class":433}," =>",[427,550,551],{"class":441}," ({\n",[427,553,555,557,559,561,563,565,568,571,574,577,579,582,585],{"class":429,"line":554},7,[427,556,448],{"class":437},[427,558,508],{"class":441},[427,560,455],{"class":454},[427,562,513],{"class":441},[427,564,516],{"class":433},[427,566,567],{"class":441}," Math.",[427,569,570],{"class":437},"round",[427,572,573],{"class":441},"(price ",[427,575,576],{"class":433},"*",[427,578,532],{"class":441},[427,580,581],{"class":461},"1",[427,583,584],{"class":433}," -",[427,586,587],{"class":441}," rate)),\n",[427,589,591],{"class":429,"line":590},8,[427,592,593],{"class":441},"})\n",[427,595,597,599,602,604,606,609,611,613,615,617,619,621],{"class":429,"line":596},9,[427,598,489],{"class":433},[427,600,601],{"class":437}," amountOff",[427,603,499],{"class":433},[427,605,532],{"class":441},[427,607,608],{"class":454},"amount",[427,610,458],{"class":433},[427,612,462],{"class":461},[427,614,465],{"class":441},[427,616,458],{"class":433},[427,618,438],{"class":437},[427,620,548],{"class":433},[427,622,551],{"class":441},[427,624,626,628,630,632,634,636,638,641,643,646,649,652],{"class":429,"line":625},10,[427,627,448],{"class":437},[427,629,508],{"class":441},[427,631,455],{"class":454},[427,633,513],{"class":441},[427,635,516],{"class":433},[427,637,567],{"class":441},[427,639,640],{"class":437},"max",[427,642,451],{"class":441},[427,644,645],{"class":461},"0",[427,647,648],{"class":441},", price ",[427,650,651],{"class":433},"-",[427,653,654],{"class":441}," amount),\n",[427,656,658],{"class":429,"line":657},11,[427,659,593],{"class":441},[427,661,663],{"class":429,"line":662},12,[427,664,483],{"emptyLinePlaceholder":482},[427,666,668,671,674],{"class":429,"line":667},13,[427,669,670],{"class":433},"class",[427,672,673],{"class":437}," Checkout",[427,675,442],{"class":441},[427,677,679,682,684,687,690,692,694,696],{"class":429,"line":678},14,[427,680,681],{"class":433},"  constructor",[427,683,451],{"class":441},[427,685,686],{"class":433},"private",[427,688,689],{"class":454}," strategy",[427,691,458],{"class":433},[427,693,438],{"class":437},[427,695,499],{"class":433},[427,697,698],{"class":441}," noDiscount) {}\n",[427,700,702],{"class":429,"line":701},15,[427,703,483],{"emptyLinePlaceholder":482},[427,705,707,710,712,715,717,719],{"class":429,"line":706},16,[427,708,709],{"class":437},"  setStrategy",[427,711,451],{"class":441},[427,713,714],{"class":454},"strategy",[427,716,458],{"class":433},[427,718,438],{"class":437},[427,720,721],{"class":441},") {\n",[427,723,725,728,731,734],{"class":429,"line":724},17,[427,726,727],{"class":461},"    this",[427,729,730],{"class":441},".strategy ",[427,732,733],{"class":433},"=",[427,735,736],{"class":441}," strategy\n",[427,738,740],{"class":429,"line":739},18,[427,741,742],{"class":441},"  }\n",[427,744,746],{"class":429,"line":745},19,[427,747,483],{"emptyLinePlaceholder":482},[427,749,751,754,756,759,761,763,766,768,770],{"class":429,"line":750},20,[427,752,753],{"class":437},"  total",[427,755,451],{"class":441},[427,757,758],{"class":454},"prices",[427,760,458],{"class":433},[427,762,462],{"class":461},[427,764,765],{"class":441},"[])",[427,767,458],{"class":433},[427,769,462],{"class":461},[427,771,442],{"class":441},[427,773,775,778,781,783,786,789,792,795,798,800,802,804,807,810,813,815],{"class":429,"line":774},21,[427,776,777],{"class":433},"    const",[427,779,780],{"class":461}," subtotal",[427,782,499],{"class":433},[427,784,785],{"class":441}," prices.",[427,787,788],{"class":437},"reduce",[427,790,791],{"class":441},"((",[427,793,794],{"class":454},"sum",[427,796,797],{"class":441},", ",[427,799,10],{"class":454},[427,801,513],{"class":441},[427,803,516],{"class":433},[427,805,806],{"class":441}," sum ",[427,808,809],{"class":433},"+",[427,811,812],{"class":441}," p, ",[427,814,645],{"class":461},[427,816,817],{"class":441},")\n",[427,819,821,824,827,830,832],{"class":429,"line":820},22,[427,822,823],{"class":433},"    return",[427,825,826],{"class":461}," this",[427,828,829],{"class":441},".strategy.",[427,831,505],{"class":437},[427,833,834],{"class":441},"(subtotal)\n",[427,836,838],{"class":429,"line":837},23,[427,839,742],{"class":441},[427,841,843],{"class":429,"line":842},24,[427,844,476],{"class":441},[427,846,848],{"class":429,"line":847},25,[427,849,483],{"emptyLinePlaceholder":482},[427,851,853,855,858,860,863,865],{"class":429,"line":852},26,[427,854,489],{"class":433},[427,856,857],{"class":461}," checkout",[427,859,499],{"class":433},[427,861,862],{"class":433}," new",[427,864,673],{"class":437},[427,866,867],{"class":441},"()\n",[427,869,871,874,877,880,883,885,888,891],{"class":429,"line":870},27,[427,872,873],{"class":441},"checkout.",[427,875,876],{"class":437},"total",[427,878,879],{"class":441},"([",[427,881,882],{"class":461},"1200",[427,884,797],{"class":441},[427,886,887],{"class":461},"800",[427,889,890],{"class":441},"])          ",[427,892,894],{"class":893},"sJ8bj","\u002F\u002F 2000\n",[427,896,898,900,903,905,908,910,913],{"class":429,"line":897},28,[427,899,873],{"class":441},[427,901,902],{"class":437},"setStrategy",[427,904,451],{"class":441},[427,906,907],{"class":437},"percentOff",[427,909,451],{"class":441},[427,911,912],{"class":461},"0.1",[427,914,915],{"class":441},"))\n",[427,917,919,921,923,925,927,929,931,933],{"class":429,"line":918},29,[427,920,873],{"class":441},[427,922,876],{"class":437},[427,924,879],{"class":441},[427,926,882],{"class":461},[427,928,797],{"class":441},[427,930,887],{"class":461},[427,932,890],{"class":441},[427,934,935],{"class":893},"\u002F\u002F 1800\n",[427,937,939,941,943,945,948,950,953],{"class":429,"line":938},30,[427,940,873],{"class":441},[427,942,902],{"class":437},[427,944,451],{"class":441},[427,946,947],{"class":437},"amountOff",[427,949,451],{"class":441},[427,951,952],{"class":461},"500",[427,954,915],{"class":441},[427,956,958,960,962,964,966,968,970,972],{"class":429,"line":957},31,[427,959,873],{"class":441},[427,961,876],{"class":437},[427,963,879],{"class":441},[427,965,882],{"class":461},[427,967,797],{"class":441},[427,969,887],{"class":461},[427,971,890],{"class":441},[427,973,974],{"class":893},"\u002F\u002F 1500\n",[10,976,977,978,981],{},"新しい割引方法を追加しても、",[402,979,980],{},"Checkout"," には手を入れずに済みます。「拡張に対して開いていて、修正に対して閉じている」（開放閉鎖の原則）の典型例です。",[983,984,986,989,1136],"tip",{"title":985},"関数が第一級なら、関数で十分",[10,987,988],{},"TypeScript では関数をそのまま値として渡せます。戦略が1つのメソッドだけなら、インターフェースやクラスを作らずに関数を渡すのが自然です。",[417,990,992],{"className":419,"code":991,"language":422,"meta":423,"style":423},"type Discount = (price: number) => number\n\nconst totalWith = (prices: number[], discount: Discount = (p) => p) =>\n  discount(prices.reduce((sum, p) => sum + p, 0))\n\ntotalWith([1200, 800], (p) => p * 0.8) \u002F\u002F 1600\n",[402,993,994,1018,1022,1065,1097,1101],{"__ignoreMap":423},[427,995,996,999,1002,1004,1006,1008,1010,1012,1014,1016],{"class":429,"line":430},[427,997,998],{"class":433},"type",[427,1000,1001],{"class":437}," Discount",[427,1003,499],{"class":433},[427,1005,532],{"class":441},[427,1007,455],{"class":454},[427,1009,458],{"class":433},[427,1011,462],{"class":461},[427,1013,513],{"class":441},[427,1015,516],{"class":433},[427,1017,470],{"class":461},[427,1019,1020],{"class":429,"line":445},[427,1021,483],{"emptyLinePlaceholder":482},[427,1023,1024,1026,1029,1031,1033,1035,1037,1039,1042,1045,1047,1049,1051,1053,1055,1057,1059,1062],{"class":429,"line":473},[427,1025,489],{"class":433},[427,1027,1028],{"class":437}," totalWith",[427,1030,499],{"class":433},[427,1032,532],{"class":441},[427,1034,758],{"class":454},[427,1036,458],{"class":433},[427,1038,462],{"class":461},[427,1040,1041],{"class":441},"[], ",[427,1043,1044],{"class":437},"discount",[427,1046,458],{"class":433},[427,1048,1001],{"class":437},[427,1050,499],{"class":433},[427,1052,532],{"class":441},[427,1054,10],{"class":454},[427,1056,513],{"class":441},[427,1058,516],{"class":433},[427,1060,1061],{"class":441}," p) ",[427,1063,1064],{"class":433},"=>\n",[427,1066,1067,1070,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091,1093,1095],{"class":429,"line":479},[427,1068,1069],{"class":437},"  discount",[427,1071,1072],{"class":441},"(prices.",[427,1074,788],{"class":437},[427,1076,791],{"class":441},[427,1078,794],{"class":454},[427,1080,797],{"class":441},[427,1082,10],{"class":454},[427,1084,513],{"class":441},[427,1086,516],{"class":433},[427,1088,806],{"class":441},[427,1090,809],{"class":433},[427,1092,812],{"class":441},[427,1094,645],{"class":461},[427,1096,915],{"class":441},[427,1098,1099],{"class":429,"line":486},[427,1100,483],{"emptyLinePlaceholder":482},[427,1102,1103,1106,1108,1110,1112,1114,1117,1119,1121,1123,1126,1128,1131,1133],{"class":429,"line":522},[427,1104,1105],{"class":437},"totalWith",[427,1107,879],{"class":441},[427,1109,882],{"class":461},[427,1111,797],{"class":441},[427,1113,887],{"class":461},[427,1115,1116],{"class":441},"], (",[427,1118,10],{"class":454},[427,1120,513],{"class":441},[427,1122,516],{"class":433},[427,1124,1125],{"class":441}," p ",[427,1127,576],{"class":433},[427,1129,1130],{"class":461}," 0.8",[427,1132,513],{"class":441},[427,1134,1135],{"class":893},"\u002F\u002F 1600\n",[10,1137,1138,1141],{},[402,1139,1140],{},"Array.prototype.sort"," に比較関数を渡すのも、Strategy パターンそのものです。",[26,1143,1145],{"id":1144},"_4-observer変化を購読する","4. Observer：変化を購読する",[10,1147,1148,1150],{},[14,1149,399],{},"：カートの中身が変わったら、ヘッダーのバッジ、合計金額、おすすめ商品…と、更新したい場所がいくつもある。カートの処理が、それらをすべて知っているのは避けたい。",[10,1152,1153,1155],{},[14,1154,414],{},"：変化を通知する側（Subject）は、購読者（Observer）のリストだけを持ち、変化があったら全員に知らせる。誰が何をするかは知らない。",[417,1157,1160],{"className":419,"code":1158,"filename":1159,"language":422,"meta":423,"style":423},"type Listener\u003CT> = (payload: T) => void\n\nclass Subject\u003CT> {\n  private listeners = new Set\u003CListener\u003CT>>()\n\n  subscribe(listener: Listener\u003CT>): () => void {\n    this.listeners.add(listener)\n    return () => this.listeners.delete(listener) \u002F\u002F 購読をやめる関数を返す\n  }\n\n  notify(payload: T): void {\n    for (const listener of this.listeners) listener(payload)\n  }\n}\n\ninterface CartEvent {\n  itemCount: number\n  total: number\n}\n\nconst cartChanged = new Subject\u003CCartEvent>()\n\nconst unsubscribeBadge = cartChanged.subscribe(({ itemCount }) =>\n  console.log(`バッジ表示を ${itemCount} に更新`)\n)\ncartChanged.subscribe(({ total }) => console.log(`合計 ${total} 円を表示`))\n\ncartChanged.notify({ itemCount: 2, total: 2000 })\n\u002F\u002F → バッジ表示を 2 に更新\n\u002F\u002F → 合計 2000 円を表示\n\nunsubscribeBadge()\ncartChanged.notify({ itemCount: 3, total: 3500 })\n\u002F\u002F → 合計 3500 円を表示（バッジはもう購読していない）\n","observer.ts",[402,1161,1162,1197,1201,1215,1242,1246,1279,1292,1313,1317,1321,1342,1367,1371,1375,1379,1388,1397,1405,1409,1413,1434,1438,1464,1485,1489,1521,1525,1547,1552,1557,1561,1569,1588],{"__ignoreMap":423},[427,1163,1164,1166,1169,1172,1175,1178,1180,1182,1185,1187,1190,1192,1194],{"class":429,"line":430},[427,1165,998],{"class":433},[427,1167,1168],{"class":437}," Listener",[427,1170,1171],{"class":441},"\u003C",[427,1173,1174],{"class":437},"T",[427,1176,1177],{"class":441},"> ",[427,1179,733],{"class":433},[427,1181,532],{"class":441},[427,1183,1184],{"class":454},"payload",[427,1186,458],{"class":433},[427,1188,1189],{"class":437}," T",[427,1191,513],{"class":441},[427,1193,516],{"class":433},[427,1195,1196],{"class":461}," void\n",[427,1198,1199],{"class":429,"line":445},[427,1200,483],{"emptyLinePlaceholder":482},[427,1202,1203,1205,1208,1210,1212],{"class":429,"line":473},[427,1204,670],{"class":433},[427,1206,1207],{"class":437}," Subject",[427,1209,1171],{"class":441},[427,1211,1174],{"class":437},[427,1213,1214],{"class":441},"> {\n",[427,1216,1217,1220,1223,1225,1227,1230,1232,1235,1237,1239],{"class":429,"line":479},[427,1218,1219],{"class":433},"  private",[427,1221,1222],{"class":454}," listeners",[427,1224,499],{"class":433},[427,1226,862],{"class":433},[427,1228,1229],{"class":437}," Set",[427,1231,1171],{"class":441},[427,1233,1234],{"class":437},"Listener",[427,1236,1171],{"class":441},[427,1238,1174],{"class":437},[427,1240,1241],{"class":441},">>()\n",[427,1243,1244],{"class":429,"line":486},[427,1245,483],{"emptyLinePlaceholder":482},[427,1247,1248,1251,1253,1256,1258,1260,1262,1264,1267,1269,1272,1274,1277],{"class":429,"line":522},[427,1249,1250],{"class":437},"  subscribe",[427,1252,451],{"class":441},[427,1254,1255],{"class":454},"listener",[427,1257,458],{"class":433},[427,1259,1168],{"class":437},[427,1261,1171],{"class":441},[427,1263,1174],{"class":437},[427,1265,1266],{"class":441},">)",[427,1268,458],{"class":433},[427,1270,1271],{"class":441}," () ",[427,1273,516],{"class":433},[427,1275,1276],{"class":461}," void",[427,1278,442],{"class":441},[427,1280,1281,1283,1286,1289],{"class":429,"line":554},[427,1282,727],{"class":461},[427,1284,1285],{"class":441},".listeners.",[427,1287,1288],{"class":437},"add",[427,1290,1291],{"class":441},"(listener)\n",[427,1293,1294,1296,1298,1300,1302,1304,1307,1310],{"class":429,"line":590},[427,1295,823],{"class":433},[427,1297,1271],{"class":441},[427,1299,516],{"class":433},[427,1301,826],{"class":461},[427,1303,1285],{"class":441},[427,1305,1306],{"class":437},"delete",[427,1308,1309],{"class":441},"(listener) ",[427,1311,1312],{"class":893},"\u002F\u002F 購読をやめる関数を返す\n",[427,1314,1315],{"class":429,"line":596},[427,1316,742],{"class":441},[427,1318,1319],{"class":429,"line":625},[427,1320,483],{"emptyLinePlaceholder":482},[427,1322,1323,1326,1328,1330,1332,1334,1336,1338,1340],{"class":429,"line":657},[427,1324,1325],{"class":437},"  notify",[427,1327,451],{"class":441},[427,1329,1184],{"class":454},[427,1331,458],{"class":433},[427,1333,1189],{"class":437},[427,1335,465],{"class":441},[427,1337,458],{"class":433},[427,1339,1276],{"class":461},[427,1341,442],{"class":441},[427,1343,1344,1347,1349,1351,1354,1357,1359,1362,1364],{"class":429,"line":662},[427,1345,1346],{"class":433},"    for",[427,1348,532],{"class":441},[427,1350,489],{"class":433},[427,1352,1353],{"class":461}," listener",[427,1355,1356],{"class":433}," of",[427,1358,826],{"class":461},[427,1360,1361],{"class":441},".listeners) ",[427,1363,1255],{"class":437},[427,1365,1366],{"class":441},"(payload)\n",[427,1368,1369],{"class":429,"line":667},[427,1370,742],{"class":441},[427,1372,1373],{"class":429,"line":678},[427,1374,476],{"class":441},[427,1376,1377],{"class":429,"line":701},[427,1378,483],{"emptyLinePlaceholder":482},[427,1380,1381,1383,1386],{"class":429,"line":706},[427,1382,434],{"class":433},[427,1384,1385],{"class":437}," CartEvent",[427,1387,442],{"class":441},[427,1389,1390,1393,1395],{"class":429,"line":724},[427,1391,1392],{"class":454},"  itemCount",[427,1394,458],{"class":433},[427,1396,470],{"class":461},[427,1398,1399,1401,1403],{"class":429,"line":739},[427,1400,753],{"class":454},[427,1402,458],{"class":433},[427,1404,470],{"class":461},[427,1406,1407],{"class":429,"line":745},[427,1408,476],{"class":441},[427,1410,1411],{"class":429,"line":750},[427,1412,483],{"emptyLinePlaceholder":482},[427,1414,1415,1417,1420,1422,1424,1426,1428,1431],{"class":429,"line":774},[427,1416,489],{"class":433},[427,1418,1419],{"class":461}," cartChanged",[427,1421,499],{"class":433},[427,1423,862],{"class":433},[427,1425,1207],{"class":437},[427,1427,1171],{"class":441},[427,1429,1430],{"class":437},"CartEvent",[427,1432,1433],{"class":441},">()\n",[427,1435,1436],{"class":429,"line":820},[427,1437,483],{"emptyLinePlaceholder":482},[427,1439,1440,1442,1445,1447,1450,1453,1456,1459,1462],{"class":429,"line":837},[427,1441,489],{"class":433},[427,1443,1444],{"class":461}," unsubscribeBadge",[427,1446,499],{"class":433},[427,1448,1449],{"class":441}," cartChanged.",[427,1451,1452],{"class":437},"subscribe",[427,1454,1455],{"class":441},"(({ ",[427,1457,1458],{"class":454},"itemCount",[427,1460,1461],{"class":441}," }) ",[427,1463,1064],{"class":433},[427,1465,1466,1469,1472,1474,1478,1480,1483],{"class":429,"line":842},[427,1467,1468],{"class":441},"  console.",[427,1470,1471],{"class":437},"log",[427,1473,451],{"class":441},[427,1475,1477],{"class":1476},"sZZnC","`バッジ表示を ${",[427,1479,1458],{"class":441},[427,1481,1482],{"class":1476},"} に更新`",[427,1484,817],{"class":441},[427,1486,1487],{"class":429,"line":847},[427,1488,817],{"class":441},[427,1490,1491,1494,1496,1498,1500,1502,1504,1507,1509,1511,1514,1516,1519],{"class":429,"line":852},[427,1492,1493],{"class":441},"cartChanged.",[427,1495,1452],{"class":437},[427,1497,1455],{"class":441},[427,1499,876],{"class":454},[427,1501,1461],{"class":441},[427,1503,516],{"class":433},[427,1505,1506],{"class":441}," console.",[427,1508,1471],{"class":437},[427,1510,451],{"class":441},[427,1512,1513],{"class":1476},"`合計 ${",[427,1515,876],{"class":441},[427,1517,1518],{"class":1476},"} 円を表示`",[427,1520,915],{"class":441},[427,1522,1523],{"class":429,"line":870},[427,1524,483],{"emptyLinePlaceholder":482},[427,1526,1527,1529,1532,1535,1538,1541,1544],{"class":429,"line":897},[427,1528,1493],{"class":441},[427,1530,1531],{"class":437},"notify",[427,1533,1534],{"class":441},"({ itemCount: ",[427,1536,1537],{"class":461},"2",[427,1539,1540],{"class":441},", total: ",[427,1542,1543],{"class":461},"2000",[427,1545,1546],{"class":441}," })\n",[427,1548,1549],{"class":429,"line":918},[427,1550,1551],{"class":893},"\u002F\u002F → バッジ表示を 2 に更新\n",[427,1553,1554],{"class":429,"line":938},[427,1555,1556],{"class":893},"\u002F\u002F → 合計 2000 円を表示\n",[427,1558,1559],{"class":429,"line":957},[427,1560,483],{"emptyLinePlaceholder":482},[427,1562,1564,1567],{"class":429,"line":1563},32,[427,1565,1566],{"class":437},"unsubscribeBadge",[427,1568,867],{"class":441},[427,1570,1572,1574,1576,1578,1581,1583,1586],{"class":429,"line":1571},33,[427,1573,1493],{"class":441},[427,1575,1531],{"class":437},[427,1577,1534],{"class":441},[427,1579,1580],{"class":461},"3",[427,1582,1540],{"class":441},[427,1584,1585],{"class":461},"3500",[427,1587,1546],{"class":441},[427,1589,1591],{"class":429,"line":1590},34,[427,1592,1593],{"class":893},"\u002F\u002F → 合計 3500 円を表示（バッジはもう購読していない）\n",[10,1595,1596,1598],{},[402,1597,1452],{}," が「購読をやめる関数」を返すようにしておくと、後片付けの書き忘れを防ぎやすくなります。",[1600,1601,1603],"warning",{"title":1602},"購読の解除忘れに注意",[10,1604,1605,1606,1609],{},"画面の部品が購読したまま破棄されると、その部品はメモリから解放されず、古い部品が通知を受け取り続けます（メモリリーク）。Vue なら ",[402,1607,1608],{},"onBeforeUnmount"," で必ず解除しましょう。",[10,1611,1612,1613,1616,1617,1620,1621,1632],{},"Observer は、身の回りのいたるところにあります。DOM の ",[402,1614,1615],{},"addEventListener","、Node.js の ",[402,1618,1619],{},"EventEmitter","、RxJS、そして ",[14,1622,1623,1624,1627,1628,1631],{},"Vue のリアクティビティ（",[402,1625,1626],{},"ref"," と ",[402,1629,1630],{},"watch","）"," も、この考え方の上に成り立っています。",[26,1634,1636],{"id":1635},"_5-factory-method何を作るかをサブクラスに任せる","5. Factory Method：何を作るかをサブクラスに任せる",[10,1638,1639,1641],{},[14,1640,399],{},"：レポートを CSV と Markdown の両方で出力したい。「見出しを書いて、行を順に書く」という流れは同じなのに、出力形式ごとにほぼ同じ処理を書いてしまう。",[10,1643,1644,1646],{},[14,1645,414],{},"：共通の流れは親クラスに書き、「どの整形係（Formatter）を作るか」だけをサブクラスに決めさせる。",[417,1648,1651],{"className":419,"code":1649,"filename":1650,"language":422,"meta":423,"style":423},"interface Row {\n  name: string\n  score: number\n}\n\ninterface Formatter {\n  header(): string\n  row(row: Row): string\n}\n\nabstract class ReportExporter {\n  \u002F\u002F サブクラスが「どの Formatter を作るか」を決める（Factory Method）\n  protected abstract createFormatter(): Formatter\n\n  export(rows: Row[]): string {\n    const formatter = this.createFormatter()\n    return [formatter.header(), ...rows.map((r) => formatter.row(r))].join('\\n')\n  }\n}\n\nclass CsvExporter extends ReportExporter {\n  protected createFormatter(): Formatter {\n    return {\n      header: () => 'name,score',\n      row: (r) => `${r.name},${r.score}`,\n    }\n  }\n}\n\nclass MarkdownExporter extends ReportExporter {\n  protected createFormatter(): Formatter {\n    return {\n      header: () => '| name | score |\\n|---|---|',\n      row: (r) => `| ${r.name} | ${r.score} |`,\n    }\n  }\n}\n\nconst rows: Row[] = [{ name: 'Alice', score: 92 }, { name: 'Bob', score: 85 }]\nnew CsvExporter().export(rows)\n\u002F\u002F name,score\n\u002F\u002F Alice,92\n\u002F\u002F Bob,85\n","factory-method.ts",[402,1652,1653,1662,1672,1681,1685,1689,1698,1710,1730,1734,1738,1751,1756,1774,1778,1801,1820,1874,1878,1882,1886,1900,1914,1920,1936,1974,1979,1983,1987,1991,2004,2018,2024,2042,2077,2082,2087,2092,2097,2140,2157,2163,2169],{"__ignoreMap":423},[427,1654,1655,1657,1660],{"class":429,"line":430},[427,1656,434],{"class":433},[427,1658,1659],{"class":437}," Row",[427,1661,442],{"class":441},[427,1663,1664,1667,1669],{"class":429,"line":445},[427,1665,1666],{"class":454},"  name",[427,1668,458],{"class":433},[427,1670,1671],{"class":461}," string\n",[427,1673,1674,1677,1679],{"class":429,"line":473},[427,1675,1676],{"class":454},"  score",[427,1678,458],{"class":433},[427,1680,470],{"class":461},[427,1682,1683],{"class":429,"line":479},[427,1684,476],{"class":441},[427,1686,1687],{"class":429,"line":486},[427,1688,483],{"emptyLinePlaceholder":482},[427,1690,1691,1693,1696],{"class":429,"line":522},[427,1692,434],{"class":433},[427,1694,1695],{"class":437}," Formatter",[427,1697,442],{"class":441},[427,1699,1700,1703,1706,1708],{"class":429,"line":554},[427,1701,1702],{"class":437},"  header",[427,1704,1705],{"class":441},"()",[427,1707,458],{"class":433},[427,1709,1671],{"class":461},[427,1711,1712,1715,1717,1720,1722,1724,1726,1728],{"class":429,"line":590},[427,1713,1714],{"class":437},"  row",[427,1716,451],{"class":441},[427,1718,1719],{"class":454},"row",[427,1721,458],{"class":433},[427,1723,1659],{"class":437},[427,1725,465],{"class":441},[427,1727,458],{"class":433},[427,1729,1671],{"class":461},[427,1731,1732],{"class":429,"line":596},[427,1733,476],{"class":441},[427,1735,1736],{"class":429,"line":625},[427,1737,483],{"emptyLinePlaceholder":482},[427,1739,1740,1743,1746,1749],{"class":429,"line":657},[427,1741,1742],{"class":433},"abstract",[427,1744,1745],{"class":433}," class",[427,1747,1748],{"class":437}," ReportExporter",[427,1750,442],{"class":441},[427,1752,1753],{"class":429,"line":662},[427,1754,1755],{"class":893},"  \u002F\u002F サブクラスが「どの Formatter を作るか」を決める（Factory Method）\n",[427,1757,1758,1761,1764,1767,1769,1771],{"class":429,"line":667},[427,1759,1760],{"class":433},"  protected",[427,1762,1763],{"class":433}," abstract",[427,1765,1766],{"class":437}," createFormatter",[427,1768,1705],{"class":441},[427,1770,458],{"class":433},[427,1772,1773],{"class":437}," Formatter\n",[427,1775,1776],{"class":429,"line":678},[427,1777,483],{"emptyLinePlaceholder":482},[427,1779,1780,1783,1785,1788,1790,1792,1794,1796,1799],{"class":429,"line":701},[427,1781,1782],{"class":437},"  export",[427,1784,451],{"class":441},[427,1786,1787],{"class":454},"rows",[427,1789,458],{"class":433},[427,1791,1659],{"class":437},[427,1793,765],{"class":441},[427,1795,458],{"class":433},[427,1797,1798],{"class":461}," string",[427,1800,442],{"class":441},[427,1802,1803,1805,1808,1810,1812,1815,1818],{"class":429,"line":706},[427,1804,777],{"class":433},[427,1806,1807],{"class":461}," formatter",[427,1809,499],{"class":433},[427,1811,826],{"class":461},[427,1813,1814],{"class":441},".",[427,1816,1817],{"class":437},"createFormatter",[427,1819,867],{"class":441},[427,1821,1822,1824,1827,1830,1833,1836,1839,1842,1844,1847,1849,1851,1854,1856,1859,1862,1864,1867,1870,1872],{"class":429,"line":724},[427,1823,823],{"class":433},[427,1825,1826],{"class":441}," [formatter.",[427,1828,1829],{"class":437},"header",[427,1831,1832],{"class":441},"(), ",[427,1834,1835],{"class":433},"...",[427,1837,1838],{"class":441},"rows.",[427,1840,1841],{"class":437},"map",[427,1843,791],{"class":441},[427,1845,1846],{"class":454},"r",[427,1848,513],{"class":441},[427,1850,516],{"class":433},[427,1852,1853],{"class":441}," formatter.",[427,1855,1719],{"class":437},[427,1857,1858],{"class":441},"(r))].",[427,1860,1861],{"class":437},"join",[427,1863,451],{"class":441},[427,1865,1866],{"class":1476},"'",[427,1868,1869],{"class":461},"\\n",[427,1871,1866],{"class":1476},[427,1873,817],{"class":441},[427,1875,1876],{"class":429,"line":739},[427,1877,742],{"class":441},[427,1879,1880],{"class":429,"line":745},[427,1881,476],{"class":441},[427,1883,1884],{"class":429,"line":750},[427,1885,483],{"emptyLinePlaceholder":482},[427,1887,1888,1890,1893,1896,1898],{"class":429,"line":774},[427,1889,670],{"class":433},[427,1891,1892],{"class":437}," CsvExporter",[427,1894,1895],{"class":433}," extends",[427,1897,1748],{"class":437},[427,1899,442],{"class":441},[427,1901,1902,1904,1906,1908,1910,1912],{"class":429,"line":820},[427,1903,1760],{"class":433},[427,1905,1766],{"class":437},[427,1907,1705],{"class":441},[427,1909,458],{"class":433},[427,1911,1695],{"class":437},[427,1913,442],{"class":441},[427,1915,1916,1918],{"class":429,"line":837},[427,1917,823],{"class":433},[427,1919,442],{"class":441},[427,1921,1922,1925,1928,1930,1933],{"class":429,"line":842},[427,1923,1924],{"class":437},"      header",[427,1926,1927],{"class":441},": () ",[427,1929,516],{"class":433},[427,1931,1932],{"class":1476}," 'name,score'",[427,1934,1935],{"class":441},",\n",[427,1937,1938,1941,1943,1945,1947,1949,1952,1954,1956,1959,1962,1964,1966,1969,1972],{"class":429,"line":847},[427,1939,1940],{"class":437},"      row",[427,1942,508],{"class":441},[427,1944,1846],{"class":454},[427,1946,513],{"class":441},[427,1948,516],{"class":433},[427,1950,1951],{"class":1476}," `${",[427,1953,1846],{"class":441},[427,1955,1814],{"class":1476},[427,1957,1958],{"class":441},"name",[427,1960,1961],{"class":1476},"},${",[427,1963,1846],{"class":441},[427,1965,1814],{"class":1476},[427,1967,1968],{"class":441},"score",[427,1970,1971],{"class":1476},"}`",[427,1973,1935],{"class":441},[427,1975,1976],{"class":429,"line":852},[427,1977,1978],{"class":441},"    }\n",[427,1980,1981],{"class":429,"line":870},[427,1982,742],{"class":441},[427,1984,1985],{"class":429,"line":897},[427,1986,476],{"class":441},[427,1988,1989],{"class":429,"line":918},[427,1990,483],{"emptyLinePlaceholder":482},[427,1992,1993,1995,1998,2000,2002],{"class":429,"line":938},[427,1994,670],{"class":433},[427,1996,1997],{"class":437}," MarkdownExporter",[427,1999,1895],{"class":433},[427,2001,1748],{"class":437},[427,2003,442],{"class":441},[427,2005,2006,2008,2010,2012,2014,2016],{"class":429,"line":957},[427,2007,1760],{"class":433},[427,2009,1766],{"class":437},[427,2011,1705],{"class":441},[427,2013,458],{"class":433},[427,2015,1695],{"class":437},[427,2017,442],{"class":441},[427,2019,2020,2022],{"class":429,"line":1563},[427,2021,823],{"class":433},[427,2023,442],{"class":441},[427,2025,2026,2028,2030,2032,2035,2037,2040],{"class":429,"line":1571},[427,2027,1924],{"class":437},[427,2029,1927],{"class":441},[427,2031,516],{"class":433},[427,2033,2034],{"class":1476}," '| name | score |",[427,2036,1869],{"class":461},[427,2038,2039],{"class":1476},"|---|---|'",[427,2041,1935],{"class":441},[427,2043,2044,2046,2048,2050,2052,2054,2057,2059,2061,2063,2066,2068,2070,2072,2075],{"class":429,"line":1590},[427,2045,1940],{"class":437},[427,2047,508],{"class":441},[427,2049,1846],{"class":454},[427,2051,513],{"class":441},[427,2053,516],{"class":433},[427,2055,2056],{"class":1476}," `| ${",[427,2058,1846],{"class":441},[427,2060,1814],{"class":1476},[427,2062,1958],{"class":441},[427,2064,2065],{"class":1476},"} | ${",[427,2067,1846],{"class":441},[427,2069,1814],{"class":1476},[427,2071,1968],{"class":441},[427,2073,2074],{"class":1476},"} |`",[427,2076,1935],{"class":441},[427,2078,2080],{"class":429,"line":2079},35,[427,2081,1978],{"class":441},[427,2083,2085],{"class":429,"line":2084},36,[427,2086,742],{"class":441},[427,2088,2090],{"class":429,"line":2089},37,[427,2091,476],{"class":441},[427,2093,2095],{"class":429,"line":2094},38,[427,2096,483],{"emptyLinePlaceholder":482},[427,2098,2100,2102,2105,2107,2109,2112,2114,2117,2120,2123,2126,2129,2132,2134,2137],{"class":429,"line":2099},39,[427,2101,489],{"class":433},[427,2103,2104],{"class":461}," rows",[427,2106,458],{"class":433},[427,2108,1659],{"class":437},[427,2110,2111],{"class":441},"[] ",[427,2113,733],{"class":433},[427,2115,2116],{"class":441}," [{ name: ",[427,2118,2119],{"class":1476},"'Alice'",[427,2121,2122],{"class":441},", score: ",[427,2124,2125],{"class":461},"92",[427,2127,2128],{"class":441}," }, { name: ",[427,2130,2131],{"class":1476},"'Bob'",[427,2133,2122],{"class":441},[427,2135,2136],{"class":461},"85",[427,2138,2139],{"class":441}," }]\n",[427,2141,2143,2146,2148,2151,2154],{"class":429,"line":2142},40,[427,2144,2145],{"class":433},"new",[427,2147,1892],{"class":437},[427,2149,2150],{"class":441},"().",[427,2152,2153],{"class":437},"export",[427,2155,2156],{"class":441},"(rows)\n",[427,2158,2160],{"class":429,"line":2159},41,[427,2161,2162],{"class":893},"\u002F\u002F name,score\n",[427,2164,2166],{"class":429,"line":2165},42,[427,2167,2168],{"class":893},"\u002F\u002F Alice,92\n",[427,2170,2172],{"class":429,"line":2171},43,[427,2173,2174],{"class":893},"\u002F\u002F Bob,85\n",[10,2176,2177,2179,2180,2182],{},[402,2178,2153],{}," が処理の流れを決め、細部をサブクラスに任せている点は ",[14,2181,376],{}," パターンでもあります。Factory Method は、Template Method の「細部」がたまたま「オブジェクトの生成」である場合、と見ることもできます。",[2184,2185,2186],"note",{},[10,2187,2188,2189,2192,2193,2196],{},"継承を使わずに、",[402,2190,2191],{},"{ csv: () => csvFormatter, markdown: () => markdownFormatter }"," のような ",[14,2194,2195],{},"生成関数の対応表"," を用意するだけで足りることも多いです。種類が少なく、増える見込みもなければ、それで十分です。",[26,2198,2200],{"id":2199},"_6-adapter合わない形を変換する","6. Adapter：合わない形を変換する",[10,2202,2203,2205,2206,2209],{},[14,2204,399],{},"：アプリ全体で使う ",[402,2207,2208],{},"Logger"," の形は決まっている。ところが、導入したい外部のログライブラリは、まったく違う形のメソッドしか持っていない。ライブラリのコードは変更できない。",[10,2211,2212,2214],{},[14,2213,414],{},"：外部ライブラリを包み、アプリが期待するインターフェースに変換する「変換アダプター」を間に挟む。",[417,2216,2219],{"className":419,"code":2217,"filename":2218,"language":422,"meta":423,"style":423},"\u002F\u002F アプリが期待するインターフェース\ninterface Logger {\n  info(message: string): void\n  error(message: string, cause?: unknown): void\n}\n\n\u002F\u002F 外部ライブラリ（変更できない）\nclass VendorLogger {\n  write(level: 'INFO' | 'ERROR', fields: Record\u003Cstring, unknown>): void {\n    console.log(`[vendor] ${level}`, JSON.stringify(fields))\n  }\n}\n\nclass VendorLoggerAdapter implements Logger {\n  constructor(private readonly vendor: VendorLogger) {}\n\n  info(message: string): void {\n    this.vendor.write('INFO', { msg: message })\n  }\n\n  error(message: string, cause?: unknown): void {\n    const detail = cause instanceof Error ? cause.message : cause\n    this.vendor.write('ERROR', { msg: message, cause: detail })\n  }\n}\n\nconst logger: Logger = new VendorLoggerAdapter(new VendorLogger())\nlogger.error('保存に失敗しました', new Error('disk full'))\n\u002F\u002F → [vendor] ERROR {\"msg\":\"保存に失敗しました\",\"cause\":\"disk full\"}\n","adapter.ts",[402,2220,2221,2226,2235,2255,2285,2289,2293,2298,2307,2356,2385,2389,2393,2397,2411,2432,2436,2456,2474,2478,2482,2510,2539,2555,2559,2563,2567,2593,2619],{"__ignoreMap":423},[427,2222,2223],{"class":429,"line":430},[427,2224,2225],{"class":893},"\u002F\u002F アプリが期待するインターフェース\n",[427,2227,2228,2230,2233],{"class":429,"line":445},[427,2229,434],{"class":433},[427,2231,2232],{"class":437}," Logger",[427,2234,442],{"class":441},[427,2236,2237,2240,2242,2245,2247,2249,2251,2253],{"class":429,"line":473},[427,2238,2239],{"class":437},"  info",[427,2241,451],{"class":441},[427,2243,2244],{"class":454},"message",[427,2246,458],{"class":433},[427,2248,1798],{"class":461},[427,2250,465],{"class":441},[427,2252,458],{"class":433},[427,2254,1196],{"class":461},[427,2256,2257,2260,2262,2264,2266,2268,2270,2273,2276,2279,2281,2283],{"class":429,"line":479},[427,2258,2259],{"class":437},"  error",[427,2261,451],{"class":441},[427,2263,2244],{"class":454},[427,2265,458],{"class":433},[427,2267,1798],{"class":461},[427,2269,797],{"class":441},[427,2271,2272],{"class":454},"cause",[427,2274,2275],{"class":433},"?:",[427,2277,2278],{"class":461}," unknown",[427,2280,465],{"class":441},[427,2282,458],{"class":433},[427,2284,1196],{"class":461},[427,2286,2287],{"class":429,"line":486},[427,2288,476],{"class":441},[427,2290,2291],{"class":429,"line":522},[427,2292,483],{"emptyLinePlaceholder":482},[427,2294,2295],{"class":429,"line":554},[427,2296,2297],{"class":893},"\u002F\u002F 外部ライブラリ（変更できない）\n",[427,2299,2300,2302,2305],{"class":429,"line":590},[427,2301,670],{"class":433},[427,2303,2304],{"class":437}," VendorLogger",[427,2306,442],{"class":441},[427,2308,2309,2312,2314,2317,2319,2322,2325,2328,2330,2333,2335,2338,2340,2343,2345,2348,2350,2352,2354],{"class":429,"line":596},[427,2310,2311],{"class":437},"  write",[427,2313,451],{"class":441},[427,2315,2316],{"class":454},"level",[427,2318,458],{"class":433},[427,2320,2321],{"class":1476}," 'INFO'",[427,2323,2324],{"class":433}," |",[427,2326,2327],{"class":1476}," 'ERROR'",[427,2329,797],{"class":441},[427,2331,2332],{"class":454},"fields",[427,2334,458],{"class":433},[427,2336,2337],{"class":437}," Record",[427,2339,1171],{"class":441},[427,2341,2342],{"class":461},"string",[427,2344,797],{"class":441},[427,2346,2347],{"class":461},"unknown",[427,2349,1266],{"class":441},[427,2351,458],{"class":433},[427,2353,1276],{"class":461},[427,2355,442],{"class":441},[427,2357,2358,2361,2363,2365,2368,2370,2372,2374,2377,2379,2382],{"class":429,"line":625},[427,2359,2360],{"class":441},"    console.",[427,2362,1471],{"class":437},[427,2364,451],{"class":441},[427,2366,2367],{"class":1476},"`[vendor] ${",[427,2369,2316],{"class":441},[427,2371,1971],{"class":1476},[427,2373,797],{"class":441},[427,2375,2376],{"class":461},"JSON",[427,2378,1814],{"class":441},[427,2380,2381],{"class":437},"stringify",[427,2383,2384],{"class":441},"(fields))\n",[427,2386,2387],{"class":429,"line":657},[427,2388,742],{"class":441},[427,2390,2391],{"class":429,"line":662},[427,2392,476],{"class":441},[427,2394,2395],{"class":429,"line":667},[427,2396,483],{"emptyLinePlaceholder":482},[427,2398,2399,2401,2404,2407,2409],{"class":429,"line":678},[427,2400,670],{"class":433},[427,2402,2403],{"class":437}," VendorLoggerAdapter",[427,2405,2406],{"class":433}," implements",[427,2408,2232],{"class":437},[427,2410,442],{"class":441},[427,2412,2413,2415,2417,2419,2422,2425,2427,2429],{"class":429,"line":701},[427,2414,681],{"class":433},[427,2416,451],{"class":441},[427,2418,686],{"class":433},[427,2420,2421],{"class":433}," readonly",[427,2423,2424],{"class":454}," vendor",[427,2426,458],{"class":433},[427,2428,2304],{"class":437},[427,2430,2431],{"class":441},") {}\n",[427,2433,2434],{"class":429,"line":706},[427,2435,483],{"emptyLinePlaceholder":482},[427,2437,2438,2440,2442,2444,2446,2448,2450,2452,2454],{"class":429,"line":724},[427,2439,2239],{"class":437},[427,2441,451],{"class":441},[427,2443,2244],{"class":454},[427,2445,458],{"class":433},[427,2447,1798],{"class":461},[427,2449,465],{"class":441},[427,2451,458],{"class":433},[427,2453,1276],{"class":461},[427,2455,442],{"class":441},[427,2457,2458,2460,2463,2466,2468,2471],{"class":429,"line":739},[427,2459,727],{"class":461},[427,2461,2462],{"class":441},".vendor.",[427,2464,2465],{"class":437},"write",[427,2467,451],{"class":441},[427,2469,2470],{"class":1476},"'INFO'",[427,2472,2473],{"class":441},", { msg: message })\n",[427,2475,2476],{"class":429,"line":745},[427,2477,742],{"class":441},[427,2479,2480],{"class":429,"line":750},[427,2481,483],{"emptyLinePlaceholder":482},[427,2483,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508],{"class":429,"line":774},[427,2485,2259],{"class":437},[427,2487,451],{"class":441},[427,2489,2244],{"class":454},[427,2491,458],{"class":433},[427,2493,1798],{"class":461},[427,2495,797],{"class":441},[427,2497,2272],{"class":454},[427,2499,2275],{"class":433},[427,2501,2278],{"class":461},[427,2503,465],{"class":441},[427,2505,458],{"class":433},[427,2507,1276],{"class":461},[427,2509,442],{"class":441},[427,2511,2512,2514,2517,2519,2522,2525,2528,2531,2534,2536],{"class":429,"line":820},[427,2513,777],{"class":433},[427,2515,2516],{"class":461}," detail",[427,2518,499],{"class":433},[427,2520,2521],{"class":441}," cause ",[427,2523,2524],{"class":433},"instanceof",[427,2526,2527],{"class":437}," Error",[427,2529,2530],{"class":433}," ?",[427,2532,2533],{"class":441}," cause.message ",[427,2535,458],{"class":433},[427,2537,2538],{"class":441}," cause\n",[427,2540,2541,2543,2545,2547,2549,2552],{"class":429,"line":837},[427,2542,727],{"class":461},[427,2544,2462],{"class":441},[427,2546,2465],{"class":437},[427,2548,451],{"class":441},[427,2550,2551],{"class":1476},"'ERROR'",[427,2553,2554],{"class":441},", { msg: message, cause: detail })\n",[427,2556,2557],{"class":429,"line":842},[427,2558,742],{"class":441},[427,2560,2561],{"class":429,"line":847},[427,2562,476],{"class":441},[427,2564,2565],{"class":429,"line":852},[427,2566,483],{"emptyLinePlaceholder":482},[427,2568,2569,2571,2574,2576,2578,2580,2582,2584,2586,2588,2590],{"class":429,"line":870},[427,2570,489],{"class":433},[427,2572,2573],{"class":461}," logger",[427,2575,458],{"class":433},[427,2577,2232],{"class":437},[427,2579,499],{"class":433},[427,2581,862],{"class":433},[427,2583,2403],{"class":437},[427,2585,451],{"class":441},[427,2587,2145],{"class":433},[427,2589,2304],{"class":437},[427,2591,2592],{"class":441},"())\n",[427,2594,2595,2598,2601,2603,2606,2608,2610,2612,2614,2617],{"class":429,"line":897},[427,2596,2597],{"class":441},"logger.",[427,2599,2600],{"class":437},"error",[427,2602,451],{"class":441},[427,2604,2605],{"class":1476},"'保存に失敗しました'",[427,2607,797],{"class":441},[427,2609,2145],{"class":433},[427,2611,2527],{"class":437},[427,2613,451],{"class":441},[427,2615,2616],{"class":1476},"'disk full'",[427,2618,915],{"class":441},[427,2620,2621],{"class":429,"line":918},[427,2622,2623],{"class":893},"\u002F\u002F → [vendor] ERROR {\"msg\":\"保存に失敗しました\",\"cause\":\"disk full\"}\n",[10,2625,2626,2627,2629],{},"アプリのコードは ",[402,2628,2208],{}," だけを知っているので、将来ログライブラリを乗り換えるときも、アダプターを1つ書き直すだけで済みます。外部 API のレスポンスを、アプリ内部の型に変換する層を設けるのも同じ発想です。",[26,2631,2633],{"id":2632},"_7-decorator機能を後から重ねる","7. Decorator：機能を後から重ねる",[10,2635,2636,2638],{},[14,2637,399],{},"：データを取得する処理に「失敗したら再試行」「結果をキャッシュ」を付けたい。しかし、取得処理そのものに手を入れたり、組み合わせの数だけサブクラスを作ったりはしたくない。",[10,2640,2641,2643],{},[14,2642,414],{},"：同じインターフェースを持つ「包み紙」を用意し、元のオブジェクトを包んで機能を足す。包み紙は何重にも重ねられる。",[417,2645,2648],{"className":419,"code":2646,"filename":2647,"language":422,"meta":423,"style":423},"interface DataSource {\n  fetch(key: string): Promise\u003Cstring>\n}\n\n\u002F\u002F 失敗したら、指定回数まで再試行する\nclass RetrySource implements DataSource {\n  constructor(private readonly inner: DataSource, private readonly retries = 2) {}\n\n  async fetch(key: string): Promise\u003Cstring> {\n    let lastError: unknown\n    for (let attempt = 0; attempt \u003C= this.retries; attempt++) {\n      try {\n        return await this.inner.fetch(key)\n      } catch (e) {\n        lastError = e\n      }\n    }\n    throw lastError\n  }\n}\n\n\u002F\u002F 同じキーの結果を使い回す\nclass CachedSource implements DataSource {\n  private readonly cache = new Map\u003Cstring, Promise\u003Cstring>>()\n\n  constructor(private readonly inner: DataSource) {}\n\n  fetch(key: string): Promise\u003Cstring> {\n    let hit = this.cache.get(key)\n    if (!hit) {\n      hit = this.inner.fetch(key)\n      this.cache.set(key, hit)\n      hit.catch(() => this.cache.delete(key)) \u002F\u002F 失敗はキャッシュしない\n    }\n    return hit\n  }\n}\n\n\u002F\u002F 1回目は失敗し、2回目に成功する API（例）\nlet calls = 0\nconst flakyApi: DataSource = {\n  async fetch(key) {\n    calls++\n    if (calls === 1) throw new Error('temporary failure')\n    return `value of ${key}`\n  },\n}\n\nconst source: DataSource = new CachedSource(new RetrySource(flakyApi))\nawait source.fetch('a') \u002F\u002F \"value of a\"（内部で1回再試行。calls = 2）\nawait source.fetch('a') \u002F\u002F \"value of a\"（キャッシュから。calls = 2 のまま）\n","decorator.ts",[402,2649,2650,2659,2687,2691,2695,2700,2713,2746,2750,2778,2791,2824,2831,2850,2861,2871,2876,2880,2888,2892,2896,2900,2905,2918,2949,2953,2971,2975,2999,3018,3031,3046,3059,3083,3087,3094,3098,3102,3106,3111,3123,3138,3150,3158,3188,3201,3207,3212,3217,3244,3265],{"__ignoreMap":423},[427,2651,2652,2654,2657],{"class":429,"line":430},[427,2653,434],{"class":433},[427,2655,2656],{"class":437}," DataSource",[427,2658,442],{"class":441},[427,2660,2661,2664,2666,2669,2671,2673,2675,2677,2680,2682,2684],{"class":429,"line":445},[427,2662,2663],{"class":437},"  fetch",[427,2665,451],{"class":441},[427,2667,2668],{"class":454},"key",[427,2670,458],{"class":433},[427,2672,1798],{"class":461},[427,2674,465],{"class":441},[427,2676,458],{"class":433},[427,2678,2679],{"class":437}," Promise",[427,2681,1171],{"class":441},[427,2683,2342],{"class":461},[427,2685,2686],{"class":441},">\n",[427,2688,2689],{"class":429,"line":473},[427,2690,476],{"class":441},[427,2692,2693],{"class":429,"line":479},[427,2694,483],{"emptyLinePlaceholder":482},[427,2696,2697],{"class":429,"line":486},[427,2698,2699],{"class":893},"\u002F\u002F 失敗したら、指定回数まで再試行する\n",[427,2701,2702,2704,2707,2709,2711],{"class":429,"line":522},[427,2703,670],{"class":433},[427,2705,2706],{"class":437}," RetrySource",[427,2708,2406],{"class":433},[427,2710,2656],{"class":437},[427,2712,442],{"class":441},[427,2714,2715,2717,2719,2721,2723,2726,2728,2730,2732,2734,2736,2739,2741,2744],{"class":429,"line":554},[427,2716,681],{"class":433},[427,2718,451],{"class":441},[427,2720,686],{"class":433},[427,2722,2421],{"class":433},[427,2724,2725],{"class":454}," inner",[427,2727,458],{"class":433},[427,2729,2656],{"class":437},[427,2731,797],{"class":441},[427,2733,686],{"class":433},[427,2735,2421],{"class":433},[427,2737,2738],{"class":454}," retries",[427,2740,499],{"class":433},[427,2742,2743],{"class":461}," 2",[427,2745,2431],{"class":441},[427,2747,2748],{"class":429,"line":590},[427,2749,483],{"emptyLinePlaceholder":482},[427,2751,2752,2755,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776],{"class":429,"line":596},[427,2753,2754],{"class":433},"  async",[427,2756,2757],{"class":437}," fetch",[427,2759,451],{"class":441},[427,2761,2668],{"class":454},[427,2763,458],{"class":433},[427,2765,1798],{"class":461},[427,2767,465],{"class":441},[427,2769,458],{"class":433},[427,2771,2679],{"class":437},[427,2773,1171],{"class":441},[427,2775,2342],{"class":461},[427,2777,1214],{"class":441},[427,2779,2780,2783,2786,2788],{"class":429,"line":625},[427,2781,2782],{"class":433},"    let",[427,2784,2785],{"class":441}," lastError",[427,2787,458],{"class":433},[427,2789,2790],{"class":461}," unknown\n",[427,2792,2793,2795,2797,2800,2803,2805,2808,2811,2814,2816,2819,2822],{"class":429,"line":657},[427,2794,1346],{"class":433},[427,2796,532],{"class":441},[427,2798,2799],{"class":433},"let",[427,2801,2802],{"class":441}," attempt ",[427,2804,733],{"class":433},[427,2806,2807],{"class":461}," 0",[427,2809,2810],{"class":441},"; attempt ",[427,2812,2813],{"class":433},"\u003C=",[427,2815,826],{"class":461},[427,2817,2818],{"class":441},".retries; attempt",[427,2820,2821],{"class":433},"++",[427,2823,721],{"class":441},[427,2825,2826,2829],{"class":429,"line":662},[427,2827,2828],{"class":433},"      try",[427,2830,442],{"class":441},[427,2832,2833,2836,2839,2841,2844,2847],{"class":429,"line":667},[427,2834,2835],{"class":433},"        return",[427,2837,2838],{"class":433}," await",[427,2840,826],{"class":461},[427,2842,2843],{"class":441},".inner.",[427,2845,2846],{"class":437},"fetch",[427,2848,2849],{"class":441},"(key)\n",[427,2851,2852,2855,2858],{"class":429,"line":678},[427,2853,2854],{"class":441},"      } ",[427,2856,2857],{"class":433},"catch",[427,2859,2860],{"class":441}," (e) {\n",[427,2862,2863,2866,2868],{"class":429,"line":701},[427,2864,2865],{"class":441},"        lastError ",[427,2867,733],{"class":433},[427,2869,2870],{"class":441}," e\n",[427,2872,2873],{"class":429,"line":706},[427,2874,2875],{"class":441},"      }\n",[427,2877,2878],{"class":429,"line":724},[427,2879,1978],{"class":441},[427,2881,2882,2885],{"class":429,"line":739},[427,2883,2884],{"class":433},"    throw",[427,2886,2887],{"class":441}," lastError\n",[427,2889,2890],{"class":429,"line":745},[427,2891,742],{"class":441},[427,2893,2894],{"class":429,"line":750},[427,2895,476],{"class":441},[427,2897,2898],{"class":429,"line":774},[427,2899,483],{"emptyLinePlaceholder":482},[427,2901,2902],{"class":429,"line":820},[427,2903,2904],{"class":893},"\u002F\u002F 同じキーの結果を使い回す\n",[427,2906,2907,2909,2912,2914,2916],{"class":429,"line":837},[427,2908,670],{"class":433},[427,2910,2911],{"class":437}," CachedSource",[427,2913,2406],{"class":433},[427,2915,2656],{"class":437},[427,2917,442],{"class":441},[427,2919,2920,2922,2924,2927,2929,2931,2934,2936,2938,2940,2943,2945,2947],{"class":429,"line":842},[427,2921,1219],{"class":433},[427,2923,2421],{"class":433},[427,2925,2926],{"class":454}," cache",[427,2928,499],{"class":433},[427,2930,862],{"class":433},[427,2932,2933],{"class":437}," Map",[427,2935,1171],{"class":441},[427,2937,2342],{"class":461},[427,2939,797],{"class":441},[427,2941,2942],{"class":437},"Promise",[427,2944,1171],{"class":441},[427,2946,2342],{"class":461},[427,2948,1241],{"class":441},[427,2950,2951],{"class":429,"line":847},[427,2952,483],{"emptyLinePlaceholder":482},[427,2954,2955,2957,2959,2961,2963,2965,2967,2969],{"class":429,"line":852},[427,2956,681],{"class":433},[427,2958,451],{"class":441},[427,2960,686],{"class":433},[427,2962,2421],{"class":433},[427,2964,2725],{"class":454},[427,2966,458],{"class":433},[427,2968,2656],{"class":437},[427,2970,2431],{"class":441},[427,2972,2973],{"class":429,"line":870},[427,2974,483],{"emptyLinePlaceholder":482},[427,2976,2977,2979,2981,2983,2985,2987,2989,2991,2993,2995,2997],{"class":429,"line":897},[427,2978,2663],{"class":437},[427,2980,451],{"class":441},[427,2982,2668],{"class":454},[427,2984,458],{"class":433},[427,2986,1798],{"class":461},[427,2988,465],{"class":441},[427,2990,458],{"class":433},[427,2992,2679],{"class":437},[427,2994,1171],{"class":441},[427,2996,2342],{"class":461},[427,2998,1214],{"class":441},[427,3000,3001,3003,3006,3008,3010,3013,3016],{"class":429,"line":918},[427,3002,2782],{"class":433},[427,3004,3005],{"class":441}," hit ",[427,3007,733],{"class":433},[427,3009,826],{"class":461},[427,3011,3012],{"class":441},".cache.",[427,3014,3015],{"class":437},"get",[427,3017,2849],{"class":441},[427,3019,3020,3023,3025,3028],{"class":429,"line":938},[427,3021,3022],{"class":433},"    if",[427,3024,532],{"class":441},[427,3026,3027],{"class":433},"!",[427,3029,3030],{"class":441},"hit) {\n",[427,3032,3033,3036,3038,3040,3042,3044],{"class":429,"line":957},[427,3034,3035],{"class":441},"      hit ",[427,3037,733],{"class":433},[427,3039,826],{"class":461},[427,3041,2843],{"class":441},[427,3043,2846],{"class":437},[427,3045,2849],{"class":441},[427,3047,3048,3051,3053,3056],{"class":429,"line":1563},[427,3049,3050],{"class":461},"      this",[427,3052,3012],{"class":441},[427,3054,3055],{"class":437},"set",[427,3057,3058],{"class":441},"(key, hit)\n",[427,3060,3061,3064,3066,3069,3071,3073,3075,3077,3080],{"class":429,"line":1571},[427,3062,3063],{"class":441},"      hit.",[427,3065,2857],{"class":437},[427,3067,3068],{"class":441},"(() ",[427,3070,516],{"class":433},[427,3072,826],{"class":461},[427,3074,3012],{"class":441},[427,3076,1306],{"class":437},[427,3078,3079],{"class":441},"(key)) ",[427,3081,3082],{"class":893},"\u002F\u002F 失敗はキャッシュしない\n",[427,3084,3085],{"class":429,"line":1590},[427,3086,1978],{"class":441},[427,3088,3089,3091],{"class":429,"line":2079},[427,3090,823],{"class":433},[427,3092,3093],{"class":441}," hit\n",[427,3095,3096],{"class":429,"line":2084},[427,3097,742],{"class":441},[427,3099,3100],{"class":429,"line":2089},[427,3101,476],{"class":441},[427,3103,3104],{"class":429,"line":2094},[427,3105,483],{"emptyLinePlaceholder":482},[427,3107,3108],{"class":429,"line":2099},[427,3109,3110],{"class":893},"\u002F\u002F 1回目は失敗し、2回目に成功する API（例）\n",[427,3112,3113,3115,3118,3120],{"class":429,"line":2142},[427,3114,2799],{"class":433},[427,3116,3117],{"class":441}," calls ",[427,3119,733],{"class":433},[427,3121,3122],{"class":461}," 0\n",[427,3124,3125,3127,3130,3132,3134,3136],{"class":429,"line":2159},[427,3126,489],{"class":433},[427,3128,3129],{"class":461}," flakyApi",[427,3131,458],{"class":433},[427,3133,2656],{"class":437},[427,3135,499],{"class":433},[427,3137,442],{"class":441},[427,3139,3140,3142,3144,3146,3148],{"class":429,"line":2165},[427,3141,2754],{"class":433},[427,3143,2757],{"class":437},[427,3145,451],{"class":441},[427,3147,2668],{"class":454},[427,3149,721],{"class":441},[427,3151,3152,3155],{"class":429,"line":2171},[427,3153,3154],{"class":441},"    calls",[427,3156,3157],{"class":433},"++\n",[427,3159,3161,3163,3166,3169,3172,3174,3177,3179,3181,3183,3186],{"class":429,"line":3160},44,[427,3162,3022],{"class":433},[427,3164,3165],{"class":441}," (calls ",[427,3167,3168],{"class":433},"===",[427,3170,3171],{"class":461}," 1",[427,3173,513],{"class":441},[427,3175,3176],{"class":433},"throw",[427,3178,862],{"class":433},[427,3180,2527],{"class":437},[427,3182,451],{"class":441},[427,3184,3185],{"class":1476},"'temporary failure'",[427,3187,817],{"class":441},[427,3189,3191,3193,3196,3198],{"class":429,"line":3190},45,[427,3192,823],{"class":433},[427,3194,3195],{"class":1476}," `value of ${",[427,3197,2668],{"class":441},[427,3199,3200],{"class":1476},"}`\n",[427,3202,3204],{"class":429,"line":3203},46,[427,3205,3206],{"class":441},"  },\n",[427,3208,3210],{"class":429,"line":3209},47,[427,3211,476],{"class":441},[427,3213,3215],{"class":429,"line":3214},48,[427,3216,483],{"emptyLinePlaceholder":482},[427,3218,3220,3222,3225,3227,3229,3231,3233,3235,3237,3239,3241],{"class":429,"line":3219},49,[427,3221,489],{"class":433},[427,3223,3224],{"class":461}," source",[427,3226,458],{"class":433},[427,3228,2656],{"class":437},[427,3230,499],{"class":433},[427,3232,862],{"class":433},[427,3234,2911],{"class":437},[427,3236,451],{"class":441},[427,3238,2145],{"class":433},[427,3240,2706],{"class":437},[427,3242,3243],{"class":441},"(flakyApi))\n",[427,3245,3247,3250,3253,3255,3257,3260,3262],{"class":429,"line":3246},50,[427,3248,3249],{"class":433},"await",[427,3251,3252],{"class":441}," source.",[427,3254,2846],{"class":437},[427,3256,451],{"class":441},[427,3258,3259],{"class":1476},"'a'",[427,3261,513],{"class":441},[427,3263,3264],{"class":893},"\u002F\u002F \"value of a\"（内部で1回再試行。calls = 2）\n",[427,3266,3268,3270,3272,3274,3276,3278,3280],{"class":429,"line":3267},51,[427,3269,3249],{"class":433},[427,3271,3252],{"class":441},[427,3273,2846],{"class":437},[427,3275,451],{"class":441},[427,3277,3259],{"class":1476},[427,3279,513],{"class":441},[427,3281,3282],{"class":893},"\u002F\u002F \"value of a\"（キャッシュから。calls = 2 のまま）\n",[10,3284,3285,3286,3289,3290,3293,3294,3297,3298,3301],{},"ここでは ",[402,3287,3288],{},"CachedSource(RetrySource(api))"," の順に包んでいるので、「再試行込みで成功した結果」がキャッシュされます。逆に ",[402,3291,3292],{},"RetrySource(CachedSource(api))"," の順にすると、再試行のたびにキャッシュを経由します。もし ",[402,3295,3296],{},"CachedSource"," が失敗した結果も残す実装だったら、再試行しても同じ失敗を受け取るだけで、再試行が意味をなさなくなります。このように ",[14,3299,3300],{},"包む順番が意味を持つ"," のが Decorator の特徴です。",[2184,3303,3304],{},[10,3305,3306,3307,3310,3311,3314],{},"TypeScript の ",[402,3308,3309],{},"@decorator"," 構文（デコレーター）は名前が同じですが、クラスやメソッドの定義に処理を差し込む言語機能です。考え方は似ていますが、GoF の Decorator パターンは ",[14,3312,3313],{},"実行時にオブジェクトを包む"," 設計である点が異なります。",[26,3316,3318],{"id":3317},"_8-chain-of-responsibility要求を順に渡す","8. Chain of Responsibility：要求を順に渡す",[10,3320,3321,3323],{},[14,3322,399],{},"：リクエストに対して「ログを取る」「認証を確認する」「本来の処理をする」を順に行いたい。途中で認証に失敗したら、そこで処理を打ち切りたい。",[10,3325,3326,3328],{},[14,3327,414],{},"：処理を小さな部品（ミドルウェア）に分け、鎖のようにつなぐ。各部品は「自分で処理を終える」か「次に渡す」かを決める。",[417,3330,3333],{"className":419,"code":3331,"filename":3332,"language":422,"meta":423,"style":423},"interface Context {\n  path: string\n  user?: string\n  status?: number\n  body?: string\n}\n\ntype Middleware = (ctx: Context, next: () => Promise\u003Cvoid>) => Promise\u003Cvoid>\n\n\u002F\u002F ミドルウェアの配列を、1つの処理につなげる\nconst compose = (middlewares: Middleware[]) => (ctx: Context): Promise\u003Cvoid> => {\n  const dispatch = (i: number): Promise\u003Cvoid> => {\n    const mw = middlewares[i]\n    return mw ? mw(ctx, () => dispatch(i + 1)) : Promise.resolve()\n  }\n  return dispatch(0)\n}\n\nconst logging: Middleware = async (ctx, next) => {\n  const start = Date.now()\n  await next()\n  console.log(`${ctx.path} -> ${ctx.status} (${Date.now() - start}ms)`)\n}\n\nconst auth: Middleware = async (ctx, next) => {\n  if (ctx.path.startsWith('\u002Fadmin') && !ctx.user) {\n    ctx.status = 401\n    ctx.body = 'Unauthorized'\n    return \u002F\u002F next() を呼ばない＝ここで処理を打ち切る\n  }\n  await next()\n}\n\nconst handler: Middleware = async (ctx) => {\n  ctx.status = 200\n  ctx.body = `Hello ${ctx.user ?? 'guest'}`\n}\n\nconst app = compose([logging, auth, handler])\n\nawait app({ path: '\u002Fadmin\u002Fsettings' })                 \u002F\u002F \u002Fadmin\u002Fsettings -> 401\nawait app({ path: '\u002Fadmin\u002Fsettings', user: 'kyosei' }) \u002F\u002F \u002Fadmin\u002Fsettings -> 200\nawait app({ path: '\u002Fblog' })                           \u002F\u002F \u002Fblog -> 200\n","middleware.ts",[402,3334,3335,3344,3353,3362,3371,3380,3384,3388,3437,3441,3446,3493,3528,3540,3580,3584,3597,3601,3605,3635,3652,3662,3712,3716,3720,3749,3776,3786,3796,3803,3807,3815,3819,3823,3848,3858,3881,3885,3889,3903,3907,3925,3946],{"__ignoreMap":423},[427,3336,3337,3339,3342],{"class":429,"line":430},[427,3338,434],{"class":433},[427,3340,3341],{"class":437}," Context",[427,3343,442],{"class":441},[427,3345,3346,3349,3351],{"class":429,"line":445},[427,3347,3348],{"class":454},"  path",[427,3350,458],{"class":433},[427,3352,1671],{"class":461},[427,3354,3355,3358,3360],{"class":429,"line":473},[427,3356,3357],{"class":454},"  user",[427,3359,2275],{"class":433},[427,3361,1671],{"class":461},[427,3363,3364,3367,3369],{"class":429,"line":479},[427,3365,3366],{"class":454},"  status",[427,3368,2275],{"class":433},[427,3370,470],{"class":461},[427,3372,3373,3376,3378],{"class":429,"line":486},[427,3374,3375],{"class":454},"  body",[427,3377,2275],{"class":433},[427,3379,1671],{"class":461},[427,3381,3382],{"class":429,"line":522},[427,3383,476],{"class":441},[427,3385,3386],{"class":429,"line":554},[427,3387,483],{"emptyLinePlaceholder":482},[427,3389,3390,3392,3395,3397,3399,3402,3404,3406,3408,3411,3413,3415,3417,3419,3421,3424,3427,3429,3431,3433,3435],{"class":429,"line":590},[427,3391,998],{"class":433},[427,3393,3394],{"class":437}," Middleware",[427,3396,499],{"class":433},[427,3398,532],{"class":441},[427,3400,3401],{"class":454},"ctx",[427,3403,458],{"class":433},[427,3405,3341],{"class":437},[427,3407,797],{"class":441},[427,3409,3410],{"class":437},"next",[427,3412,458],{"class":433},[427,3414,1271],{"class":441},[427,3416,516],{"class":433},[427,3418,2679],{"class":437},[427,3420,1171],{"class":441},[427,3422,3423],{"class":461},"void",[427,3425,3426],{"class":441},">) ",[427,3428,516],{"class":433},[427,3430,2679],{"class":437},[427,3432,1171],{"class":441},[427,3434,3423],{"class":461},[427,3436,2686],{"class":441},[427,3438,3439],{"class":429,"line":596},[427,3440,483],{"emptyLinePlaceholder":482},[427,3442,3443],{"class":429,"line":625},[427,3444,3445],{"class":893},"\u002F\u002F ミドルウェアの配列を、1つの処理につなげる\n",[427,3447,3448,3450,3453,3455,3457,3460,3462,3464,3467,3469,3471,3473,3475,3477,3479,3481,3483,3485,3487,3489,3491],{"class":429,"line":657},[427,3449,489],{"class":433},[427,3451,3452],{"class":437}," compose",[427,3454,499],{"class":433},[427,3456,532],{"class":441},[427,3458,3459],{"class":454},"middlewares",[427,3461,458],{"class":433},[427,3463,3394],{"class":437},[427,3465,3466],{"class":441},"[]) ",[427,3468,516],{"class":433},[427,3470,532],{"class":441},[427,3472,3401],{"class":454},[427,3474,458],{"class":433},[427,3476,3341],{"class":437},[427,3478,465],{"class":441},[427,3480,458],{"class":433},[427,3482,2679],{"class":437},[427,3484,1171],{"class":441},[427,3486,3423],{"class":461},[427,3488,1177],{"class":441},[427,3490,516],{"class":433},[427,3492,442],{"class":441},[427,3494,3495,3498,3501,3503,3505,3508,3510,3512,3514,3516,3518,3520,3522,3524,3526],{"class":429,"line":662},[427,3496,3497],{"class":433},"  const",[427,3499,3500],{"class":437}," dispatch",[427,3502,499],{"class":433},[427,3504,532],{"class":441},[427,3506,3507],{"class":454},"i",[427,3509,458],{"class":433},[427,3511,462],{"class":461},[427,3513,465],{"class":441},[427,3515,458],{"class":433},[427,3517,2679],{"class":437},[427,3519,1171],{"class":441},[427,3521,3423],{"class":461},[427,3523,1177],{"class":441},[427,3525,516],{"class":433},[427,3527,442],{"class":441},[427,3529,3530,3532,3535,3537],{"class":429,"line":667},[427,3531,777],{"class":433},[427,3533,3534],{"class":461}," mw",[427,3536,499],{"class":433},[427,3538,3539],{"class":441}," middlewares[i]\n",[427,3541,3542,3544,3547,3550,3552,3555,3557,3559,3562,3564,3566,3569,3571,3573,3575,3578],{"class":429,"line":678},[427,3543,823],{"class":433},[427,3545,3546],{"class":441}," mw ",[427,3548,3549],{"class":433},"?",[427,3551,3534],{"class":437},[427,3553,3554],{"class":441},"(ctx, () ",[427,3556,516],{"class":433},[427,3558,3500],{"class":437},[427,3560,3561],{"class":441},"(i ",[427,3563,809],{"class":433},[427,3565,3171],{"class":461},[427,3567,3568],{"class":441},")) ",[427,3570,458],{"class":433},[427,3572,2679],{"class":461},[427,3574,1814],{"class":441},[427,3576,3577],{"class":437},"resolve",[427,3579,867],{"class":441},[427,3581,3582],{"class":429,"line":701},[427,3583,742],{"class":441},[427,3585,3586,3589,3591,3593,3595],{"class":429,"line":706},[427,3587,3588],{"class":433},"  return",[427,3590,3500],{"class":437},[427,3592,451],{"class":441},[427,3594,645],{"class":461},[427,3596,817],{"class":441},[427,3598,3599],{"class":429,"line":724},[427,3600,476],{"class":441},[427,3602,3603],{"class":429,"line":739},[427,3604,483],{"emptyLinePlaceholder":482},[427,3606,3607,3609,3612,3614,3616,3618,3621,3623,3625,3627,3629,3631,3633],{"class":429,"line":745},[427,3608,489],{"class":433},[427,3610,3611],{"class":437}," logging",[427,3613,458],{"class":433},[427,3615,3394],{"class":437},[427,3617,499],{"class":433},[427,3619,3620],{"class":433}," async",[427,3622,532],{"class":441},[427,3624,3401],{"class":454},[427,3626,797],{"class":441},[427,3628,3410],{"class":454},[427,3630,513],{"class":441},[427,3632,516],{"class":433},[427,3634,442],{"class":441},[427,3636,3637,3639,3642,3644,3647,3650],{"class":429,"line":750},[427,3638,3497],{"class":433},[427,3640,3641],{"class":461}," start",[427,3643,499],{"class":433},[427,3645,3646],{"class":441}," Date.",[427,3648,3649],{"class":437},"now",[427,3651,867],{"class":441},[427,3653,3654,3657,3660],{"class":429,"line":774},[427,3655,3656],{"class":433},"  await",[427,3658,3659],{"class":437}," next",[427,3661,867],{"class":441},[427,3663,3664,3666,3668,3670,3673,3675,3677,3680,3683,3685,3687,3690,3693,3696,3698,3700,3703,3705,3707,3710],{"class":429,"line":820},[427,3665,1468],{"class":441},[427,3667,1471],{"class":437},[427,3669,451],{"class":441},[427,3671,3672],{"class":1476},"`${",[427,3674,3401],{"class":441},[427,3676,1814],{"class":1476},[427,3678,3679],{"class":441},"path",[427,3681,3682],{"class":1476},"} -> ${",[427,3684,3401],{"class":441},[427,3686,1814],{"class":1476},[427,3688,3689],{"class":441},"status",[427,3691,3692],{"class":1476},"} (${",[427,3694,3695],{"class":441},"Date",[427,3697,1814],{"class":1476},[427,3699,3649],{"class":437},[427,3701,3702],{"class":1476},"() ",[427,3704,651],{"class":433},[427,3706,3641],{"class":441},[427,3708,3709],{"class":1476},"}ms)`",[427,3711,817],{"class":441},[427,3713,3714],{"class":429,"line":837},[427,3715,476],{"class":441},[427,3717,3718],{"class":429,"line":842},[427,3719,483],{"emptyLinePlaceholder":482},[427,3721,3722,3724,3727,3729,3731,3733,3735,3737,3739,3741,3743,3745,3747],{"class":429,"line":847},[427,3723,489],{"class":433},[427,3725,3726],{"class":437}," auth",[427,3728,458],{"class":433},[427,3730,3394],{"class":437},[427,3732,499],{"class":433},[427,3734,3620],{"class":433},[427,3736,532],{"class":441},[427,3738,3401],{"class":454},[427,3740,797],{"class":441},[427,3742,3410],{"class":454},[427,3744,513],{"class":441},[427,3746,516],{"class":433},[427,3748,442],{"class":441},[427,3750,3751,3754,3757,3760,3762,3765,3767,3770,3773],{"class":429,"line":852},[427,3752,3753],{"class":433},"  if",[427,3755,3756],{"class":441}," (ctx.path.",[427,3758,3759],{"class":437},"startsWith",[427,3761,451],{"class":441},[427,3763,3764],{"class":1476},"'\u002Fadmin'",[427,3766,513],{"class":441},[427,3768,3769],{"class":433},"&&",[427,3771,3772],{"class":433}," !",[427,3774,3775],{"class":441},"ctx.user) {\n",[427,3777,3778,3781,3783],{"class":429,"line":870},[427,3779,3780],{"class":441},"    ctx.status ",[427,3782,733],{"class":433},[427,3784,3785],{"class":461}," 401\n",[427,3787,3788,3791,3793],{"class":429,"line":897},[427,3789,3790],{"class":441},"    ctx.body ",[427,3792,733],{"class":433},[427,3794,3795],{"class":1476}," 'Unauthorized'\n",[427,3797,3798,3800],{"class":429,"line":918},[427,3799,823],{"class":433},[427,3801,3802],{"class":893}," \u002F\u002F next() を呼ばない＝ここで処理を打ち切る\n",[427,3804,3805],{"class":429,"line":938},[427,3806,742],{"class":441},[427,3808,3809,3811,3813],{"class":429,"line":957},[427,3810,3656],{"class":433},[427,3812,3659],{"class":437},[427,3814,867],{"class":441},[427,3816,3817],{"class":429,"line":1563},[427,3818,476],{"class":441},[427,3820,3821],{"class":429,"line":1571},[427,3822,483],{"emptyLinePlaceholder":482},[427,3824,3825,3827,3830,3832,3834,3836,3838,3840,3842,3844,3846],{"class":429,"line":1590},[427,3826,489],{"class":433},[427,3828,3829],{"class":437}," handler",[427,3831,458],{"class":433},[427,3833,3394],{"class":437},[427,3835,499],{"class":433},[427,3837,3620],{"class":433},[427,3839,532],{"class":441},[427,3841,3401],{"class":454},[427,3843,513],{"class":441},[427,3845,516],{"class":433},[427,3847,442],{"class":441},[427,3849,3850,3853,3855],{"class":429,"line":2079},[427,3851,3852],{"class":441},"  ctx.status ",[427,3854,733],{"class":433},[427,3856,3857],{"class":461}," 200\n",[427,3859,3860,3863,3865,3868,3870,3872,3875,3878],{"class":429,"line":2084},[427,3861,3862],{"class":441},"  ctx.body ",[427,3864,733],{"class":433},[427,3866,3867],{"class":1476}," `Hello ${",[427,3869,3401],{"class":441},[427,3871,1814],{"class":1476},[427,3873,3874],{"class":441},"user",[427,3876,3877],{"class":433}," ??",[427,3879,3880],{"class":1476}," 'guest'}`\n",[427,3882,3883],{"class":429,"line":2089},[427,3884,476],{"class":441},[427,3886,3887],{"class":429,"line":2094},[427,3888,483],{"emptyLinePlaceholder":482},[427,3890,3891,3893,3896,3898,3900],{"class":429,"line":2099},[427,3892,489],{"class":433},[427,3894,3895],{"class":461}," app",[427,3897,499],{"class":433},[427,3899,3452],{"class":437},[427,3901,3902],{"class":441},"([logging, auth, handler])\n",[427,3904,3905],{"class":429,"line":2142},[427,3906,483],{"emptyLinePlaceholder":482},[427,3908,3909,3911,3913,3916,3919,3922],{"class":429,"line":2159},[427,3910,3249],{"class":433},[427,3912,3895],{"class":437},[427,3914,3915],{"class":441},"({ path: ",[427,3917,3918],{"class":1476},"'\u002Fadmin\u002Fsettings'",[427,3920,3921],{"class":441}," })                 ",[427,3923,3924],{"class":893},"\u002F\u002F \u002Fadmin\u002Fsettings -> 401\n",[427,3926,3927,3929,3931,3933,3935,3938,3941,3943],{"class":429,"line":2165},[427,3928,3249],{"class":433},[427,3930,3895],{"class":437},[427,3932,3915],{"class":441},[427,3934,3918],{"class":1476},[427,3936,3937],{"class":441},", user: ",[427,3939,3940],{"class":1476},"'kyosei'",[427,3942,1461],{"class":441},[427,3944,3945],{"class":893},"\u002F\u002F \u002Fadmin\u002Fsettings -> 200\n",[427,3947,3948,3950,3952,3954,3957,3960],{"class":429,"line":2171},[427,3949,3249],{"class":433},[427,3951,3895],{"class":437},[427,3953,3915],{"class":441},[427,3955,3956],{"class":1476},"'\u002Fblog'",[427,3958,3959],{"class":441}," })                           ",[427,3961,3962],{"class":893},"\u002F\u002F \u002Fblog -> 200\n",[10,3964,3965,3968,3969,3972],{},[402,3966,3967],{},"logging"," は ",[402,3970,3971],{},"await next()"," の前後に処理を書けるので、「後続の処理全体にかかった時間」を測れます。Koa のミドルウェアはまさにこの形で、Express のミドルウェアや Nuxt のルートミドルウェアも同じ考え方です。",[26,3974,3976],{"id":3975},"_9-singleton-には要注意","9. Singleton には要注意",[10,3978,3979,3980,3983],{},"Singleton は「インスタンスが1つだけであることを保証する」パターンで、23パターンの中でも特に有名です。ただし、現代ではむしろ ",[14,3981,3982],{},"慎重に使うべきもの"," とされています。",[1600,3985,3987],{"title":3986},"Singleton が招きやすい問題",[58,3988,3989,3995,4001],{},[61,3990,3991,3994],{},[14,3992,3993],{},"実質的なグローバル変数になる","：どこからでも読み書きでき、状態の変化を追いにくくなります。",[61,3996,3997,4000],{},[14,3998,3999],{},"テストしにくい","：テストごとに状態をリセットしたり、偽物（モック）に差し替えたりするのが難しくなります。",[61,4002,4003,4006],{},[14,4004,4005],{},"依存関係が見えなくなる","：関数やクラスの引数に現れないまま、内部で Singleton を使っていることがあります。",[10,4008,4009],{},"TypeScript（ES Modules）では、モジュールの中で作ったインスタンスは、そのモジュールが1回しか評価されないため、それだけで実質的に1つになります。わざわざ Singleton クラスを書く必要はほとんどありません。",[10,4011,4012,4013,4016,4017,4020,4021,4024],{},"それよりも、必要な部品を ",[14,4014,4015],{},"外から渡す（依存性の注入）"," 形にすると、テストで差し替えやすくなります。Vue の ",[402,4018,4019],{},"provide"," \u002F ",[402,4022,4023],{},"inject"," も、この考え方の仕組みです。",[26,4026,4028],{"id":4027},"_10-パターンはどこに溶け込んでいるか","10. パターンはどこに溶け込んでいるか",[10,4030,4031,4032,4035],{},"1996年、ピーター・ノーヴィグは講演で、GoF の23パターンのうち16は、第一級関数などを持つ動的な言語では ",[14,4033,4034],{},"不要になるか、ずっと簡単に書ける"," と指摘しました。多くのパターンは、当時の C++ や Smalltalk に足りなかった機能を補う工夫でもあったのです。",[10,4037,4038],{},"現代の TypeScript やフロントエンド開発では、次のようにパターンが言語やフレームワークに溶け込んでいます。",[115,4040,4041,4050],{},[118,4042,4043],{},[121,4044,4045,4047],{},[124,4046,132],{},[124,4048,4049],{},"溶け込んでいる場所",[134,4051,4052,4064,4081,4095,4102,4109,4119,4126,4136,4149],{},[121,4053,4054,4057],{},[139,4055,4056],{},"Strategy、Command",[139,4058,4059,4060,4063],{},"関数を値として渡す（コールバック、",[402,4061,4062],{},"sort"," の比較関数、イベントハンドラー）",[121,4065,4066,4068],{},[139,4067,328],{},[139,4069,4070,4073,4074,4077,4078],{},[402,4071,4072],{},"for...of","、ジェネレーター（",[402,4075,4076],{},"function*","）、",[402,4079,4080],{},"Symbol.iterator",[121,4082,4083,4085],{},[139,4084,352],{},[139,4086,4087,4089,4090,4020,4092,4094],{},[402,4088,1615],{},"、Vue の ",[402,4091,1626],{},[402,4093,1630],{},"、RxJS",[121,4096,4097,4099],{},[139,4098,304],{},[139,4100,4101],{},"Express \u002F Koa のミドルウェア、Nuxt のルートミドルウェア",[121,4103,4104,4106],{},[139,4105,272],{},[139,4107,4108],{},"高階関数（関数を受け取って、機能を足した関数を返す）",[121,4110,4111,4113],{},[139,4112,296],{},[139,4114,4115,4116,4118],{},"JavaScript の ",[402,4117,296],{},"（Vue 3 のリアクティビティもこれで作られている）",[121,4120,4121,4123],{},[139,4122,264],{},[139,4124,4125],{},"DOM ツリー、コンポーネントツリー",[121,4127,4128,4130],{},[139,4129,232],{},[139,4131,4132,4133],{},"JavaScript のプロトタイプチェーン、",[402,4134,4135],{},"structuredClone",[121,4137,4138,4140],{},[139,4139,280],{},[139,4141,4142,4144,4145,4148],{},[402,4143,2846],{}," のラッパー、composable（",[402,4146,4147],{},"useXxx","）で複雑な処理を1つの窓口にまとめる",[121,4150,4151,4153],{},[139,4152,240],{},[139,4154,4155,4156,4020,4158],{},"ES Modules のモジュールスコープ、",[402,4157,4019],{},[402,4159,4023],{},[10,4161,4162,4163,4166,4167,4169],{},"パターンを知っていると、こうした仕組みの ",[14,4164,4165],{},"設計意図"," が読めるようになります。「なぜ Vue は ",[402,4168,1630],{}," の戻り値で監視を止められるのか」は、Observer の「購読解除」を知っていれば自然に理解できます。",[26,4171,4173],{"id":4172},"_11-パターンとの付き合い方","11. パターンとの付き合い方",[983,4175,4177],{"title":4176},"パターンは「目的」ではなく「結果」",[10,4178,4179,4180,4183],{},"最初からパターンを当てはめようとすると、必要のない抽象化が増えて、かえって読みにくいコードになります。まずは素直に書き、",[14,4181,4182],{},"変更が繰り返し起きる場所"," が見えてきたら、そこにパターンを適用する（リファクタリングする）のがおすすめです。",[10,4185,4186],{},"判断に迷ったときは、次の問いが役に立ちます。",[58,4188,4189,4196,4202],{},[61,4190,4191,4192,4195],{},"その ",[14,4193,4194],{},"変化は本当に起きるか","？（「将来増えるかもしれない」だけでは、まだ早いことが多い）",[61,4197,4198,4201],{},[14,4199,4200],{},"関数1つ"," で済まないか？",[61,4203,4204,4205,4208],{},"チームの全員が、そのパターンの ",[14,4206,4207],{},"名前と意図"," を共有しているか？",[26,4210,4212],{"id":4211},"_12-デザインパターンの歴史","12. デザインパターンの歴史",[10,4214,4215,4216,4219],{},"デザインパターンの考え方は、ソフトウェアではなく ",[14,4217,4218],{},"建築"," から始まりました。",[4221,4222],"timeline",{":show-header":4223,"name":4224},"false","design-patterns",[26,4226,4227],{"id":4227},"まとめ",[58,4229,4230,4237,4240,4243,4250],{},[61,4231,4232,4233,4236],{},"デザインパターンは、繰り返し現れる設計の問題と解決策に ",[14,4234,4235],{},"名前を付けて共有"," するためのもの。",[61,4238,4239],{},"GoF の23パターンは「生成」「構造」「振る舞い」の3つに分類される。根底には「インターフェースに対してプログラムせよ」「継承よりコンポジション」という原則がある。",[61,4241,4242],{},"Strategy、Observer、Factory Method、Adapter、Decorator、Chain of Responsibility は、現代の TypeScript でも出番が多い。",[61,4244,4245,4246,4249],{},"多くのパターンは、関数やフレームワークの仕組みとして ",[14,4247,4248],{},"すでに溶け込んでいる","。パターンを知ることは、それらの設計意図を読むことにつながる。",[61,4251,4252],{},"パターンは目的ではない。必要になったときに、必要な分だけ使う。",[4254,4255,4256],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":423,"searchDepth":445,"depth":445,"links":4258},[4259,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271,4272,4273],{"id":28,"depth":445,"text":29,"children":4260},[4261],{"id":82,"depth":473,"text":83},{"id":109,"depth":445,"text":110},{"id":393,"depth":445,"text":394},{"id":1144,"depth":445,"text":1145},{"id":1635,"depth":445,"text":1636},{"id":2199,"depth":445,"text":2200},{"id":2632,"depth":445,"text":2633},{"id":3317,"depth":445,"text":3318},{"id":3975,"depth":445,"text":3976},{"id":4027,"depth":445,"text":4028},{"id":4172,"depth":445,"text":4173},{"id":4211,"depth":445,"text":4212},{"id":4227,"depth":445,"text":4227},"2026-11-14","GoF の23パターンの全体像を整理し、現代の TypeScript やフロントエンド開発でも役立つ6つのパターンを、動くコードとともに解説します。使いすぎの落とし穴と、言語やフレームワークに溶け込んだパターンの見つけ方も。",false,"md",null,{},"\u002Fblog\u002F003-design-patterns",{"title":5,"description":4275},"blog\u002F003-design-patterns",[4284,4285,4286],"design-pattern","programming","typescript","Lp8Ttpvl2oM4b2LtqaWNZmMdv3C6oSO3Lz3Discycig",[4289,4297,4299,4304],{"path":4290,"title":4291,"no":479,"date":4274,"updated":4278,"image":4278,"description":4292,"tags":4293,"pinned":4276,"draft":4276},"\u002Fblog\u002F004-completeness-theorem-proofs","完全性定理の2つの証明 ― ゲーデル流とヘンキン流","一階述語論理の完全性定理を、ゲーデルによる元の証明（1929–1930年）と、今日の教科書の標準であるヘンキンの証明（1949年）の2つの道筋で解説します。どちらも「記号からモデルを作る」点は同じですが、足場の置き方が大きく異なります。",[4294,4295,4296],"logic","math","history",{"path":4280,"title":5,"no":473,"date":4274,"updated":4278,"image":4278,"description":4275,"tags":4298,"pinned":4276,"draft":4276},[4284,4285,4286],{"path":4300,"title":4301,"no":445,"date":4274,"updated":4278,"image":4278,"description":4302,"tags":4303,"pinned":4276,"draft":4276},"\u002Fblog\u002F002-mathematical-logic-timeline","年表で見る20世紀の数理論理学 ― ゲーデルとタルスキ","不完全性定理と真理の定義不可能性定理を軸に、20世紀の数理論理学の流れを年表で整理します。",[4294,4296,4221],{"path":4305,"title":4306,"no":430,"date":4274,"updated":4278,"image":4278,"description":4307,"tags":4308,"pinned":4276,"draft":4276},"\u002Fblog\u002F001-backpropagation","バックプロパゲーション（誤差逆伝播法）を数式から理解する","ニューラルネットワークの学習を支えるバックプロパゲーションを、連鎖律から4つの基本式を導くところまで丁寧に解説します。数値例と NumPy の実装つき。",[4309,4295],"machine-learning",{"id":4311,"title":4312,"description":4313,"events":4314,"extension":4369,"legend":4370,"meta":4374,"stem":4375,"__hash__":4376},"timelines\u002Ftimelines\u002Fdesign-patterns.yml","デザインパターンの歴史","建築家クリストファー・アレグザンダーの「パターン・ランゲージ」から、GoF の『デザインパターン』、そしてアーキテクチャやドメイン設計のパターンへ。ソフトウェアの設計知識が「名前の付いた解決策」として共有されるようになった流れをたどる。",[4315,4319,4323,4327,4333,4336,4340,4344,4348,4352,4356,4360,4364],{"date":4316,"title":4317,"description":4318},"1977","アレグザンダー『パタン・ランゲージ』","建築家クリストファー・アレグザンダーらが、町や建物の設計で繰り返し現れる問題と解決策を253のパターンとしてまとめた。「問題・状況・解決策」に名前を付けて共有するという考え方が、のちにソフトウェアへ持ち込まれる。",{"date":4320,"title":4321,"description":4322},"1979","アレグザンダー『時を超えた建設の道』","パターン・ランゲージの背後にある思想を論じた。良い設計には言葉にしにくい「名づけ得ぬ質」があり、パターンはそれを生み出すための手段であるとした。",{"date":4324,"title":4325,"description":4326},"1987","ベックとカニンガム、パターンをプログラムに応用","ケント・ベックとウォード・カニンガムが、OOPSLA '87 のワークショップで、アレグザンダーのパターン・ランゲージをオブジェクト指向プログラムの設計に使う試みを発表した。",{"date":4328,"title":4329,"description":4330,"tags":4331},"1994","GoF『デザインパターン』出版","ガンマ、ヘルム、ジョンソン、ブリシディースの4人（Gang of Four）が、オブジェクト指向設計の23パターンを生成・構造・振る舞いの3分類で体系化した。「実装ではなくインターフェースに対してプログラムせよ」「継承よりコンポジションを優先せよ」という原則も示した。",[4332],"gof",{"date":4328,"title":4334,"description":4335},"第1回 PLoP（Pattern Languages of Programs）開催","パターンを書き、互いに批評し合うための会議が始まった。著者同士が原稿を読み合う「ライターズ・ワークショップ」の形式が定着する。",{"date":4337,"title":4338,"description":4339},"1996","POSA『パターン指向ソフトウェアアーキテクチャ』","ブッシュマンらが、Layers、Pipes and Filters、Broker、Model-View-Controller などのアーキテクチャパターンをまとめた。クラスの設計より大きな単位でパターンを扱う流れが生まれる。",{"date":4337,"title":4341,"description":4342,"tags":4343},"ノーヴィグ「動的言語におけるデザインパターン」","ピーター・ノーヴィグが講演で、GoF の23パターンのうち16は、第一級関数やマクロを持つ言語では不要になるか、ずっと簡単に書けると指摘した。パターンの多くが言語の機能不足を補うものだという見方を広めた。",[4332],{"date":4345,"title":4346,"description":4347},"1998","『アンチパターン』出版","ブラウンらが、うまくいかない解決策が繰り返される様子を「アンチパターン」として整理した。何をすべきでないかにも名前を付けて共有する考え方が広まる。",{"date":4349,"title":4350,"description":4351},"1999","ファウラー『リファクタリング』","外から見た振る舞いを変えずにコードの構造を改善する手法を、名前の付いた手順の集まりとしてまとめた。既存のコードを少しずつパターンへ近づける道筋が示された。",{"date":4353,"title":4354,"description":4355},"2002","ファウラー『エンタープライズアプリケーションアーキテクチャパターン』","Repository、Unit of Work、Active Record、Data Mapper など、業務システムでデータベースや層構造を扱うためのパターンをまとめた。",{"date":4357,"title":4358,"description":4359},"2003","エヴァンス『ドメイン駆動設計』と『エンタープライズ統合パターン』","エヴァンスは Entity、Value Object、Aggregate、Repository などで業務の知識をモデルに表す方法を、ホーペとウルフはメッセージングによるシステム連携の65のパターンを示した。",{"date":4361,"title":4362,"description":4363},"2004-01","ファウラー「依存性の注入（Dependency Injection）」","制御の反転（IoC）コンテナの考え方を整理し、「依存性の注入」という名前を広めた記事を公開した。Singleton に頼らずに部品を組み立てる方法として定着する。",{"date":4365,"title":4366,"description":4367,"tags":4368},"2004","『Head First デザインパターン』","図や会話を多用した入門書として、GoF のパターンを多くの開発者に広めた。",[4332],"yml",[4371],{"key":4332,"label":4372,"color":4373},"GoF の23パターン関連","#7048c2",{},"timelines\u002Fdesign-patterns","LbqPuNQZ97gWpj3ernpYRvFRYDrpaRLDGFwuriQTe6A",1794635675661]