classとmodule、どちらを使うべき?(TypeScript)
https://gyazo.com/b3128bdfacb9d81dacefa8adbb583607
前提
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を使う場面というのは
上記の比較に倣って考えるなら
JSだとこれがかなりでかいtakker.icon
状況で考えると
同じ処理を複数箇所から(もしくは一箇所から何度も)呼び出して、なおかつそれぞれに異なった状態をもたせる必要がある場合
一言で言えないほど複雑で多岐に渡る機能を1つにまとめたい場合
一方でmoduleを使う場面というのは
classを使用する必要がない場面全て
classを使用するだけで(7番目の理由で)コードが複雑化するから
むやみに状態をもたせるような構造にすることはデバッグやテストの難易度を上げるから
※状態を持たないclassも作れるけれど、あまりメリットがない
状態を持たない関数しかない場合
関数外に状態を持っていたとしても呼び出し元全てから同じ状態を参照していても問題のない場合
他の言語で言うクラス変数のみで事足りる場合
どちらかとかではなく併用すべき
それはそうMijinko_SD.icon
このページのコンテキストとしては、一番上に書いてある通り「どちらを使うべきか迷った時の判断基準を考える」が前提にある
個人的にはclassのように末端で状態を持ったコードは保守性の観点で悪であると考えているので、基本的にはclassを使わない ページの本筋とはずれるけど
同じ引数を得たら同じ値を返す関数(関数型的なもの)が一番読みやすいと考えている
(どうしても必要な場合でも)状態を持つのは大元(もしくはDBやストレージ)だけにしてできるだけ最小限にしたい
末端にまで状態を持たせると、どこの値がどこに影響しているのかがわからなくなる
副作用が酷いライブラリだと、とあるプロパティを変更した時に継承元内部の別のプロパティが暗黙的に変更(最適化)されて、何もしてないのに壊れた状態になったことがある クラスの継承も同様で、継承元はここで言う末端と同じ立ち位置なので、結局読みづらくなる
役割を明確にすれば可読性は上がる。けれどそれは運用に依る部分も大きいから技量や経験で差が出る
クラスとメンバ変数が乱立して相互作用し合っている地獄のようなコードを見たことがあるが、あれは実行してデバッグする以外にまともに読む方法あるのだろうか
ならmoduleのトップレベルにある変数はどうなのかという話も出てきそうだが、それも上記と同じ理由で悪と考えている
インスタンス以外の定数は除く
それでも使わなければならない場面はあるので、不要な存在ではないけれど
大量にある非同期のワーカーに任せる処理をカプセル化したい時 つまりこういう時
枝先に個別の状態を持たせなければならない
その上で、スコープの観点から親に全ての状態をオブジェクトとして持たせるのが保守性の観点から適切ではない
もしくは、非同期処理のメモリにアクセスできない
別のスコープから内部の変数にアクセスする必要はない
完全に独立させた状態で一連の処理を複数同時に回したい
殆どのコードは同期的(もしくは一時的に非同期)なので、機会としてはそこまで多くない
結局色々言ったけど、classを使うか否かに関しては、
総合的に見て保守性(もしくは可読性)が上がる根拠があればclass
そうではない場合、もしくは曖昧な場合は上記判断基準に従って極力classを使わない
かな
Summer498.icon
「状態を持つ」の「状態」って何やろ
mutable member?
readonly member なら状態じゃないと思うし
同じメソッドの挙動が変わる原因のことか
必要な値がセットされていません!まずは setHoge を用いてください
うん。こういうのは作ったことある。
あのときは仕方がなかったんだ。もう直したから許せ。
constructor で値をキッチリ決めなさいよと
ほぼ mutable member のことでいいと思うけどこれに着目するのは本質的ではない
点 P が移動するたびに member x, y が変化するのは、大した問題ではない
逆に点 P が移動するたびに new 点 P するか?
バケツリレーみたいに新しい点 P のポインタを渡す必要がある
面倒そう
sta.icon
typescriptのclassとmoduleは覚えてないけど、一般論としてクラスとモジュールのどっちを使うか、で議論しても良さそう
typescriptも要は双方にできることとできないことが多少あるという程度
限度はあるが、工夫すればどうとでもなる
いや、これは一般論にしないほうがいいと思いますtakker.iconMijinko_SD.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
TypeScriptのclassはJavaのclassとは違い、内部的にはprototypeだったはず ただtypescriptはprototypeで使うことを想定していない気がするが...
それはそれとして、併用すべきでは
併用するデメリットがない
Summer498.icon
双方を比べる際に、
「どうしても〇〇したい場合は」とか「XXする必要がある」と出てきているところは、
その時点で分が悪いから他方を使えばよいのではないか
https://gyazo.com/1b3b642c7f460d583d34e0b4c6a650be
class つかえ
https://gyazo.com/52d43e60dcd8a219e7c3e81cef1d7aa4
module 使え
あと、static がついてない関数は内部で this が使われているはず
そうでなければ static をつけるか、そもそも関数として外部に取り出してある方が自然
つまり、static がついていない関数をインスタンスを介して呼び出さなくちゃいけないよ~><という状況そのものがおかしい
てか static 関数って何よ。class のことネームスペースだと思ってるんか
シングルトンクラスでしか使ったこと無いわ
プライベートプロパティ・メソッドが使えるのが特徴
大量のインスタンスを並列生成したいとき?
まさに俺がやりたいことでは?
そういうものではなく、クラスの作成時に通信してクラス内容を取得したかったbsahd.icon
コンストラクタ使いたい時がクラス使いたいときだろうな
claude.icon
モジュールとクラスは互いに排他的なものではなく、多くの場合は併用します。
モジュールシステムを使ってコードを整理し、必要に応じてモジュール内でクラスを定義・エクスポートすることで、両方の利点を活かすことができます。
よく想像できてないのですが、1つのmoduleに1つのclassとそれ以外を同時に入れることがあるということですか?Mijinko_SD.icon
gpt.icon 4o mini(chat)
はい、TypeScriptでは1つのモジュール(module)に1つのクラスとその他の要素(例えば関数や変数)を同時に含めることができます。モジュール内に複数のクラスを含めることもできますし、クラスと関数や変数を一緒に定義することも可能です。
このように、MyClass クラスと myFunction 関数、myVariable 変数を同じモジュール内に定義できます。モジュールは、必要に応じてクラスだけでなく、他の要素も同時に管理できます。
ただし、モジュールが大きくなりすぎると管理が難しくなるので、一般的には関連する機能を持つクラスや関数を同じモジュールにまとめることが推奨されます。
関連してそうなページ
中立派
module派(もしくは非class派)
class派(もしくは非module派)