ゲッターとセッターのメソッドが悪である理由

「isevil」シリーズを始めるつもりはありませんでしたが、先月のコラム「Why extends Is Evil」で、get / setメソッドを避けるべきだと言った理由を説明するように数人の読者から尋ねられました。

ゲッター/セッターメソッドはJavaでは一般的ですが、特にオブジェクト指向(OO)ではありません。実際、コードの保守性を損なう可能性があります。さらに、多数のゲッターおよびセッターメソッドの存在は、プログラムがオブジェクト指向の観点から必ずしも適切に設計されているとは限らないことを示す危険信号です。

この記事では、ゲッターとセッターを使用すべきでない理由(およびそれらを使用できる場合)について説明し、ゲッター/セッターの精神から抜け出すのに役立つ設計方法を提案します。

デザインの性質について

別のデザイン関連のコラム(挑発的なタイトルもあります)を始める前に、いくつかのことを明確にしておきたいと思います。

先月のコラム「WhyextendsIs Evil」(記事の最後のページのトークバックを参照)からの読者のコメントに驚かされました。一部の人々はextends、2つの概念が同等であるかのように、問題があるという理由だけでオブジェクト指向が悪いと私が主張したと信じていました。それは確かに私が言ったと思っていたものではないので、いくつかのメタ問題を明確にしましょう。

このコラムと先月の記事はデザインに関するものです。デザインは、本質的に、一連のトレードオフです。すべての選択には良い面と悪い面があり、必要性によって定義された全体的な基準のコンテキストで選択を行います。しかし、善と悪は絶対的なものではありません。ある状況での良い決定は、別の状況では悪いかもしれません。

問題の両面を理解していないと、賢明な選択をすることはできません。実際、アクションのすべての影響を理解していない場合は、まったく設計していません。あなたは暗闇の中でつまずいている。Gang of Fourのデザインパターンの本のすべての章に、パターンの使用が不適切な場合と理由を説明する「結果」セクションが含まれているのは偶然ではありません。

一部の言語機能または一般的なプログラミングイディオム(アクセサーなど)に問題があると述べることは、いかなる状況でも決してそれらを使用してはならないということと同じではありません。また、機能やイディオムが一般的に使用されているからといって、それ使用する必要があるとは限りません。知識のないプログラマーは多くのプログラムを作成し、Sun MicrosystemsまたはMicrosoftに採用されただけでは、誰かのプログラミング能力や設計能力が魔法のように向上することはありません。 Javaパッケージには、多くの優れたコードが含まれています。しかし、そのコードの一部もあり、著者が自分たちが書いたことを認めるのは恥ずかしいと思います。

同様に、マーケティングや政治的インセンティブは、しばしばデザインのイディオムを押し上げます。プログラマーが悪い決定をすることもありますが、企業はテクノロジーができることを宣伝したいので、あなたがそれを行う方法が理想的とは言えないことを強調しません。彼らは悪い状況を最大限に活用します。その結果、「それがあなたが物事を行うことになっている方法である」という理由だけでプログラミングの練習を採用するとき、あなたは無責任に行動します。多くの失敗したEnterpriseJavaBeans(EJB)プロジェクトは、この原則を証明しています。 EJBベースのテクノロジーは、適切に使用すると優れたテクノロジーですが、不適切に使用すると文字通り会社を倒す可能性があります。

私のポイントは、盲目的にプログラムするべきではないということです。機能やイディオムがもたらす可能性のある大混乱を理解する必要があります。そうすることで、その機能とイディオムのどちらを使用するかを決定するのにはるかに適した立場になります。あなたの選択は、情報に基づいた実用的なものでなければなりません。これらの記事の目的は、目を開けてプログラミングに取り組む手助けをすることです。

データの抽象化

OOシステムの基本的な原則は、オブジェクトがその実装の詳細を公開してはならないということです。このようにして、オブジェクトを使用するコードを変更せずに実装を変更できます。したがって、オブジェクト指向システムでは、ゲッター関数とセッター関数はほとんどの場合、実装の詳細へのアクセスを提供するため、避ける必要があります。

その理由を理解するgetX()ために、プログラム内のメソッドへの呼び出しが1,000回あり、各呼び出しは戻り値が特定のタイプであると想定していると考えてください。getX()たとえば、の戻り値をローカル変数に格納する場合、その変数タイプは戻り値タイプと一致する必要があります。Xのタイプが変わるようにオブジェクトの実装方法を変更する必要がある場合は、深刻な問題に直面します。

