プライベート vs.実際のパブリック メンバー (カプセル化はどのくらい重要ですか?) [終了]

StackOverflow https://stackoverflow.com/questions/99688

  •  01-07-2019
  •  | 
  •  

質問

オブジェクト指向プログラミングの最大の利点の 1 つはカプセル化であり、私たちが (少なくとも私が) 教えられてきた「真実」の 1 つは、メンバーは常にプライベートにし、アクセサーとミューテーターを介して利用できるようにする必要があるということです。これにより、変更を検証および検証できるようになります。

しかし、これが実際にどれほど重要なのか、私は興味があります。特に、より複雑なメンバー (コレクションなど) がある場合、コレクションのキーを取得したり、コレクションに項目を追加/削除したりするためのメソッドを多数作成するのではなく、単にパブリックにしたいという誘惑に駆られることがあります。等

あなたは一般的にルールに従っていますか?自分のために書かれたコードなのか、それとも自分のために書かれたコードなのかによって、答えは変わりますか?他人に使われるには?この難読化のために私が見逃しているもっと微妙な理由があるのでしょうか?

役に立ちましたか?

解決

場合によります。これは現実的に決定しなければならない問題の 1 つです。

点を表すクラスがあるとします。X 座標と Y 座標のゲッターとセッターを用意することも、両方をパブリックにして、データへの自由な読み取り/書き込みアクセスを許可することもできます。私の意見では、これは問題ありません。なぜなら、このクラスは、おそらくいくつかの便利な関数が付加されたデータ コレクションである、美化された構造体のように動作しているからです。

ただし、内部データへの完全なアクセスを提供したくない場合や、オブジェクトと対話するためにクラスが提供するメソッドに依存する状況も数多くあります。例としては、HTTP リクエストとレスポンスが挙げられます。この場合、誰でもネットワーク経由で何かを送信できるようにするのは悪い考えです。送信はクラス メソッドによって処理され、フォーマットされる必要があります。この場合、クラスは単純なデータ ストアではなく、実際のオブジェクトとして考えられます。

結局のところ、動詞 (メソッド) が構造を駆動するかどうか、それともデータが駆動するかによって決まります。

他のヒント

過去に多くの人が取り組んだ数年前のコードを保守しなければならない私にとって、メンバー属性が公開されると、最終的には悪用されることは明らかです。アクセサとミューテーターの考え方に反対する人さえいると聞いたことがあります。なぜなら、それは「クラスの内部動作を隠す」というカプセル化の目的をまだ十分に果たせていないからです。これは明らかに物議を醸すトピックですが、私の意見は、「すべてのメンバー変数をプライベートにし、クラスが何をしなければならないかを主に考えてください」です。 する (方法)ではなく どうやって 人々に内部変数を変更させることになるのです。」

はい、カプセル化は重要です。基礎となる実装を公開すると、(少なくとも) 2 つの問題が発生します。

  1. 責任を混同してしまう。呼び出し元は、基礎となる実装を理解する必要も、理解する必要もありません。彼らはクラスがその仕事をすることだけを望んでいるはずです。基礎となる実装を公開すると、クラスはその役割を果たさなくなります。むしろ、発信者に責任を押し付けているだけです。
  2. 基礎となる実装に結び付けられます。基礎となる実装を公開すると、それに結び付けられます。たとえば、下にコレクションがあることを呼び出し元に伝えた場合、そのコレクションを新しい実装に簡単に交換することはできません。

これら (およびその他) の問題は、基になる実装への直接アクセスを許可するか、基になるすべてのメソッドを単に複製するかに関係なく当てはまります。必要な実装を公開する必要がありますが、それ以上のものは公開しません。実装をプライベートに保つことで、システム全体の保守が容易になります。

