クラスのいくつかのメソッドで共通のデータを使用する必要がある場合、これらの2つの方法のどちらを使用するかを決めるのに苦労することがよくあります。何がより良い選択でしょうか?
このオプションでは、インスタンス変数を作成して、追加の変数を宣言する必要性を回避し、メソッドパラメーターの定義を回避することもできますが、これらの変数がインスタンス化/変更される場所はそれほど明確ではありません。
public class MyClass {
private int var1;
MyClass(){
doSomething();
doSomethingElse();
doMoreStuff();
}
private void doSomething(){
var1 = 2;
}
private void doSomethingElse(){
int var2 = var1 + 1;
}
private void doMoreStuff(){
int var3 = var1 - 1;
}
}
または、ローカル変数をインスタンス化して引数として渡すだけですか?
public class MyClass {
MyClass(){
int var1 = doSomething();
doSomethingElse(var1);
doMoreStuff(var1);
}
private int doSomething(){
int var = 2;
return var;
}
private void doSomethingElse(int var){
int var2 = var + 1;
}
private void doMoreStuff(int var){
int var3 = var - 1;
}
}
どちらも正しいという答えである場合、どちらがより頻繁に見られ/使用されますか?また、各オプションに追加の長所/短所を提供できる場合は、非常に価値があります。
これがまだ言及されていないことに驚いています...
var1
は実際にはオブジェクトのstateの一部です。
あなたは、これらのアプローチの両方が正しく、それが単なるスタイルの問題であると想定しています。あなたは間違っている。
これは完全に適切にモデル化する方法についてです。
同様に、private
インスタンスメソッドはオブジェクトの状態を変更に存在します。それがあなたのメソッドがしていることではない場合、それはprivate static
。
どちらがより一般的かはわかりませんが、常に後者を使用します。データフローとライフタイムをより明確に伝達し、関連するライフタイムのみが初期化中のフィールドを持つクラスのすべてのインスタンスを膨らませません。前者は混乱を招くだけで、コードのレビューが非常に難しくなります。メソッドがvar1
を変更する可能性を考慮する必要があるためです。
変数のscopeを可能な限り(そして合理的に)削減する必要があります。メソッドだけでなく、一般的に。
質問については、変数がオブジェクトの状態の一部であるかどうかによって異なります。はいの場合、そのスコープ、つまりオブジェクト全体で使用しても問題ありません。この場合、最初のオプションを使用します。いいえの場合、変数の可視性を減らし、全体的な複雑さを減らすため、2番目のオプションを使用します。
Javaでより良いスタイル(インスタンス変数と戻り値)
別のスタイルがあります-コンテキスト/状態を使用します。
public static class MyClass {
// Hold my state from one call to the next.
public static final class State {
int var1;
}
MyClass() {
State state = new State();
doSomething(state);
doSomethingElse(state);
doMoreStuff(state);
}
private void doSomething(State state) {
state.var1 = 2;
}
private void doSomethingElse(State state) {
int var2 = state.var1 + 1;
}
private void doMoreStuff(State state) {
int var3 = state.var1 - 1;
}
}
このアプローチには多くの利点があります。状態オブジェクトは、オブジェクトとは関係なく変更できるため、将来のために大きな余地を与えることができます。
これは、一部の詳細を呼び出し間で保持する必要がある分散/サーバーシステムでもうまく機能するパターンです。ユーザーの詳細、データベース接続などをstate
オブジェクトに格納できます。
それは副作用についてです。
var1
は、この質問の要点を逃した州の一部です。確かにvar1
は永続化する必要があります。インスタンスである必要があります。どちらのアプローチでも、永続化が必要かどうかに関係なく機能します。
副作用アプローチ
一部のインスタンス変数は、呼び出しから呼び出しまでのプライベートメソッド間の通信にのみ使用されます。この種のインスタンス変数は存在しないようにリファクタリングできますが、そうである必要はありません。時には、物事がより明確になります。しかし、これにはリスクがないわけではありません。
変数が2つの異なるプライベートスコープで使用されているため、変数をスコープから外しています。配置するスコープで必要なためではありません。これは混乱を招く可能性があります。 「地球儀は悪だ!」混乱のレベル。これは機能しますが、うまくスケーリングしません。それは小さなものでのみ機能します。大きなオブジェクトはありません。長い継承チェーンはありません。 yo yo 効果を引き起こさないでください。
機能的アプローチ
さて、var1
何も永続化する必要はありません。すべての一時的な値がパブリックコール間で保持したい状態に達する前に、一時的な値が発生する可能性がある場合は、それを使用する必要があります。つまり、var1
インスタンスは、より機能的なメソッドのみを使用します。
これらの例では、 'var1'はカプセル化されているため、デバッガーはそれが存在することを認識しています。あなたが私たちにバイアスをかけたくないので、私はあなたが意図的にそれをしたと思います。幸い、どちらでもかまいません。
副作用のリスク
そうは言っても、あなたの質問がどこから来ているのか私は知っています。私は惨めな yo yo 'ing継承の下で働き、インスタンス変数を複数のメソッドの複数のレベルで変更し、それに追従しようとしました。これがリスクです。
これが私をより機能的なアプローチに駆り立てる苦痛です。メソッドは、その依存関係をドキュメント化し、シグネチャに出力できます。これは強力で明確なアプローチです。また、プライベートメソッドに渡すものを変更して、クラス内で再利用しやすくすることもできます。
副作用の利点
また、制限もあります。純粋な関数には副作用がありません。これは良いことですが、オブジェクト指向ではありません。オブジェクト指向の大部分は、メソッドの外部のコンテキストを参照する機能です。ここ全体でグローバルをリークすることなくそれを行うことは、OOPの強みです。グローバルの柔軟性が得られますが、クラスにうまく含まれています。必要に応じて、1つのメソッドを呼び出して、すべてのインスタンス変数を一度に変更できます。その場合は、少なくともメソッドにその状況を明確にする名前を付けて、人々がそれが起こっても驚かないようにする義務があります。コメントも役立ちます。時々これらのコメントは「ポスト条件」として形式化されます。
関数型プライベートメソッドの欠点
機能的アプローチにより、some依存関係が明確になります。純粋な関数型言語でない限り、隠れた依存関係を除外することはできません。メソッドのシグネチャだけを見ても、それがコードの残りの部分で副作用を隠していないことはわかりません。あなたはしません。
ポストコンディショナル
あなたとチームの他の全員がコメントで副作用(事前/事後条件)を確実に文書化すると、機能的アプローチからの利益ははるかに少なくなります。ええ、私は夢を見ています。
結論
個人的には、可能であればどちらの場合でも関数型プライベートメソッドを使用する傾向がありますが、正直なところ、それらの前後の条件付き副作用コメントは、古くなったり、メソッドが順序どおりに呼び出されなかったりしても、コンパイラエラーを引き起こさないためです。副作用の柔軟性が本当に必要な場合を除いて、物事が機能することを知っているだけです。
最初のバリアントは直感的ではなく、潜在的に危険であるように見えます(何らかの理由で誰かがプライベートメソッドを公開していると想像してください)。
クラス構築時に変数をインスタンス化するか、引数として渡します。後者は、機能的なイディオムを使用し、包含オブジェクトの状態に依存しないオプションを提供します。
何かをする例を試してみましょう。これはJavascriptではなくjavascriptなので許してください。ポイントは同じでなければなりません。
https://blockly-games.appspot.com/pond-duck?lang=en にアクセスし、javascriptタブをクリックして、次の内容を貼り付けます。
_hunt = lock(-90,1),
heading = -135;
while(true) {
hunt()
heading += 2
swim(heading,30)
}
function lock(direction, width) {
var dir = direction
var wid = width
var dis = 10000
//hunt
return function() {
//randomize() //Calling this here makes the state of dir meaningless
scanLock()
adjustWid()
if (isSpotted()) {
if (inRange()) {
if (wid <= 4) {
cannon(dir, dis)
}
}
} else {
if (!left()) {
right()
}
}
}
function scanLock() {
dis = scan(dir, wid);
}
function adjustWid() {
if (inRange()) {
if (wid > 1)
wid /= 2;
} else {
if (wid < 16) {
wid *= 2;
}
}
}
function isSpotted() {
return dis < 1000;
}
function left() {
dir += wid;
scanLock();
return isSpotted();
}
function right() {
dir -= wid*2;
scanLock();
return isSpotted()
}
function inRange() {
return dis < 70;
}
function randomize() {
dir = Math.random() * 360
}
}
_
dir
、wid
、およびdis
はあまり受け渡されないことに注意してください。また、lock()
が返す関数内のコードは、疑似コードのように見えることに注意してください。まあそれは実際のコードです。しかし、それは非常に読みやすいです。受け渡しと割り当てを追加することもできますが、それにより、疑似コードでは決して見られない混乱が追加されます。
これらの3つの変数は永続的な状態であるため、割り当てと受け渡しを行わなくても問題ないと主張する場合は、ループごとにランダムな値をdir
に割り当てる再設計を検討してください。今はしつこくありませんね。
確かに、これでdir
をスコープ内にドロップできるようになりましたが、受け渡しと設定で疑似ライクなコードを散らかすことを余儀なくされることはありません。
だから、いいえ、状態は、パスして戻るのではなく、副作用を使用するかどうかを決定する理由ではありません。副作用だけでコードが読めなくなることもありません。 純粋な関数コードの利点 は得られません。しかし、よくできていて、良い名前が付いているので、実際には読みやすいと思います。
彼らが悪夢のようなスパゲッティに変身できないわけではありません。しかし、それではできませんか?
幸せな鴨狩り。
オブジェクトの状態と2番目の方法が望ましい場合については、すでに回答があります。最初のパターンに1つの一般的な使用例を追加したいだけです。
最初のパターンは、クラスの処理がアルゴリズムをカプセル化するである場合に完全に有効です。この使用例の1つは、アルゴリズムを単一のメソッドに記述した場合、アルゴリズムが大きすぎることです。したがって、それを小さなメソッドに分解してクラスにし、サブメソッドをプライベートにします。
パラメータを介してアルゴリズムのすべての状態を渡すのは面倒になる可能性があるので、プライベートフィールドを使用します。また、基本的にはインスタンスの状態であるため、最初の段落のルールにも一致します。 このためにプライベートフィールドを使用する場合、アルゴリズムは再入可能ではありませんに留意し、適切に文書化する必要があります。これはほとんどの場合問題にはなりませんが、おそらくあなたを噛む可能性があります。