Xがであったintが、今はである必要があるlong場合、1,000のコンパイルエラーが発生します。戻り値をにキャストして問題を誤って修正するintと、コードは正常にコンパイルされますが、機能しません。(戻り値が切り捨てられる場合があります。)変更を補正するには、1,000回の呼び出しのそれぞれを囲むコードを変更する必要があります。私は確かにそれほど多くの仕事をしたくありません。

オブジェクト指向システムの基本原則の1つは、データの抽象化です。オブジェクトがメッセージハンドラーを実装する方法をプログラムの残りの部分から完全に隠す必要があります。これが、すべてのインスタンス変数(クラスの非定数フィールド)がである必要がある理由の1つprivateです。

インスタンス変数を作成する場合public、フィールドを使用する外部コードを壊してしまうため、クラスが時間の経過とともに進化するにつれてフィールドを変更することはできません。クラスを変更したという理由だけで、クラスの1,000回の使用を検索する必要はありません。

この実装隠蔽の原則は、オブジェクト指向システムの品質の優れた酸性テストにつながります。クラス定義に大規模な変更を加えることができますか。それを使用するコードに影響を与えることなく、すべてを破棄して完全に異なる実装に置き換えることもできます。クラスのオブジェクト?この種のモジュール化は、オブジェクト指向の中心的な前提であり、メンテナンスがはるかに簡単になります。実装を隠すことなく、他のOO機能を使用する意味はほとんどありません。

ゲッターメソッドとセッターメソッド(アクセサーとも呼ばれます)は、publicフィールドが危険であるのと同じ理由で危険です。これらは、実装の詳細への外部アクセスを提供します。アクセスされたフィールドのタイプを変更する必要がある場合はどうなりますか?また、アクセサの戻り値の型を変更する必要があります。この戻り値はさまざまな場所で使用されるため、そのコードもすべて変更する必要があります。変更の影響を単一のクラス定義に制限したいと思います。プログラム全体に波及してほしくない。

アクセサはカプセル化の原則に違反しているため、アクセサを多用または不適切に使用するシステムは、単にオブジェクト指向ではないと合理的に主張できます。コーディングだけでなく、設計プロセスを経ても、プログラムにアクセサーはほとんど見つかりません。プロセスは重要です。この問題については、記事の最後で詳しく説明します。

ゲッター/セッターメソッドがないからといって、一部のデータがシステムを流れないというわけではありません。それでも、データの移動を可能な限り最小限に抑えることが最善です。私の経験では、保守性はオブジェクト間を移動するデータの量に反比例します。まだわかりませんが、実際にはこのデータ移動のほとんどを排除できます。

注意深く設計し、どのように行うかではなく、何をしなければならないかに焦点を当てることにより、プログラム内のゲッター/セッターメソッドの大部分を排除します。仕事をするために必要な情報を求めないでください。あなたのために仕事をするための情報を持っているオブジェクトに尋ねてください。ほとんどのアクセサは、設計者が動的モデル、つまりランタイムオブジェクトと、作業を行うために相互に送信するメッセージについて考えていなかったため、コードへの道を見つけます。それらは、クラス階層を設計することから(誤って)開始し、次にそれらのクラスを動的モデルにシューホーンしようとします。このアプローチは決して機能しません。静的モデルを構築するには、クラス間の関係を検出する必要があります。これらの関係は、メッセージフローに正確に対応しています。 1つのクラスのオブジェクトが他のクラスのオブジェクトにメッセージを送信する場合にのみ、2つのクラス間に関連付けが存在します。静的モデルの主な目的は、動的にモデル化するときにこの関連付け情報を取得することです。

明確に定義された動的モデルがなければ、クラスのオブジェクトをどのように使用するかを推測しているだけです。その結果、アクセサメソッドは、必要かどうかを予測できないため、できるだけ多くのアクセスを提供する必要があるため、モデルに含まれることがよくあります。この種の推測による設計戦略は、せいぜい非効率的です。無駄なメソッドを書く(またはクラスに不要な機能を追加する)のに時間を浪費します。

アクセサーはまた、習慣の力によってデザインになってしまいます。手続き型プログラマーがJavaを採用する場合、使い慣れたコードを作成することから始める傾向があります。手続き型言語にはクラスはありませんが、Cはありますstruct(メソッドのないクラスを考えてみてください)。したがってstruct、実質的にメソッドがなく、publicフィールドだけでクラス定義を構築することによって、を模倣するのは自然なことのように思われます。privateただし、これらの手続き型プログラマーは、フィールドが必要であることをどこかで読んでいるため、フィールドを作成し、アクセサーメソッドprivateを提供しpublicます。しかし、彼らはパブリックアクセスを複雑にしているだけです。彼らは確かにシステムをオブジェクト指向にしたわけではありません。