私は、同じクラス内からであっても、メンバーをできるだけプライベートに保ち、ゲッター経由でのみアクセスすることを好みます。また、値スタイル オブジェクトを促進するための最初のドラフトとして、可能な限りセッターを避けるようにしています。依存関係注入を扱う場合、セッターはあってもゲッターがないことがよくあります。クライアントはオブジェクトを設定できる必要がありますが、これは実装の詳細であるため、実際に何が設定されているかを (他のユーザーは) 知ることができないためです。

よろしく、オリー

私は、たとえそれが自分のコードであっても、ルールにかなり厳密に従う傾向があります。そういう理由で、私は C# のプロパティが大好きです。これにより、与えられる値を制御することが非常に簡単になりますが、それでも変数として使用することができます。または、set をプライベートにして get をパブリックにするなどです。

基本的に、情報の隠蔽はコードの明瞭さに関係します。これは、他の人がコードを簡単に拡張できるようにし、クラスの内部データを操作するときに誤ってバグが発生するのを防ぐように設計されています。という原則に基づいています 誰もコメントを読んでいない, 、特に説明が記載されているもの。

例: 変数を更新するコードを作成しています。Gui がその変更を反映するように確実に変更する必要があります。最も簡単な方法は、データを更新する代わりに呼び出されるアクセサー メソッド (別名「セッター」) を追加することです。が更新されます。

そのデータを公開し、Setter メソッドを経由せずに何かが変数を変更した場合 (これは悪口のたびに起こります)、更新が表示されない理由を調べるために、誰かがデバッグに 1 時間費やす必要があります。程度は低いですが、同じことがデータの「取得」にも当てはまります。ヘッダー ファイルにコメントを入れることもできますが、おそらく、何か非常に深刻な問題が発生するまで、誰もそのコメントを読まないでしょう。プライベートで強制すると間違いが発生します できない なぜなら、それは実行時のバグではなく、コンパイル時のバグとして簡単に発見されるからです。

経験上、メンバー変数をパブリックにして、Getter メソッドと Setter メソッドを除外する必要があるのは、メンバー変数を変更しても副作用がないことを完全に明確にしたい場合のみです。特に、2 つの変数をペアとして単純に保持するクラスなど、データ構造が単純な場合はそうです。

通常はそうであるように、これはかなりまれな出来事です。 欲しい 副作用があり、作成しているデータ構造が単純すぎて作成できない場合 (ペアリングなど)、標準ライブラリには、より効率的に記述されたものが既に存在します。

そうは言っても、大学で受けるプログラムのような、1 回限りの拡張機能のない小規模なプログラムのほとんどでは、それは何よりも「良い練習」になります。なぜなら、プログラムを書いているうちに覚えて、その後、それらを提出し、二度とコードに触れることはありません。また、リリース コードとしてではなく、データの保存方法を調べる方法としてデータ構造を作成している場合、ゲッターとセッターは役に立たず、学習エクスペリエンスの邪魔になるという十分な議論があります。 。

これらの「リマインダー」を強力にすることが重要になるのは、職場や大規模なプロジェクトに到達した場合のみであり、さまざまな人々が作成したオブジェクトや構造によってコードが呼び出される可能性が高くなります。それが一人のプロジェクトであるかどうかは、驚くほど重要ではありません。「6週間後のあなた」は同僚と同じくらい別人であるという単純な理由からです。そして、「6週間前の私」は怠け者であることがよくあります。

最後のポイントは、一部の人々は情報の隠蔽に非常に熱心で、データが不必要に公開されるとイライラするということです。彼らにユーモアを与えるのが最善です。

C# プロパティはパブリック フィールドを「シミュレート」します。見た目は非常にクールで、この構文により get/set メソッドの作成が大幅に高速化されます。

オブジェクトのメソッドを呼び出すセマンティクスに留意してください。メソッド呼び出しは、コンパイラまたはランタイム システムにさまざまな方法で実装できる非常に高レベルの抽象化です。

