テーマ、知りたいこと
JavaScriptでクラス化すべきかどうか悩んでいます。
文末にある「対処案:モジュールのまま」か「対処案:クラス化する」について、どちらにすべきか(どちらにどういう利点があり、どういうケースで優先すべきか)ご意見いただけましたら幸いです。
背景、状況
以下のように、あるエリアについて「色々な処理のファイル」と「HTMLテンプレートのファイル」というペア構成でモジュールファイルを作りました。エリアを超えて類似処理がある場合にクラス化すべきかどうかが判断できない状況です。
エリア | 色々な処理のファイル | HTMLテンプレートのファイル |
---|---|---|
.xxx-area | xxxAreaManager.js | xxxAreaHtml.js |
コードは以下のような感じです。(最低限のイメージを伝えるためのもので実際は数百行ありますが、それぞれの処理を詳細にお読みいただく必要はないかと思います。)
JavaScript
1/* 2 このファイル名は xxxAreaManager.js です 3 .xxx-area エリアについての色々な処理が書かれています 4*/ 5 6// 各種 import 7import util from '/assets/js/area/utiljs'; 8import xxxAreaSearchConditions from '/assets/js/area/xxxAreaSearchConditions.js'; 9import xxxAreaHtml from '/assets/js/area/xxxAreaHtml.js'; 10 11// エリア名 12const areaName = 'xxx-area'; 13 14// エリア表示 15function setAreaHtml(){ 16 document.quertySelector('.target') 17 .insertAdjacentHTML('afterbegin', xxxAreaHtml.areaHtml()); 18} 19 20// ソートメニュー表示切替 21function toggleDisplaySortMenu(){ 22 document.quertySelector('.sort-menu') 23 .classList.toggle('is-hide'); 24} 25 26// Ajax でアイテムを取得し HTML を作りセット 27async function fetchItemsAndSetHtml(){ 28 const queryString = xxxAreaSearchConditions.generateQueryString(); 29 const response = await util.getAjax('/item/get?' + queryString ); 30 document.quertySelector('.items') 31 .insertAdjacentHTML('afterbegin', xxxAreaHtml.itemsHtml(response.items)); 32} 33 34export default { setAreaHtml, toggleDisplaySortMenu, fetchItemsAndSetHtml }
JavaScript
1/* 2 このファイル名は xxxAreaHtml.js です 3 .xxx-area エリアのHTMLテンプレートが書かれています 4*/ 5 6// 各種 import 7import util from '/assets/js/area/util.js'; 8import utilHtml from '/assets/js/area/utilHtml.js'; 9 10// エリア名 11const areaName = 'xxx-area'; 12 13// エリアのHTMLテンプレート 14function areaHtml(){ 15 return ` 16 <div class="${areaName}"> 17 ${sortMenuHtml()} 18 <div class="others">${utilHtml.esc('その他いろいろ')}</div> 19 </div>`; 20} 21 22// ソートメニューのHTMLテンプレート 23function sortMenuHtml(){ 24 return `<ul class="sort-menu"></ul>`; 25} 26 27// アイテムのHTMLテンプレート 28function itemsHtml(items) { 29 return items.map(item => `<div>${utilHtml.esc(item.name)}</div>`).join(''); 30} 31 32export default { areaHtml, itemsHtml }
そしてこのようなエリアごとのペアが大量にあります。(各ファイル名をきちんと一読される必要はありません。ざっくり.main-xxx-area
系と.library-xxx-area
系があります。)
エリア | 色々な処理のファイル | HTMLテンプレートのファイル |
---|---|---|
.main-books-area | mainBooksAreaManager.js | mainBooksAreaHtml.js |
.main-users-area | mainUsersAreaManager.js | mainUsersAreaHtml.js |
.main-recommend-books-area | mainRecommondBooksAreaManager.js | mainRecommondBooksAreaHtml.js |
.library-books-area | libraryBooksAreaManager.js | libraryBooksAreaHtml.js |
.library-followers-area | libraryFollowersAreaManager.js | libraryFollowersAreaHtml.js |
などなど・・
質問
しかし、いくつかのファイルにはかなり類似の関数が見られます。
例えばsortMenuHtml()
について、mainBooksAreaHtml.js では「人気順と作成順」ですし、
JavaScript
1function sortMenuHtml(){ 2 return ` 3 <ul class="sort-menu"> 4 <li>人気順</li><li>作成順</li> 5 </ul>`; 6}
また libraryBooksAreaHtml.js では「更新順と作成順」といった感じです。
JavaScript
1function sortMenuHtml(){ 2 return ` 3 <ul class="sort-menu"> 4 <li>更新順</li><li>作成順</li> 5 </ul>`; 6}
そこで共通処理を担う上位ファイルが必要だろうとなったのですが、モジュールのままで上位ファイルを作るか、すべてクラス化し継承関係を持たせるべきか、というのが全くわかりません。
対処案:モジュールのまま
クラス化しなければ上位ファイル commonAreaManager.js と commonAreaHtml.js などを作り、例えば上のようなメニュー関数ならこのように書くかと思います。
JavaScript
1/* 2 このファイル名は commonAreaHtml.js です 3 各エリアで共通するHTMLテンプレートが書かれています 4*/ 5 6function sortMenuHtml(areaName){ 7 if (areaName === 'main-books-area') { 8 return ` 9 <ul class="sort-menu"> 10 <li>人気順</li><li>作成順</li> 11 </ul>`; 12 } else if (areaName === 'library-books-area') { 13 return ` 14 <ul class="sort-menu"> 15 <li>更新順</li><li>作成順</li> 16 </ul>`; 17 } 18} 19 20export { sortMenuHtml }
しかしこの対処案では、「この commonAreaHtml.js は各エリアの上位ファイルなのか?それとも .main-books-area
などと同じように .common-area
というエリアがサイトにあるのか?」というのが分かりにくく、(コメントを見れば済む話ですが)この点がいまいちだと思います。
対処案:クラス化する
一方でクラス化すると以下のconstructor
でのthrow
によって、「これは必ず抽象クラスであって.common-area
というエリアがサイトにあるわけではない」ということが強制できる点が良さそうだと思いました。
JavaScript
1/* 2 このファイル名は CommonAreaHtml.js です 3 各エリアで共通するHTMLテンプレートが書かれています 4*/ 5 6export default class CommonAreaHtml 7{ 8 constructor(areaName) { 9 this.areaName = areaName; 10 11 if (new.target === CommonAreaHtml) { 12 throw new TypeError("Cannot instantiate abstract class"); 13 } 14 } 15 16 sortMenuHtml(){ 17 if (this.areaName === 'main-books-area') { 18 // 同上 19 } else if (this.areaName === 'library-books-area') { 20 // 同上 21 } 22 } 23}
そして各エリアのファイルも以下のようにCommonAreaHtml
を継承した上でクラス化し、モジュールではconst areaName = ''
と宣言していたものをsuper('')
と宣言します。
JavaScript
1/* 2 このファイル名は MainBooksAreaHtml.js です 3 .main-books-area エリアのHTMLテンプレートが書かれています 4*/ 5 6export default class MainBooksAreaHtml extends CommonAreaHtml 7{ 8 constructor() { 9 super('main-books-area'); 10 } 11 12 // 略 13}
しかしクラスをこういう使い方していいのかなという疑念があります。クラスというのは「なんからの状態を持たせ、処理の過程でそれが変わっていくようなケースで用いるものだ」という理解をしているためです。
今回のようにただHTMLテンプレートを返すだけとか、単発的な処理を行うだけのケースにすぎないのに、抽象クラスであることの明示と強制という目的で用いていいのだろうか?という疑念です。
まとめ
ここまでお読みいただきありがとうございます。
以上のように「対処案:モジュールのまま」か「対処案:クラス化する」について、どちらにすべきかという判断ができない状況です。
この点のアドバイスはもちろん、「そもそも全体的な設計がおかしい」など根本的なご指摘も大募集です。
なるべく必要最低限にと思いコードは各所で割愛しておりますが、これだけでは判断がつかないだとか、情報の不足などございましたらご指摘ください。
よろしくお願い致します。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。