classとmodule、どちらを使うべき?(TypeScript)
前提
classかmoduleかに関わらず、常に合理的な記法を選択するものとする
日常生活でまず使わないような回りくどい書き方は避ける
JavaScriptのclassはスコープ周りの機能が貧弱なので、TypeScriptを基準として考える
ここでのmoduleとは、ファイル単位で分割されたコードのことを指す
関数の中に入れ子で関数を入れることや、prototypeに何かしらを代入するのは今回禁止とする アクセス修飾子の無い劣化版classのようなものなので、それをするならclassを選択すべきと考えるから
ごめんなさいやっちゃってますbsahd.icon
推奨はし難いですが……
双方の特徴
1. 複数の関数を実装する
module: できる
class: できる
2. 関数や変数を外部から参照できるようにしたりできなくしたりする
module: できる(参照可能なものにはexportをつける)
class: できる(参照不可なものにはprotectedかprivateをつける)
3. 1つの変数(letかconst)をclass/module内の複数の関数から参照する
module: できる
class: できる
4. 上記の変数の中に、呼び出し元ごとに別々の値を格納する(インスタンス化)
module: できない
どうしても値(状態)を持たせたい場合は、呼び出し元でその値を保持しておいて、呼び出し時に引数に渡すという手もある
この手法で呼び出し元のコードが煩雑にならないのであれば実用上は問題ない
class: できる
5. 既存のmodule/classを元にして、一部の処理を書き換えた別のmodule/classを実装する
module: できる
ただし元のmoduleが必要な関数・変数をすべてexportしている必要がある
class: できる
元のclassを継承して実装する
ただし元のclassが継承先に必要な関数・フィールドにprivateがつけられていないことが条件
こういうの:foo.bar().baz().qux()
module: できない(多分)
できないtakker.icon
foo.qux(foo.baz(foo.bar()))ならできるbsahd.icon
class: できる
7. class/module内部の関数を外部から直接importする
module: できる(exportされている関数のみ)
class: 直接は無理
staticがついている関数であっても大本のclassのimportを介す必要がある code:ts
import FooClass from "./foo.js"
FooClass.foo()
code:ts
import FooClass from "./foo.js"
const foo = FooClass.foo
foo()
staticがついていない関数はさらにインスタンスも介す必要がある
code:ts
import FooClass from "./foo.js"
const foo = new FooClass()
foo.bar()
コメント
Mijinko_SD.icon
まとめてみて、機能面ではclassはmoduleより色々できるということがわかった気がする
けれど、普段からそんないろんな機能を使うことが無い上、7番みたいにclassを使うとむしろ長くなったり、classがあることによってインデントが深くなったりする
なので、コードの簡潔さという面ではやはりclassよりmoduleの方が優れていそう
classを使う場面というのは
上記の比較に倣って考えるなら
インスタンスが必要な場面
ここが一番大きいと感じたwogikaze.icon
もう少し言葉を補うと「複数のインスタンスが異なる状態になることが必要な場面」nishio.icontakker.icon
なので複数箇所から呼ばれるとしても内部状態を持たないならインスタンスである必要がない
内部状態が1つだけの場合もtakker.icon
このときは
code:lib.ts
const stack: string[] = [];
export const pop = (): string | undefined => stack.pop();
export const push = (item: string): void => stack.push(item);
JSだとこれがかなりでかいtakker.icon
状況で考えると
同じ処理を複数箇所から(もしくは一箇所から何度も)呼び出して、なおかつそれぞれに異なった状態をもたせる必要がある場合
一言で言えないほど複雑で多岐に渡る機能を1つにまとめたい場合
一方でmoduleを使う場面というのは
classを使用する必要がない場面全て
classを使用するだけで(7番目の理由で)コードが複雑化するから
むやみに状態をもたせるような構造にすることはデバッグやテストの難易度を上げるから
※状態を持たないclassも作れるけれど、あまりメリットがない
状態を持たない関数しかない場合
関数外に状態を持っていたとしても呼び出し元全てから同じ状態を参照していても問題のない場合
他の言語で言うクラス変数のみで事足りる場合
sta.icon
typescriptのclassとmoduleは覚えてないけど、一般論としてクラスとモジュールのどっちを使うか、で議論しても良さそう
typescriptも要は双方にできることとできないことが多少あるという程度
限度はあるが、工夫すればどうとでもなる
いや、これは一般論にしないほうがいいと思いますtakker.icon
class/struct/moduleはいろんな言語で採用されているが、言語ごとに仕様が若干異なり、大きな差を生むことがある
いや、やっぱ一般論でもいいかも
いろんな言語にわたって使われているパターンを抽出して、そのパターンを議論すればいい
大体パターンPで呼んだ瞬間に言語Xのユーザが考えるPと言語Yのユーザが考えるPが食い違って不毛な論争が始まるnishio.icon
両方同じPって言葉で違うものを指してて、相手のことを自分よりPのことをわかってないやつだと思ってるから水掛け論になる
各言語でそれをどんな機能で実装するかは、各言語によって変わる
例えば
Rustではenumで定数の集まりをよく定義するが、TypeScriptはただの定数値を使うほうが簡潔 classにすると、たとえコード中で一切使われないメソッドだったとしても、bundleで削除されず残ってしまう
TypeScriptにおけるclass不要の主張はtree shakingの問題が大きいと思っているtakker.icon 一方Rust/C++は(最適化オプションを使っている場合)ランタイムで使われないコードはすべて削除される(はず)
class/structを使っても、(仮想関数テーブルなどランタイムに影響するものを除けば)関数ベースで書くのと比べてオーバーヘッドはほとんどないはず
(これを踏まえると、このページはTypeScriptで具体的な実装に落とし込むときの機能選定の話題に絞っていそうだから、ここのツリーは切り出すとよさそう)
僕はclass派だなぁ
でも昨今は(Golangくらいからか?)OOPよりも構造体とそれくくるモジュール的な単位くらいでいいよね、の流れも感じる OOPだとクラスのつくりかたで宗教論争になるイメージ、まあ言葉や概念を設計するに等しいからなぁ 一方、昨今のやり方だともうちょっと雑に組める
pythonみたいなLLばっか使ってる僕
静的みたいにコンパイルしたらわかる、とかないから品質を担保するための仕組みをコードに入れる必要がある、煩雑になりがち……
特にテストコード絡めると実装量は少なくとも二倍くらいにはなる
でも静的だとそこまでやらなくていいから、仕組みつくるための複雑な機構(class)が要らないのでは説
bsahd.icon
関連してそうなページ
中立派
module派(もしくは非class派)
class派(もしくは非module派)