呼び出しているメソッドのオブジェクトが同じプロセス/メモリ マップ内に存在する場合、データ メンバーに直接アクセスできるようにメソッドがコンパイラまたは VM によって最適化される可能性があります。一方、オブジェクトが分散システムの別のノードに存在する場合、その内部データ メンバーに直接アクセスする方法はありませんが、メッセージを送信してそのメソッドを呼び出すことはできます。

インターフェイスをコーディングすると、ターゲット オブジェクトがどこに存在するか、そのメソッドがどのように呼び出されるか、または同じ言語で記述されているかどうかを気にしないコードを作成できます。


コレクションのすべてのメソッドを実装するオブジェクトの例では、確かにそのオブジェクトは実際には コレクション。したがって、これはカプセル化よりも継承の方が優れているケースかもしれません。

大切なのは、あなたが与えたものを使って人々が何をできるかをコントロールすることです。よりコントロールできるようになると、より多くの仮定を立てることができます。

また、理論的には、基礎となる実装などを変更することもできますが、ほとんどの場合は次のとおりです。

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

正当化するのは少し難しいです。

カプセル化は、次の少なくとも 1 つが当てはまる場合に重要です。

  1. あなた以外の誰もがあなたのクラスを使用することになります(そうでないと、ドキュメントを読まないためにあなたの不変条件を破ることになります)。
  2. ドキュメントを読まない人は誰でもあなたのクラスを使用することになります (慎重に文書化された不変条件を破ることになります)。このカテゴリには、2 年後のあなたも含まれることに注意してください。
  3. 将来のある時点で、誰かがあなたのクラスを継承することになります (フィールドの値が変更されたときに追加のアクションを実行する必要があるため、セッターが必要になる可能性があります)。

それが私のためだけに使用され、ほとんどの場所で使用されず、それを継承するつもりがなく、フィールドを変更してもクラスが想定する不変式が無効にならない場合、 その時だけ たまにフィールドを公開します。

私の傾向としては、可能であればすべてを非公開にしようとする傾向があります。これにより、オブジェクトの境界が可能な限り明確に定義され、オブジェクトが可能な限り分離された状態に保たれます。これが気に入っているのは、1 回目 (2 回目、5 回目?) で失敗したオブジェクトを書き直す必要がある場合に、ダメージが少数のオブジェクトに抑えられるからです。

オブジェクトを十分に緊密に結合している場合は、それらを 1 つのオブジェクトに結合する方が簡単な場合があります。結合制約を十分に緩和すると、構造化プログラミングに戻ります。

オブジェクトの束が単なるアクセサー関数であることがわかった場合は、オブジェクトの分割を再考する必要があるかもしれません。そのデータに対して何もアクションを実行していない場合、そのデータは別のオブジェクトの一部として属している可能性があります。

もちろん、ライブラリのようなものを作成している場合は、他の人がそれに対してプログラミングできるように、できるだけ明確でシャープなインターフェイスが必要です。

ツールを作業に合わせて...最近、現在のコードベースで次のようなコードを見つけました。

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

そして、このクラスは複数のデータ値を簡単に渡すために内部で使用されました。必ずしも意味があるわけではありませんが、メソッドがなくデータだけがあり、それをクライアントに公開しない場合には、これは非常に便利なパターンだと思います。

これを最近使用したのは、JSP ページで、上部に宣言的に定義されたデータのテーブルが表示されていました。したがって、最初はデータ フィールドごとに 1 つの配列、つまり複数の配列でした。これにより、一緒に表示されるフィールドが定義内で隣接していないため、コードを読み進めるのがかなり困難になりました...そこで、それをまとめる上記のような単純なクラスを作成しました...その結果、以前よりもずっと読みやすいコードになりました。

道徳の...場合によっては、よく考えて結果を考慮する限り、コードがシンプルで読みやすくなる可能性がある場合は、「悪いと認められた」代替案を検討する必要があります...聞いたことすべてを盲目的に受け入れないでください。

そうは言っても...public getter と setter は public フィールドとほぼ同等です...少なくとも本質的には(柔軟性はもう少しありますが、すべてのフィールドに適用するのは依然として悪いパターンです)。