自分を描く

フルフィールドカプセル化の1つの影響は、ユーザーインターフェイス(UI)の構築にあります。アクセサーを使用できない場合は、UIビルダークラスにgetAttribute()メソッドを呼び出させることはできません。代わりに、クラスにはdrawYourself(...)メソッドのような要素があります。

getIdentity()この方法はまた、仕事は、もちろん、それが実装するオブジェクトを返し提供することができるIdentityインタフェースを。このインターフェースには、drawYourself()(またはgive-me-a- JComponent-that-represents-your-identity)メソッドが含まれている必要があります。getIdentity「get」で始まりますが、フィールドを返すだけではないため、アクセサーではありません。妥当な動作をする複雑なオブジェクトを返します。Identityオブジェクトを持っていても、アイデンティティが内部でどのように表現されているのかわかりません。

もちろん、drawYourself()戦略とは、私がUIコードをビジネスロジックに組み込むことを意味します。 UIの要件が変更されたときに何が起こるかを検討してください。属性をまったく異なる方法で表現したいとします。今日、「アイデンティティ」は名前です。明日は名前とID番号です。その翌日は名前、ID番号、写真です。これらの変更の範囲をコード内の1か所に制限します。私がgive-me-a-- JComponentthat-represents-your-identityクラスを持っている場合、IDがシステムの他の部分から表される方法を分離しました。

私は実際にはビジネスロジックにUIコードを入れていないことを覚えておいてください。どちらも抽象化レイヤーであるAWT(Abstract Window Toolkit)またはSwingの観点からUIレイヤーを作成しました。実際のUIコードはAWT / Swing実装にあります。これが抽象化レイヤーの要点です。つまり、ビジネスロジックをサブシステムのメカニズムから分離することです。コードを変更せずに別のグラフィカル環境に簡単に移植できるので、唯一の問題は少し雑然としていることです。すべてのUIコードを内部クラスに移動する(またはFaçadeデザインパターンを使用する)ことで、この混乱を簡単に取り除くことができます。

JavaBeans

「しかし、JavaBeansはどうですか?」と言って反対するかもしれません。彼らをどう思いますか?ゲッターやセッターなしでJavaBeansを確実に構築できます。BeanCustomizerBeanInfo、およびBeanDescriptorクラスはすべて、まさにこの目的のために存在します。 JavaBean仕様の設計者は、getter / setterのイディオムを図に取り入れました。これは、Beanをすばやく作成する簡単な方法であると考えたためです。これは、正しい方法を学習しているときに実行できることです。残念ながら、誰もそれをしませんでした。

アクセサーは、UIビルダープログラムまたは同等のものがそれらを識別できるように、特定のプロパティにタグを付ける方法としてのみ作成されました。これらのメソッドを自分で呼び出すことは想定されていません。それらは、使用する自動化ツール用に存在します。このツールは、Classクラス内のイントロスペクションAPIを使用してメソッドを検索し、メソッド名から特定のプロパティの存在を推定します。実際には、この内省ベースのイディオムはうまくいきませんでした。これにより、コードが非常に複雑になり、手続き型になりました。データの抽象化を理解していないプログラマーは、実際にはアクセサーを呼び出します。その結果、コードの保守性が低下します。このため、メタデータ機能はJava 1.5に組み込まれます(2004年半ばに予定)。したがって、代わりに:

プライベートintプロパティ。public int getProperty(){return property; } public void setProperty(int value} {property = value;}

次のようなものを使用できるようになります。

プライベート@propertyintプロパティ; 

UI構築ツールまたは同等のツールは、メソッド名を調べて名前からプロパティの存在を推測するのではなく、イントロスペクションAPIを使用してプロパティを検索します。したがって、ランタイムアクセサがコードに損傷を与えることはありません。

アクセサーはいつ大丈夫ですか?

まず、前に説明したように、オブジェクトが実装するインターフェイスの観点からオブジェクトを返すメソッドは問題ありません。これは、そのインターフェイスが実装クラスへの変更からユーザーを分離するためです。この種のメソッド(インターフェース参照を返す)は、フィールドへのアクセスを提供するだけのメソッドという意味では、実際には「ゲッター」ではありません。プロバイダーの内部実装を変更する場合は、変更に対応するために、返されるオブジェクトの定義を変更するだけです。それでも、そのインターフェースを介してオブジェクトを使用する外部コードを保護します。