カプセル化には目的がありますが、誤用または悪用される可能性もあります。
数十(数百ではないにしても)のフィールドを持つクラスを持つAndroid APIのようなものを考えてください。これらのフィールドをAPIのコンシューマーに公開すると、ナビゲートおよび使用が難しくなります。また、ユーザーは、フィールドの使用方法と競合する可能性のあるフィールドを使用して、好きなことを実行できるという誤った概念をユーザーに与えます。その意味で、カプセル化は、保守性、使いやすさ、読みやすさ、およびクレイジーなバグを回避するという意味で優れています。
一方、PODまたはすべてのフィールドがパブリックであるC / C ++の構造体のような単純な古いデータ型も有用です。Lombokの@dataアノテーションによって生成されるような、役に立たないゲッター/セッターを持つことは、「カプセル化パターン」を維持するための単なる方法です。Javaで「役に立たない」ゲッター/セッターを行う数少ない理由の1つは、メソッドがコントラクトを提供することです。
Javaでは、インターフェイスにフィールドを含めることはできません。したがって、ゲッターとセッターを使用して、そのインターフェイスのすべての実装者が持つ共通のプロパティを指定します。KotlinやC#などの最近の言語では、プロパティの概念は、セッターとゲッターを宣言できるフィールドとして認識されています。結局のところ、役に立たないゲッター/セッターは、Oracleがプロパティを追加しない限り、Javaと共に生きなければならないレガシーです。たとえば、Ketlinは、JetBrainsによって開発された別のJVM言語であり、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)を実施するために使用されます。この例のカプセル化の目的は、このクラスのコンシューマーに、インターフェースによって提供されるメソッドを使用させることです。ゲッターとセッターは派手なことは何もしませんが、DataClassとIDataInterfaceの他の実装者の間で共通の特徴を定義しました。したがって、私はこのようなメソッドを持つことができます:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
さて、カプセル化について話すとき、構文の問題も解決することが重要だと思います。カプセル化自体ではなく、カプセル化を実施するために必要な構文について人々が不平を言うのをよく見ます。頭に浮かぶ1つの例は、ケーシームラトリからです(彼の暴言はこちらで確認できます)。
カプセル化を使用するプレイヤークラスがあり、自分の位置を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をよりよく使用できるようになります。
ただし、構文は多くの言語の制限事項です。ただし、新しい言語には、publiceメンバーの素晴らしい構文とカプセル化の利点を提供するプロパティがあります。MSVCを使用している場合、C ++、さらにはC ++でも、C#、Kotlinにプロパティがあります。Kotlinの例を次に示します。
クラスVerticalList:... {var posX:Int set(x){field = x; ...} var posY:Int set(y){field = y; ...}}
ここでは、Javaの例と同じことを達成しましたが、posXとposYをパブリック変数であるかのように使用できます。ただし、それらの値を変更しようとすると、セッターの本体set()が実行されます。
たとえば、Kotlinでは、これはgetter、setter、hashcode、equals、toStringが実装されたJava Beanと同等です。
data class DataClass(var data: Int)
この構文により、Java Beanを1行で実行できることに注意してください。Javaのような言語がカプセル化の実装に問題があることに気づきましたが、それはカプセル化自体ではなくJavaの欠点です。
Lombokの@Dataを使用してゲッターとセッターを生成すると言いました。@Dataという名前に注意してください。これは主に、データのみを保存するデータクラスで使用されることを意図しており、シリアル化および逆シリアル化されることを意図しています。ゲームの保存ファイルのようなものを考えてください。しかし、UI要素のような他のシナリオでは、変数の値を変更するだけでは期待される動作を得るのに十分でない可能性があるため、最も明確にセッターが必要です。
"It will create getters, setters and setting constructors for all private fields."
-このツールを説明する方法は、カプセル化を維持しているようです。(少なくとも、ゆるく、自動化された、多少貧弱なモデルの意味で。)では、正確に何が問題なのでしょうか?