Javaの標準ライブラリでもいくつかのケースがあります。 パブリックフィールド.

私がオブジェクトに意味を持たせるとき、それらは より簡単に使用 そしてより簡単に 維持する.

例えば:Person.Hand.Grab(どのくらい早く、どのくらい);

コツは、メンバーを単純な値として考えるのではなく、それ自体がオブジェクトであると考えることです。

この質問はカプセル化の概念と「情報の隠蔽」を混同していると私は主張します。
(これは「カプセル化」の概念の一般的な解釈と一致しているように見えるため、批評ではありません)

ただし、私にとって、「カプセル化」は次のいずれかです。

  • 複数のアイテムを 1 つのコンテナに再グループ化するプロセス
  • コンテナ自体がアイテムを再グループ化する

納税者システムを設計しているとします。納税者ごとに、次のことができます。 カプセル化する という概念 子供 の中へ

  • 子供たちを代表する子供のリスト
  • to のマップでは、異なる親からの子が考慮されます。
  • 必要な情報(子の総数など)を提供するオブジェクト Children (Child ではありません)

ここには 3 つの異なる種類のカプセル化があり、2 つは低レベルのコンテナー (リストまたはマップ) で表され、1 つはオブジェクトで表されます。

そうした決定を下すことで、

  • そのカプセル化をパブリック、保護、またはプライベートにします。「情報隠蔽」の選択はまだ行われていない
  • 完全な抽象化を行う (オブジェクト Children の属性を調整する必要があり、納税者システムの観点から関連情報のみを保持するオブジェクト Child を作成することもできます)
    抽象化は、オブジェクトのどの属性がシステムに関連しており、どの属性を完全に無視する必要があるかを選択するプロセスです。

したがって、私のポイントは次のとおりです。
その質問のタイトルは次のとおりです。
プライベート vs.実際のパブリックメンバー (どれくらい重要か) 情報隠蔽?)

ただし、私の2セントだけです。私は、カプセル化を「情報隠蔽」の決定を含むプロセスとして考えることができることを完全に尊重します。

ただし、私は常に「抽象化」-「カプセル化」-「情報の隠蔽または可視化」を区別するように努めています。

@VonC

国際標準化機構の「オープン分散処理の参照モデル」は興味深い読み物になるかもしれません。以下を定義します。「カプセル化:オブジェクトに含まれる情報には、そのオブジェクトがサポートするインターフェイスでの対話を通じてのみアクセスできるという特性。」

私はここで、情報隠蔽がこの定義の重要な部分であることを主張してみました。http://www.edmundkirwan.com/encap/s2.html

よろしくお願いします。

エド。

ゲッターとセッターはたくさんあると思います コードの匂い プログラムの構造が適切に設計されていない可能性があります。これらのゲッターとセッターを使用するコードを調べて、実際にクラスの一部であるべき機能を探す必要があります。ほとんどの場合、クラスのフィールドはプライベート実装の詳細である必要があり、そのクラスのメソッドのみがフィールドを操作できます。

ゲッターとセッターの両方があることは、フィールドがパブリックであることと同じです (ゲッターとセッターが自明であるか、自動的に生成される場合)。場合によっては、ポリモーフィズムが必要な場合や、フレームワークで get/set メソッドが必要な場合 (フレームワークを変更できない場合) を除き、コードをより単純にするためにフィールドを public に宣言する方が良い場合があります。

ただし、ゲッターとセッターを使用することが良いパターンである場合もあります。一例:

アプリケーションの GUI を作成するときは、簡単に単体テストできるように GUI の動作を 1 つのクラス (FooModel) に保持し、テストできる別のクラス (FooView) で GUI を視覚化するようにします。手動でのみ。ビューとモデルは、単純なグルー コードで結合されます。ユーザーがフィールドの値を変更したとき x, 、ビューは呼び出します setX(String) これにより、モデルの他の部分が変更されたというイベントが発生する可能性があり、ビューはゲッターを使用してモデルから更新された値を取得します。

