カプセル化は一般的なトピックです
が、それをエレガントにカプセル化するにはどうすればよいでしょうか?
心配しないでください。
この記事ではエレガントなパッケージングのいくつかの方法について説明します。
エレガントにカプセル化する方法
1: カプセル化の意味:
カプセル化はオブジェクト指向プログラミングの概念であり、クラスの内部データとメソッドを保護して、外部から直接アクセスしたり変更したりすることはできませんが、クラスのパブリック メソッドを通じてのみ操作できるようにすることを指します。これにより、コードのセキュリティと保守性が向上します。
カプセル化は次の 2 つの側面に分類できます。
- データのカプセル化。つまり、クラスのプライベート データ メンバーへの外部アクセスを制限し、クラスのパブリック インターフェイス経由でのみアクセスできます。
- 動作のカプセル化、つまりクラスの内部関数が外部から直接呼び出されないよう制限するには、クラスのパブリック インターフェイスを介してのみアクセスできます。
2: エレガントなパッケージ
1. インターフェースと抽象クラスの使用
一部の一般的なメソッドまたはプロパティについては、インターフェイスまたは抽象クラスとして定義できます。これにより、サブクラスがこれらのメソッドまたはプロパティを継承して実装できるようになり、コードの柔軟性とスケーラビリティが向上します。
例:
public interface Shape {
void draw();
}
public abstract class ShapeWithDrawMethod {
public abstract void draw();
}
public class Circle extends ShapeWithDrawMethod implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Square extends ShapeWithDrawMethod implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
public class ShapeFactory {
public static Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
} else if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
} else {
throw new IllegalArgumentException("Invalid shape type");
}
}
}
public class Main {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("CIRCLE");
if (circle != null) {
circle.draw(); // 调用Circle类中的draw方法,会输出"Drawing a circle"
} else {
System.out.println("Invalid shape type");
}
}
}
この例では、Shape インターフェイスと、このインターフェイスを実装する 2 つのクラス Circle および Square を定義します。同時に、渡されたパラメータの型に応じて対応するインスタンス オブジェクトを返すために使用される ShapeFactory クラスも定義します。MainクラスではShapeFactory.getShape()メソッドを呼び出してShapeオブジェクトを取得し、それが空かどうかを判定します。null でない場合は、draw() が呼び出されます。
2. プライベート変数とパブリック メソッドの使用
保護する必要があるデータをプライベート変数に入れ、外部呼び出しにはパブリック メソッドのみを提供します。これにより、外部からの直接アクセスやプライベート変数の変更が回避され、データのセキュリティとカプセル化が確保されます。
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public void draw() {
System.out.println("Drawing " + name);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
circle.draw(); // 输出"Drawing Circle"
}
}
この例では、プライベート変数名とパブリック メソッドdraw()を含む Shape クラスを定義します。コンストラクターでは、受信パラメーターをプライベート変数に割り当てます。これにより、データのセキュリティとカプセル化が保証されます。同時に、パブリック インターフェイスのみが提供され、外部からプライベート変数に直接アクセスできないため、コードの保守性とセキュリティが確保されます。
3. 静的メソッドと定数の使用
インスタンス変数にアクセスする必要のない一部のメソッドについては、静的メソッドまたは定数を使用して実装できます。これにより、インスタンス化された各オブジェクトで同じメソッドや変数を作成することが回避され、コードの再利用性と保守性が向上します。
静的メソッドと定数ラッパーを使用した例を次に示します。
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public static final String CIRCLE_NAME = "Circle";
public static void draw() {
System.out.println("Drawing " + CIRCLE_NAME);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
Shape.draw(); // 输出"Drawing Circle"
}
}
この例では、Shape
プライベート変数を含むクラスを定義しますname
。同時に、CIRCLE_NAME
サークルの名前を表す静的定数を定義します。static メソッドでは、インスタンス オブジェクトを作成せずに、draw()
static 定数を直接使用します。CIRCLE_NAME
これにより、オブジェクトの繰り返し作成が回避され、コードの効率と保守性が向上します。同時に、静的メソッドはクラスのインスタンスではなくクラス自体に属するため、インスタンス オブジェクトを作成せずにクラス名を通じて直接呼び出すことができます。
4. 注釈の使用
Java のアノテーションを使用して、クラス、メソッド、フィールドなどの要素の動作や属性を記述することができます。注釈を使用すると、コンパイル時または実行時に要素の情報を取得できるため、より柔軟で効率的なカプセル化が実現します。
アノテーションのカプセル化を使用した例を次に示します。
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
@Override
public String toString() {
return "Shape{name=" + name + "}";
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
System.out.println(circle); // 输出"Shape{name=Circle}"
}
}
この例では、Shape
プライベート変数を含むクラスを定義しますname
。同時に、Java のアノテーションを使用して、親クラスのメソッド@Override
をオーバーライドしました。このメソッドでは、現在のオブジェクトの情報を表す文字列を返します。このように、印刷コードを手動で記述することなく、メソッドを通じてオブジェクトの情報を出力できると便利です。同時に、アノテーションはクラスのインスタンスではなくクラス自体に属するため、インスタンス オブジェクトを作成せずにクラス名を通じて直接呼び出すことができます。Object
toString()
toString()
System.out.println()
5. 列挙型を使用する
値の範囲を制限する必要がある一部の変数については、列挙型を使用して実現できます。これにより、整数などのデータ型を使用するときに発生する可能性のあるオーバーフローやエラーが回避され、同時にコードの可読性と保守性が向上します。
enum 型でラップされた例を次に示します。
public enum Shape {
ROUND, CIRCLE;
public static Shape getShapeByName(String shapeName) {
for (Shape shape : values()) {
if (shape.name().equalsIgnoreCase(shapeName)) {
return shape;
}
}
throw new IllegalArgumentException("Invalid shape name: " + shapeName);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = Shape.CIRCLE;
System.out.println(Shape.getShapeByName("Circle")); // 输出"Circle"
}
}
この例では、 2 つの列挙値と をShape
含む列挙型を定義します。同時に、渡された name パラメーターに従って対応する列挙値を返す静的メソッドも定義します。このメソッドでは、すべての列挙値をループし、メソッドを通じてそれらを比較します。一致する列挙値が見つかった場合は、それが直接返されます。それ以外の場合は、例外がスローされます。これにより、データのセキュリティとカプセル化が保証されると同時に、より適切な型チェックとエラー プロンプトが提供されます。ROUND
CIRCLE
getShapeByName()
for-each
equalsIgnoreCase()
IllegalArgumentException
6. その他
6.1 複数の方法で組み合わせた梱包
1. アクセス制御シンボルを使用する:
Java には、属性やメソッドのアクセス権を制限できる public、private、protected などのアクセス制御シンボルがあります。一般に、属性ではプライベート アクセス制御シンボルを使用する必要があり、メソッドでは必要に応じてパブリック、プライベート、またはプロテクトを使用できます。
2. getter メソッドと setter メソッドを使用します。
プロパティをプライベートに設定してから、パブリックの getter メソッドと setter メソッドを提供します。これにより、外部からオブジェクトのプロパティにアクセスできますが、プロパティを直接変更することはできません。同時に、属性の有効性のチェックなど、ゲッター メソッドとセッター メソッドに制御ロジックを追加できます。
3. Final キーワードを使用します。final
キーワードを使用してプロパティとメソッドを不変に設定します。これにより、オブジェクトに対する不必要な外部変更が回避され、コードのセキュリティと保守性が向上します。
例:
public class Shape {
private final String name;
public Shape(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
throw new IllegalArgumentException("Cannot modify the shape name");
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape("Circle");
System.out.println(circle.getName()); // 输出"Circle"
try {
circle.setName("Square"); // 抛出异常,因为setName方法被final修饰了
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 输出"Cannot modify the shape name"
}
}
}
Shape
この例では、プライベート変数を含むクラス を定義しname
、final キーワードを使用してそれを変更します。同時に、アクセス制御シンボルpublic
、private
、を使用してget
、set
パブリック、プライベート、ゲッター、セッター メソッドのアクセス権をそれぞれ制御します。これにより、データのセキュリティとカプセル化が保証されると同時に、より適切な型チェックとエラー通知が提供されます。同時に、プライベート変数の値が誤って変更されることが防止されます。Final キーワードで修飾されたメソッドは再定義したり呼び出したりできないため、潜在的な問題の一部を回避できます。
6.2 ラッパークラスの使用
いくつかのプリミティブ型または複雑な型のデータをカプセル化する必要がある場合は、Integer、Double、List などの Java が提供するラッパー クラスを使用して、ラッパー クラスを通じていくつかのパブリック インターフェイスを公開し、基礎となる実装の詳細を非表示にすることができます。 。
ラッパー クラスのカプセル化を使用する利点は次のとおりです。
- コードの可読性と保守性が向上します。ラッパー クラスは内部実装の詳細を隠し、必要なインターフェイスのみを公開することができるため、コードがより明確で理解しやすくなります。
- コードの柔軟性とスケーラビリティを向上させます。新しい関数を追加したり、ラッパー クラスの既存の関数を変更したりしても、元のインターフェイスやコード ロジックは影響を受けません。
- 内部実装を直接アクセスから保護します。内部実装をラッパー クラスにカプセル化すると、外部コードが内部状態に直接アクセスして変更するのを防ぐことができるため、システムのセキュリティが向上します。
- ポリモーフィズムがサポートされています。パッケージング クラスを通じて、オブジェクトのさまざまな実装メソッドを統合してポリモーフィズムをサポートできるため、コードがより柔軟で拡張しやすくなります。
- データは検証および処理できます。データの正確性と整合性を確保するために、データの検証および処理メソッドをラッパー クラスに追加できます。
ラッパー クラスを使用したカプセル化の例を次に示します。
public class Shape {
private String name;
public Shape(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
throw new IllegalArgumentException("Cannot modify the shape name");
}
}
public class ShapeWrapper implements Shape {
private final Shape innerShape;
public ShapeWrapper(String name) {
innerShape = new Shape(name);
}
@Override
public String getName() {
return innerShape.getName();
}
@Override
public void setName(String name) throws IllegalArgumentException {
innerShape.setName(name);
}
}
public class Main {
public static void main(String[] args) {
ShapeWrapper circle = new ShapeWrapper("Circle");
System.out.println(circle.getName()); // 输出"Circle"
try {
circle.setName("Square"); // 抛出异常,因为setName方法被final修饰了
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 输出"Cannot modify the shape name"
}
}
}
この例では、Shape
クラスとShapeWrapper
クラスを定義します。このうち、ShapeWrapper
クラスはShape
クラスを継承し、Shape
get メソッドと set メソッドをオーバーライドします。結合が減少し、コードの再利用性が向上しました。
要約:
適切なカプセル化により、コードの品質と効率が向上し、コードの保守と拡張が容易になりますが、過度にカプセル化しないでください。