カプセル化は、すべてまたはほとんどすべてのフィールドをプライベートにし、ゲッター/セッターによってこれらを公開するように指示します。しかし、今では Lombok などのライブラリが表示され、1つの短い注釈@Data
ですべてのプライベートフィールドを公開できます。すべてのプライベートフィールドのゲッター、セッター、設定コンストラクターを作成します。
誰かが私にすべてのフィールドをプライベートとして非表示にし、その後いくつかの特別なテクノロジーによってそれらのすべてを公開することの意味を説明できますか?では、なぜパブリックフィールドだけを使用しないのでしょうか。出発点に戻るためだけに、長い道のりを歩んできたような気がします。
はい、ゲッターとセッターを介して機能する他の技術があります。また、単純なパブリックフィールドでは使用できません。しかし、これらのテクノロジーが登場したのは、これらの多数のプロパティ(パブリックゲッター/セッターの背後にあるプライベートフィールド)があったためです。プロパティがなければ、これらのテクノロジーは別の方法で開発され、パブリックフィールドをサポートします。そして、すべてが簡単になり、今はロンボク島を必要としません。
このサイクル全体の意味は何ですか?そして、カプセル化は本当に現実のプログラミングで今意味がありますか?
すべての属性をゲッター/セッターで公開すると、Cまたはその他の手続き型言語でまだ使用されているデータ構造だけが得られます。それはnotカプセル化であり、Lombokは手続き型コードを簡単に操作できるようにしています。プレーンなパブリックフィールドと同じくらいひどいゲッター/セッター。本当に違いはありません。
また、データ構造はオブジェクトではありません。インターフェースの作成からオブジェクトの作成を開始する場合、ゲッター/セッターをインターフェースに追加することはありません。属性を公開すると、データによる操作がオブジェクトの外にあり、コードベース全体に広がるスパゲッティ手続き型コードにつながります。これで、オブジェクトと対話する代わりに、データとデータの操作を処理します。ゲッター/セッターを使用すると、データ駆動型の手続き型プログラミングが可能になり、それを使った操作は直接命令型の方法で行われます。データを取得-何かを行う-データを設定します。
OOPカプセル化は正しい方法で行うと象になります。オブジェクトが完全に制御できるように、状態と実装の詳細をカプセル化する必要があります。ロジックはオブジェクト内に集中し、全体に広がることはありませんそして、コードベースは維持されます。
[〜#〜]編集[〜#〜]
議論が進んでいることを確認した後、いくつか追加したいと思います。
したがって、なぜこれを行うべきなのかという質問に戻ると、この簡単な例を考えてみましょう:
public class Document {
private String title;
public String getTitle() {
return title;
}
}
public class SomeDocumentServiceOrHandler {
public void printDocument(Document document) {
System.out.println("Title is " + document.getTitle());
}
}
ここでは、getterによって内部の詳細を公開するDocumentがあり、オブジェクトのoutsideで機能するprintDocument
関数にexternal手続き型コードがあります。なぜこれが悪いのですか?今はCスタイルのコードしかありません。はい、構造化されていますが、実際の違いは何ですか? C関数は、さまざまなファイルに名前を付けて構造化できます。そして、いわゆるレイヤーはまさにそれを行っています。サービスクラスは、データを処理する一連のプロシージャにすぎません。このコードは保守性が低く、多くの欠点があります。
public interface Printable {
void print();
}
public final class PrintableDocument implements Printable {
private final String title;
public PrintableDocument(String title) {
this.title = title;
}
@Override
public void print() {
System.out.println("Title is " + title);
}
}
これと比べて。これでコントラクトが作成されました。このコントラクトの実装の詳細は、オブジェクトのinsideに隠されています。これで、そのクラスを実際にテストでき、そのクラスはいくつかのデータをカプセル化しています。そのデータをどのように処理するかは、オブジェクトの問題です。 talkをオブジェクトで使用するには、askでオブジェクトを印刷する必要があります。それはカプセル化であり、それはオブジェクトです。 OOPを使用すると、依存関係の注入、モック、テスト、単一の責任、および多数のメリットを最大限に活用できます。
誰かが私に説明してもらえますか、すべてのフィールドをプライベートとして非表示にし、その後いくつかの特別なテクノロジーですべてを公開する意味は何ですか?では、なぜパブリックフィールドだけを使用しないのでしょうか。
意味はあなたはそうすることになっているわけではないだ。
カプセル化とは、アクセスするために他のクラスが実際に必要とするフィールドのみを公開すること、そしてそれに対して非常に選択的で注意深いことを意味します。
Donotデフォルトですべてのフィールドにゲッターとセッターを与えるだけです!
これは、皮肉にもパブリックゲッターとセッターの概念の元となったJavaBeans仕様の精神に完全に反しています。
しかし、仕様を見ると、ゲッターとセッターの作成を非常に選択的にすることを意図しており、「読み取り専用」プロパティ(セッターなし)と「書き込み専用」プロパティ(いいえ)について話していることがわかります。ゲッター)。
別の要因は、ゲッターとセッターは必ずしもプライベートフィールドへの単純なアクセスではないです。ゲッターは、返される値を任意に複雑な方法で計算できます。キャッシュすることもできます。セッターは値を検証したり、リスナーに通知したりできます。
つまり、ここにあります:カプセル化は、実際に公開する必要のある機能のみを公開することを意味します。しかし、何を公開する必要があるかを考えず、構文に従ってすべてを無意味に公開する場合もちろん、変換は実際にはカプセル化されていません。
問題の核心はあなたのコメントで説明されていると思います:
私はあなたの考えに完全に同意します。しかし、どこかでオブジェクトをデータとともにロードする必要があります。たとえば、XMLから。そして、それをサポートする現在のプラットフォームは、getter/setterを介してそれを行うため、コードの品質と考え方を低下させます。ロンボクは、それ自体は悪いことではありませんが、その存在自体が、私たちに何か悪いことがあることを示しています。
あなたが持っている問題は、永続性データモデルとアクティブ データ・モデル。
アプリケーションには通常、multipleデータモデルがあります。
計算を実行するために実際に使用するデータモデルの上に。
一般に、外部との通信に使用されるデータモデルは、isolatedおよびindependent計算が実行される内部データモデル(ビジネスオブジェクトモデル、BOM)から:
このシナリオでは、通信レイヤーで使用されるオブジェクトがすべてのアイテムをパブリックにしたり、ゲッター/セッターに公開したりしても問題ありません。それらは不変のプレーンオブジェクトです。
一方、BOMには不変式が必要です。これにより、多くのセッターを使用できなくなります(ゲッターはカプセル化をある程度まで削減しますが、不変式には影響しません)。
プロパティint age
を持つUser
クラスがあります:
class User {
int age;
}
これを拡大して、User
に年齢だけではなく生年月日を指定します。ゲッターの使用:
class User {
private int age;
public int getAge() {
return age;
}
}
int age
フィールドをより複雑なLocalDate dateOfBirth
に置き換えることができます。
class User {
private LocalDate dateOfBirth;
public int getAge() {
LocalDate now = LocalDate.now();
int year = ...; // calculate using dateOfBirth and now
return year;
}
// other behaviors can now make use of dateOfBirth
}
契約違反やコード違反はありません。より複雑な動作に備えて内部表現をスケールアップする以外の何ものもありません。
フィールド自体はカプセル化されています。
Lombokの@Data
注釈は、Kotlinの データクラス に似ています。
すべてのクラスが動作オブジェクトを表すわけではありません。カプセル化の解除については、機能の使用方法によって異なります。ゲッターを介してフィールドをall公開しないでください。
カプセル化は、クラス内の構造化データオブジェクトの値または状態を非表示にするために使用されます
より一般的な意味では、カプセル化は情報を隠す行為です。 @Data
を乱用した場合、おそらくカプセル化を解除していると思い込むのは簡単です。しかし、それが目的がないと言っているのではありません。たとえば、JavaBeansは、一部の人に嫌われています。それでも、企業開発で広く使用されています。
Beanを使用しているため、企業の開発は悪いと思いますか?もちろん違います!要件は標準開発の要件とは異なります。豆を乱用することはできますか?もちろん!彼らはいつも虐待されています!
Lombokはまた、@Getter
および@Setter
を個別にサポートします-要件が要求するものを使用します。
カプセル化は、すべてまたはほとんどすべてのフィールドをプライベートにし、ゲッター/セッターによってこれらを公開するように指示します。
これは、オブジェクト指向プログラミングでカプセル化を定義する方法ではありません。カプセル化とは、各オブジェクトがカプセルのようになり、その外部シェル(パブリックAPI)が内部(プライベートメソッドとフィールド)へのアクセスを保護および規制し、ビューから非表示にすることを意味します。内部を非表示にすることで、呼び出し元は内部に依存せず、呼び出し元を変更(または再コンパイル)せずに内部を変更できます。また、カプセル化により、呼び出し元が安全な操作のみを使用できるようにすることで、各オブジェクトは独自の不変条件を強制できます。
したがって、カプセル化は情報隠蔽の特殊なケースであり、各オブジェクトはその内部を隠し、不変条件を適用します。
すべてのフィールドのゲッターとセッターを生成することは、内部データの構造が隠されておらず、不変式を強制できないため、カプセル化のかなり弱い形式です。ただし、呼び出し元を変更(または再コンパイル)することなく、データを内部に格納する方法を変更できるという利点があります(古い構造との間で変換できる限り)。
誰かが私にすべてのフィールドをプライベートとして非表示にし、その後いくつかの特別なテクノロジーによってそれらのすべてを公開することの意味を説明できますか?では、なぜパブリックフィールドだけを使用しないのでしょうか。出発点に戻るためだけに、長い道のりを歩んできたような気がします。
一部、これは歴史的な事故によるものです。ストライク1は、Javaでは、メソッド呼び出し式とフィールドアクセス式が呼び出しサイトで構文的に異なることです。つまり、フィールドアクセスをゲッターまたはセッター呼び出しで置き換えると、クラスのAPIが壊れます。したがって、アクセサが必要な場合は、今すぐ作成するか、APIを中断できる必要があります。この 言語レベルのプロパティサポート の欠如は、他の最近の言語、特に C# および EcmaScript とは対照的です。
ストライク2は、 JavaBeansspecification がゲッター/セッターとしてプロパティを定義したことです。フィールドはプロパティではありませんでした。その結果、ほとんどの初期のエンタープライズフレームワークはゲッター/セッターをサポートしましたが、フィールドはサポートしませんでした。それは今では長いです( Java Persistence API(JPA) 、 Bean Validation 、 Java Architecture for XML Binding(JAXB) 、- Jackson すべてのフィールドがサポートされるようになりました)。ただし、古いチュートリアルや本は今も残っており、誰もが状況の変化に気づいているわけではありません。言語レベルのプロパティサポートがない場合でも、一部のケースでは苦痛になることがあります(たとえば、単一エンティティのJPAレイジーロードがパブリックフィールドの読み取り時にトリガーされないため)、ほとんどのパブリックフィールドは問題なく機能します。つまり、私の会社はREST APIのパブリックフィールドを使用してすべてのDTOを作成します(結局、インターネット経由で送信されるパブリックは増えません:-)。
とは言っても、Lombokの_@Data
_は、getter/setterを生成するだけではありません。これは、toString()
、hashCode()
とequals(Object)
も生成するため、非常に貴重です。
そして、カプセル化は、現実のプログラミングで今本当に意味がありますか?
カプセル化は非常に貴重であるか、まったく役に立たない場合があります。カプセル化されるオブジェクトによって異なります。一般に、クラス内のロジックが複雑になるほど、カプセル化のメリットが大きくなります。
すべてのフィールドに対して自動的に生成されるゲッターとセッターは一般的に過剰に使用されますが、レガシーフレームワークを操作したり、フィールドでサポートされていない臨時のフレームワーク機能を使用したりするのに役立ちます。
カプセル化は、ゲッターとコマンドメソッドを使用して実現できます。セッターは単一のフィールドのみを変更することが予想されるため、通常は適切ではありませんが、不変条件を維持するには、一度に複数のフィールドを変更する必要がある場合があります。
まとめ
ゲッター/セッターは、カプセル化が不十分です。
Java=でのゲッター/セッターの普及は、プロパティに対する言語レベルのサポートの欠如、および多くの教育資料やそれら。
EcmaScriptなどの他のオブジェクト指向言語は、言語レベルでプロパティをサポートしているため、APIを壊すことなくゲッターを導入できます。そのような言語では、事前にではなく、実際に必要なときに1日だけ必要なときにゲッターを導入できます。これにより、はるかに快適なプログラミングエクスペリエンスを実現できます。
私は確かにその質問を自問しました。
しかし、それは完全に真実ではありません。ゲッター/セッターIMOの普及は、Java Bean仕様が原因で発生します。そのため、オブジェクト指向プログラミングではなく、Bean指向プログラミングの機能の1つです。違いは2つの間は、それらが存在する抽象化レイヤーのものです。Beansは、より多くのシステムインターフェイス、つまり上位レイヤーにあります。これらは、OO基盤から抽象化するか、少なくとも-いつものように、物事はあまりにも頻繁に遠くに追いやられています。
JavaプログラミングにユビキタスであるこのBeanに対応するJava言語機能が追加されていない-私はm C#のプロパティの概念のようなものを考えています。それを知らない人にとっては、次のような言語構成です。
class MyClass {
string MyProperty { get; set; }
}
とにかく、実際の実装の要点は、カプセル化から非常に多くの利益を得ます。
誰かが私に説明してもらえますか、すべてのフィールドをプライベートとして非表示にし、その後いくつかの特別なテクノロジーですべてを公開する意味は何ですか?では、なぜパブリックフィールドだけを使用しないのでしょうか。スタート地点に戻るためだけに、私たちは長く困難な道を歩んできたと感じています。
ここでの簡単な答えは、あなたが絶対に正しいということです。ゲッターとセッターは、カプセル化の価値のほとんど(すべてではない)を排除します。これは、カプセル化を解除するgetメソッドまたはsetメソッド、あるいはその両方があるときはいつでも、クラスのすべてのプライベートメンバーに盲目的にアクセサーを追加している場合は、間違っていることを意味します。
はい、ゲッターとセッターを介して機能する他の技術があります。また、単純なパブリックフィールドでは使用できません。しかし、これらのテクノロジーが登場したのは、パブリックゲッター/セッターの背後にあるプライベートフィールドが数多くあるためです。プロパティがなければ、これらのテクノロジーは別の方法で開発され、パブリックフィールドをサポートします。そして、すべてが簡単になり、今はロンボク島を必要としません。このサイクル全体の意味は何ですか?そして、カプセル化は、現実のプログラミングで今本当に意味がありますか?
ゲッターはセッターであり、Javaプログラミングではユビキタスです。これは、JavaBeanの概念が機能を事前に構築されたコードに動的にバインドする方法としてプッシュされたためです。たとえば、アプレット(それらを覚えていますか?)オブジェクトを検査してすべてのプロパティを検索し、フィールドとして表示します。次に、UIはユーザー入力に基づいてこれらのプロパティを変更できます。開発者は、クラスを記述して検証またはビジネスロジックを配置するだけです。そこなど.
これ自体はひどい考えではありませんが、Javaでのアプローチの大ファンではありません。それは単に穀物に反しているだけです。 Python、Groovyなどを使用します。この種のアプローチをより自然にサポートするもの。
JavaBeanは、JOBOL、つまりJava OOを理解しない書かれた開発者を作成したため、制御不能になりました。基本的に、オブジェクトはデータのバッグにすぎず、すべてのロジックは外部で作成されました長い方法で見られたので、それは普通に見られたので、これを質問するあなたや私のような人々はクックと見なされました。最近、私はシフトを見ました、そしてこれはそれほど部外者の立場ではありません
XMLバインディングは難しい問題です。これは、おそらくJavaBeansに対抗するための良い戦場ではありません。これらのJavaBeanをビルドする必要がある場合は、実際のコードに含めないようにしてください。それらをシリアル化レイヤーの一部として扱います。
これは(ご覧のとおり)論争の的になっている問題です。これは、多くの教義と誤解がゲッターとセッターの問題に関する合理的な懸念と混同されているためです。しかし、要するに、@Data
には何も問題がなく、カプセル化が壊れることはありません。
パブリックフィールドではなくゲッターとセッターを使用する理由
ゲッター/セッターはカプセル化を提供するためです。値をパブリックフィールドとして公開し、後でその場で値を計算するように変更する場合は、フィールドにアクセスするすべてのクライアントを変更する必要があります。明らかにこれは悪いです。オブジェクトの一部のプロパティがフィールドに格納されているか、オンザフライで生成されているか、または他の場所からフェッチされているかは、実装の詳細であるため、その違いはクライアントに公開されません。ゲッター/セッターセッターは実装を隠すため、これを解決します。
しかし、getter/setterが基になるプライベートフィールドを反映するだけの場合、それは同じように悪いことではありませんか?
番号!ポイントは、カプセル化により、クライアントに影響を与えずに実装を変更できることです。クライアントが知っていたり、気にする必要がない限り、フィールドは値を格納するための完全に優れた方法である可能性があります。
しかし、カプセル化を壊すフィールドからゲッター/セッターを自動生成していませんか?
カプセル化はまだありません! @Data
アノテーションは、基礎となるフィールドを使用するゲッター/セッターペアを記述するための便利な方法にすぎません。クライアントの視点から見ると、これは通常のゲッター/セッターのペアと同じです。実装を書き換える場合でも、クライアントに影響を与えることなく行うことができます。つまり、カプセル化との簡潔な構文の両方の長所を利用できます。
しかし、ゲッター/セッターは常に悪いと言う人もいます!
別の論争があり、ゲッター/セッターパターンは常に悪いとはいえ、基礎となる実装に関係なくと考える人もいます。オブジェクトから値を設定したり取得したりするのではなく、オブジェクト間の相互作用は、1つのオブジェクトasksが実行するメッセージとしてモデル化する必要があるという考え方です。何か。これは、ほとんどの場合、オブジェクト指向の考え方の初期の頃の教義の一部です。現在の考え方は、特定のパターン(値オブジェクト、データ転送オブジェクトなど)では、ゲッター/セッターが完全に適切な場合があるということです。
ゲッターなしでどれだけのことができますか?それらを完全に削除することは可能ですか?これはどのような問題を引き起こしますか? return
キーワードを禁止することもできますか?
あなたが仕事をする用意があるなら、あなたは多くのことができることがわかりました。では、この完全にカプセル化されたオブジェクトから情報がどのように取り出されるのでしょうか。協力者を通じて。
コードに質問をさせるのではなく、何かをするように指示します。それらもまた戻らない場合、それらが戻すものについて何もする必要はありません。したがって、return
に到達することを考えている場合は、代わりに、実行する必要があるすべてのことを行う出力ポートコラボレーターに到達してみてください。
この方法で物事を行うことには、利点と結果があります。あなたはあなたが返したであろうもの以上のものについて考えなければなりません。それを要求しなかったオブジェクトにメッセージとして送信する方法を考える必要があります。返されたのと同じオブジェクトを渡すか、単にメソッドを呼び出すだけで十分な場合があります。その思考を行うことは代償を伴います。
利点は、今、インターフェイスに直面して話していることです。つまり、抽象化のメリットを十分に活用できます。
それはまた、あなたが言っていることを知っている間、あなたが正確にそれが言っていることを知る必要がないので、あなたに多態的なディスパッチを与えます。
これは、レイヤーのスタックを一方向に進むだけでよいと考えているかもしれませんが、多態性を使用して、クレイジーな循環依存関係を作成せずに逆方向に移動できることがわかります。
次のようになります。
class Interactor implements InputPort {
OutputPort out;
int y = 0;
Interactor(OutputPort out){
this.out = out;
}
void accumulate(int x) {
y = y + x;
out.showAsImage(y);
}
}
このようにコーディングできる場合は、getterを使用することを選択します。必要悪ではありません。
カプセル化には目的がありますが、誤用または乱用される可能性もあります。
Android APIには数十(数百ではないにしても)のフィールドを持つクラスがあります)のようなものを考えてください。これらのフィールドを公開すると、APIのコンシューマーによりナビゲートと使用が困難になり、ユーザーにこれらのフィールドは、使用方法と競合する可能性があるフィールドで何でも好きなように実行できるという誤った考えなので、カプセル化は、その意味で、保守性、使いやすさ、読みやすさ、クレイジーなバグの回避に優れています。
一方、すべてのフィールドがパブリックであるC/C++からの構造体のようなPODまたはプレーンな古いデータタイプも有用です。 Lombokの@dataアノテーションによって生成されたもののような役に立たないゲッター/セッターを用意することは、「カプセル化パターン」を維持する方法の1つにすぎません。 Java=)で「役に立たない」ゲッター/セッターを使用する数少ない理由の1つは、メソッドがコントラクトを提供することです。
Javaでは、インターフェースにフィールドを含めることはできないため、ゲッターとセッターを使用して、そのインターフェースのすべての実装者が持つ共通のプロパティを指定します。 KotlinやC#などの最近の言語では、プロパティの概念を、セッターとゲッターを宣言できるフィールドと見なしています。結局のところ、役に立たないゲッター/セッターは、Javaは、Oracleがそれにプロパティを追加しない限り、存続しなければならないレガシーです。たとえば、JetBrainsによって開発された別のJVM言語であるKotlin、 Lombokで@dataアノテーションが行うことを基本的に行うデータクラスがあります。
また、いくつかの例を示します。
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
これはカプセル化の悪いケースです。ゲッターとセッターは事実上役に立たない。これはJavaなどの言語の標準であるため、カプセル化は主に使用されます。コードベース全体の一貫性を維持する以外に、実際には役立ちません。
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
これはカプセル化の良い例です。カプセル化は、コントラクト(この場合はIDataInterface)を適用するために使用されます。この例でのカプセル化の目的は、このクラスのコンシューマーに、インターフェースによって提供されるメソッドを使用させることです。 getterとsetterは特別なことは何もしませんが、DataClassとIDataInterfaceの他の実装者との間の共通の特性を定義しました。したがって、私はこのような方法を持つことができます:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
さて、カプセル化について話すとき、構文の問題にも対処することが重要だと思います。カプセル化そのものではなく、カプセル化を強制するために必要な構文について人々が不満を言うのをよく見ます。頭に浮かぶ1つの例は、Casey Muratoriです(彼の怒りを見ることができます here )。
カプセル化を使用するプレーヤークラスがあり、その位置を1ユニット移動したいとします。コードは次のようになります。
player.setPosX(player.getPosX() + 1);
カプセル化しないと、次のようになります。
player.posX++;
ここで彼は、カプセル化は追加の利点なしでより多くのタイピングにつながると主張し、これは多くの場合本当であるかもしれませんが、何かに気づきます。引数は構文自体に対するものではなく、カプセル化自体に対するものです。カプセル化の概念が欠けているCのような言語でも、構造体の変数に「_」や「my」などのプレフィックスまたはサフィックスが付けられた変数が頻繁に表示され、APIのコンシューマーがそれらを使用してはならないことを示します。民間。
問題は、カプセル化により、コードをはるかに保守しやすく、使いやすくできることです。このクラスを考えてみましょう:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
この例で変数がパブリックの場合、このAPIのコンシューマーは、posXおよびposYをいつ使用するか、およびsetPosition()をいつ使用するかについて混乱します。これらの詳細を非表示にすることで、消費者が直感的な方法でAPIをより適切に使用できるようになります。
ただし、構文は多くの言語の制限事項です。ただし、新しい言語には、公開メンバーのニース構文とカプセル化の利点を提供するプロパティが用意されています。 MSVCを使用している場合、C++、K ++でも、C#、Kotlinでプロパティを見つけることができます。これはKotlinの例です。
クラスVerticalList:... {var posX:Int set(x){field = x; ...} var posY:Int set(y){field = y; ...}}
ここでは、Javaの例と同じことを実現しましたが、posXとposYをパブリック変数のように使用できます。ただし、これらの値を変更しようとすると、設定セットの本体()が実行されます。
たとえばKotlinでは、これはJava Beanのゲッター、セッター、ハッシュコード、イコール、およびtoStringが実装されたBeanと同じです。
data class DataClass(var data: Int)
この構文により、Java Beanを1行で実行できるようになります。Javaのような言語がカプセル化の実装に持っているという問題に正しく気づきましたが、 Javaカプセル化自体のせいではありません。
Lombokの@Dataを使用してゲッターとセッターを生成するとのことですが、名前@Dataに注目してください。これは主に、データを格納するだけのデータクラスで使用することを目的としており、シリアル化および逆シリアル化されることを意図しています。ゲームの保存ファイルのようなものを考えてください。しかし、UI要素のような他のシナリオでは、変数の値を変更するだけでは期待される動作を得るには不十分な場合があるため、最も明確にセッターが必要です。
カプセル化によって柔軟性が得られます。構造とインターフェースを分離することにより、インターフェースを変更せずに構造を変更できます。
例えば。構築時に基になるフィールドを初期化する代わりに、他のフィールドに基づいてプロパティを計算する必要があることがわかった場合は、単にゲッターを変更できます。フィールドを直接公開した場合は、代わりにインターフェースを変更し、すべての使用サイトで変更を加える必要があります。
カプセル化とクラス設計の問題空間を説明し、最後に質問に答えます。
他の回答で述べたように、カプセル化の目的は、コントラクトとして機能するパブリックAPIの背後にあるオブジェクトの内部の詳細を隠すことです。オブジェクトは、パブリックAPIを介してのみ呼び出されることがわかっているため、内部を変更しても安全です。
パブリックフィールド、ゲッター/セッター、またはより高レベルのトランザクションメソッドやメッセージパッシングがあることが理にかなっているかどうかは、モデル化されているドメインの性質によって異なります。本のAkka Concurrency(多少古くてもお勧めできます)に、これを説明する例があります。ここでは省略します。
Userクラスを考えてみましょう:
public class User {
private String first = "";
private String last = "";
public String getFirstName() {
return this.first;
}
public void setFirstName(String s) {
this.first = s;
}
public String getLastName() {
return this.last;
}
public void setLastName(String s) {
this.last = s;
}
}
これは、シングルスレッドのコンテキストでは問題なく機能します。モデル化されるドメインは個人の名前であり、その名前がどのように保存されるかについてのメカニズムは、セッターによって完全にカプセル化できます。
ただし、これはマルチスレッドのコンテキストで提供する必要があると想像してください。 1つのスレッドが定期的に名前を読み取っているとします。
System.out.println(user.getFirstName() + " " + user.getLastName());
そして、他の2つのスレッドが綱引きと戦い、それをHillary ClintonとDonald Trumpに順番に設定しています。それぞれが2つのメソッドを呼び出す必要があります。ほとんどの場合これは問題なく機能しますが、時々Hillary TrumpまたはDonald Clintonが通り過ぎることがあります。
ロックは姓または名のいずれかを設定している間だけ保持されるため、セッター内にロックを追加してこの問題を解決することはできません。ロックによる唯一の解決策は、オブジェクト全体の周りにロックを追加することですが、呼び出し元のコードがロックを管理する必要があるため(デッドロックを引き起こす可能性があるため)、カプセル化が解除されます。
結局のところ、ロックによるクリーンな解決策はありません。クリーンなソリューションは、内部をより粗くすることで内部を再びカプセル化することです。
public class UserName {
public final String first;
public final String last;
public UserName(String first, String last) { ... }
}
public class User
private UserName name;
public UserName getName() { return this.name; }
public setName(UserName n) { this.name = n; }
}
名前自体は不変になり、作成された後は変更できない純粋なデータオブジェクトであるため、そのメンバーをパブリックにできることがわかります。次に、UserクラスのパブリックAPIはより粗くなり、セッターは1つしか残っていないため、名前は全体としてのみ変更できます。 APIの背後にある内部状態の多くをカプセル化します。
このサイクル全体の意味は何ですか?そして、カプセル化は、現実のプログラミングで今本当に意味がありますか?
このサイクルで目にするのは、特定の状況に適したソリューションを広範に適用しようとする試みです。適切なカプセル化レベルでは、モデル化されるドメインを理解し、適切なレベルのカプセル化を適用する必要があります。これは、すべてのフィールドがパブリックであることを意味する場合もあれば、(Akkaアプリケーションのように)メッセージを受信する単一のメソッドを除いて、パブリックAPIがないことを意味します。ただし、カプセル化自体の概念、つまり安定したAPIの背後にある内部を隠すことは、特にマルチスレッドシステムでソフトウェアを大規模にプログラミングするための鍵となります。
これが理にかなっている使用例を1つ考えることができます。最初は単純なゲッター/セッターAPIを介してアクセスするクラスがあるかもしれません。後で拡張または変更して、同じフィールドを使用しないようにしますが、まだ同じAPIをサポートしていますです。
やや工夫された例:p.x()
とp.y()
を持つデカルトペアとして開始するポイント。後で極座標を使用する新しい実装またはサブクラスを作成するため、p.r()
およびp.theta()
を呼び出すこともできますが、p.x()
およびp.y()
は引き続き有効です。クラス自体は内部の極形式から透過的に変換します。つまり、y()
はreturn r * sin(theta);
になります。 (この例では、x()
またはy()
のみを設定してもあまり意味がありませんが、それでも可能です。)
この場合、「フィールドをパブリックにする代わりに、getterとsetterを自動的に宣言するのが面倒だったか、APIを中断しなければならなかったのではないか」と思われるかもしれません。
私は実際にはJava開発者ではありませんが、以下はプラットフォームにとらわれないものです。
私たちが作成するほぼすべてのものは、プライベート変数にアクセスするパブリックゲッターとセッターを使用しています。ゲッターとセッターのほとんどは簡単です。しかし、セッターが何かを再計算する必要があるか、セッターが何らかの検証を行うか、またはこのクラスのメンバー変数のプロパティにプロパティを転送する必要があると判断した場合、これは完全にコード全体に影響を与えず、バイナリ互換であるため、その1つのモジュールを交換できます。
このプロパティをその場で実際に計算する必要があると判断した場合、それを参照するすべてのコードを変更する必要はなく、それに書き込むコードだけを変更する必要があります。IDEそれが書き込み可能な計算フィールドであると判断した場合(これを数回実行するだけで済みました)、それも可能です。これらの変更の多くはバイナリ互換です(読み取り専用に変更)計算されたフィールドは理論上ではありませんが、とにかく実際にあるかもしれません)。
結局、複雑なセッターを備えた簡単なゲッターがたくさんありました。また、かなりの数のキャッシングゲッターが作成されました。その結果、ゲッターはかなり安価であると想定することができますが、セッターはそうではないかもしれません。一方、セッターはディスクに永続化しないことを決定するのに十分賢明です。
しかし、すべてのメンバー変数を盲目的にプロパティに変更している人を追跡する必要がありました。彼はアトミック追加が何であるかを知りませんでしたので、実際にパブリック変数である必要があるものをプロパティに変更し、コードを微妙に壊しました。
誰かが私に説明してもらえますか、すべてのフィールドをプライベートとして非表示にし、その後いくつかの特別なテクノロジーですべてを公開する意味は何ですか?
全く意味がありません。しかし、あなたがその質問をするという事実は、Lombokが何をするのかを理解していないこと、およびOOカプセル化されたコードの記述方法を理解していないことを示しています。少し巻き戻してみましょう...
クラスインスタンスの一部のデータは常に内部にあり、決して公開されるべきではありません。クラスインスタンスの一部のデータは外部で設定する必要があり、一部のデータはクラスインスタンスから戻す必要がある場合があります。ただし、クラスが表面下で実行する方法を変更したい場合があるため、関数を使用してデータを取得および設定できるようにします。
一部のプログラムはクラスインスタンスの状態を保存する必要があるため、これらにはいくつかのシリアル化インターフェイスがある場合があります。クラスインスタンスがその状態をストレージに格納し、その状態をストレージから取得できるようにする関数をさらに追加します。これは、クラスインスタンスがまだ独自のデータを制御しているため、カプセル化を維持します。プライベートデータをシリアル化している可能性がありますが、プログラムの残りの部分はそのデータにアクセスできません(より正確には、プライベートデータを故意に破損しないことを選択することによって Chinese Wall を維持します)。クラスインスタンスは、データが正常に戻ったことを確認するために、逆シリアル化の整合性チェックを実行できます(また、実行する必要があります)。
時々、データは範囲チェック、完全性チェック、またはそのようなものを必要とします。これらの関数を自分で書くことで、すべてを行うことができます。この場合、Lombokは不要であり、必要もありません。すべてを自分で行っているからです。
ただし、外部で設定されたパラメータが単一の変数に格納されていることがよくあります。この場合、その変数の内容を取得/設定/シリアライズ/デシリアライズするには、4つの関数が必要になります。これらの4つの関数を自分で書くと、速度が低下し、エラーが発生しやすくなります。 Lombokでプロセスを自動化すると、開発がスピードアップし、エラーの可能性がなくなります。
はい、その変数を公開することは可能です。この特定のバージョンのコードでは、機能的には同じです。しかし、関数を使用している理由に戻ります。「クラスが表面下で実行する方法を変更したい場合があります...」変数をパブリックにすると、このパブリック変数が次のようになるようにコードを制限することになります。インターフェース。ただし、関数を使用する場合、またはLombokを使用してそれらの関数を自動的に生成する場合は、将来いつでも基礎となるデータと基礎となる実装を自由に変更できます。
これはそれをより明確にしますか?