1 つのプロジェクトには、15 個のゲッターとセッターを含む GUI モデルがあり、そのうち 3 つの get メソッドだけが簡単です (IDE がそれらを生成できるような)。他のすべてには、次のようないくつかの機能または重要な式が含まれています。

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

実際には、私は常に 1 つのルールだけに従っています。それは、「すべてに適合するサイズはない」というルールです。

カプセル化とその重要性は、プロジェクトの成果です。どのオブジェクトがインターフェースにアクセスするのか、どのように使用するのか、メンバーに対する不必要なアクセス権を持っているかどうかは問題ですか?各プロジェクトの実装に取り​​組むときに自問する必要があるこれらの質問など。

私はモジュール内のコードの深さに基づいて決定を行います。モジュールの内部にあり、外部とインターフェースしないコードを書いている場合は、プログラマのパフォーマンス (コードを書いたり書き直したりする速度) に影響するため、プライベートでカプセル化することはあまりしません。

ただし、モジュールとユーザー コードのインターフェイスとして機能するオブジェクトについては、厳格なプライバシー パターンを遵守します。

確かに、自分が作成する内部コードか、他の人 (または自分自身が、含まれるユニットとして) に使用するコードを作成するかは違います。外部で使用されるコードには、明確に定義/文書化されたインターフェイスが必要です。できるだけ変化を少なくしたいと思うでしょう。

内部コードの場合、難易度によっては、今は単純な方法で作業を行い、後で少しペナルティを支払う方が作業が少ないと感じるかもしれません。もちろん、マーフィーの法則により、カプセル化できなかったクラスの内部を変更する必要がある場合に、後で広範な変更を加える必要があるため、短期的な利益は何倍も帳消しになります。

特に、返されるコレクションを使用する例では、そのようなコレクションの実装が変更され (単純なメンバー変数とは異なり)、カプセル化の有用性が高くなる可能性があるようです。

そうは言っても、私は Python の対処方法が気に入っています。メンバー変数はデフォルトでパブリックです。それらを非表示にしたり、検証を追加したりする場合には、いくつかのテクニックが提供されていますが、それらは特殊なケースとみなされます。

私はほぼ常にこのルールに従っています。私には 4 つのシナリオがあります。基本的には、ルール自体といくつかの例外 (すべて Java の影響) です。

  1. 現在のクラス外のあらゆるものから使用可能で、ゲッター/セッター経由でアクセスできます。
  2. 内部からクラスへの使用では通常、メソッド パラメーターではないことを明確にするために「this」が前に付けられます。
  3. トランスポート オブジェクトのように、非常に小さいものを意図したもの - 基本的には属性のストレートなショットです。すべて公開
  4. 何らかの拡張のために非プライベートである必要がある

ここには、既存の回答のほとんどでは対処されていない実際的な懸念があります。カプセル化とクリーンで安全なインターフェイスを外部コードに公開することは常に素晴らしいことですが、作成しているコードが空間的および/または時間的に大規模な「ユーザー」ベースによって使用されることを意図している場合には、より重要になります。私が言いたいのは、将来にわたって誰か (あなた自身も) がコードを保守する予定がある場合、または他の少数の開発者のコ​​ードと連携するモジュールを作成している場合は、さらに多くのことを考える必要があるということです。 1 回限りのコード、または完全に自分で書いたコードを作成する場合よりも慎重に行ってください。

正直に言うと、これがソフトウェア エンジニアリングの惨めな行為であることはわかっていますが、私は多くの場合、最初にすべてを公開します。そうすることで、内容を覚えたり入力したりするのがわずかに速くなり、その後、必要に応じてカプセル化を追加します。最近の最も一般的な IDE のリファクタリング ツールにより、どちらのアプローチを使用するかが決まります (カプセル化の追加か、カプセル化の追加か)。それを取り除く)以前よりもはるかに関連性が低くなりました。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top