11.1 オブジェクト指向の基本
11.1.1 カプセル化
オブジェクト指向プログラミングの基本となる概念の一つがカプセル化です。これは、データとそのデータを操作するメソッドを一つの単位(オブジェクト)にまとめ、外部に不必要な情報を隠蔽する仕組みです。
以下の例では、学生に関する情報をカプセル化したクラスを定義しています。
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
この例では、nameとageフィールドはプライベートで、外部からはgetName()やgetAge()メソッドを通じてのみアクセス可能です。これにより、内部実装の隠蔽とデータの整合性維持を実現しています。
11.1.2 継承
継承は、既存のクラス(スーパークラス)の機能を引き継いで新しいクラス(サブクラス)を作成する仕組みです。共通の機能をスーパークラスに集約することで、コードの再利用性を高めます。
例えば、先ほどのStudentクラスを基に大学生を表すクラスを作成する場合:
class CollegeStudent extends Student {
private String major;
public CollegeStudent(String name, int age, String major) {
super(name, age);
this.major = major;
}
public String getMajor() {
return major;
}
}
このようにすることで、CollegeStudentクラスはStudentクラスの持つ機能をすべて引き継ぎつつ、専攻を表すmajorフィールドを追加しています。
11.1.3 ポリモーフィズム
ポリモーフィズム(多態性)は、同じインターフェースに対して異なる実装を提供する仕組みです。継承とメソッドのオーバーライドを組み合わせることで実現します。
abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
void makeSound() {
System.out.println("ワンワン");
}
}
class Cat extends Animal {
void makeSound() {
System.out.println("ニャンニャン");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // "ワンワン"と出力
myCat.makeSound(); // "ニャンニャン"と出力
}
}
この例では、Animalという抽象クラスを基に、DogとCatがそれぞれ異なる鳴き声を実装しています。ポリモーフィズムにより、同じmakeSound()メソッドがオブジェクトによって異なる動作をします。
11.2 コード依存の発生要因
コード依存は、オブジェクト間の相互作用によって必然的に発生します。主な発生要因は以下の通りです:
- メソッド呼び出し:あるオブジェクトが他のオブジェクトのメソッドを呼び出す
- 継承:サブクラスがスーパークラスに依存する
- メンバー変数:あるオブジェクトが他のオブジェクトを保持する
- パラメータ:メソッドの引数として他のオブジェクトを使用する
- 一時変数:メソッド内で他のオブジェクトを一時的に使用する
11.3 依存度の軽減
過度なコード依存は、保守性や拡張性の低下を招きます。以下の方法で依存度を軽減することが重要です:
11.3.1 抽象化の活用
具体的なクラスに依存するのではなく、抽象的なインターフェースや抽象クラスに依存することで、柔軟性の高い設計が可能になります。
interface Database {
void executeQuery(String query);
}
class MySQLDatabase implements Database {
public void executeQuery(String query) {
// MySQLの実装
}
}
class MongoDB implements Database {
public void executeQuery(String query) {
// MongoDBの実装
}
}
class DataManager {
private Database database;
public DataManager(Database database) {
this.database = database;
}
public void fetchData() {
database.executeQuery("SELECT * FROM data");
}
}
この例では、DataManagerが具体的なデータベース実装に依存せず、Databaseインターフェースに依存しています。これにより、データベースの実装を簡単に切り替えることができます。
11.3.2 依存性の注入(DI)
依存性の注入(Dependency Injection)は、オブジェクトの依存関係を外部から与える仕組みです。主な方法は以下の通りです:
- コンストラクタインジェクション:コンストラクタで依存を注入
- メソッドインジェクション:メソッドの引数で依存を注入
- プロパティインジェクション:セッター経由で依存を注入
class Service {
private Repository repository;
// コンストラクタインジェクション
public Service(Repository repository) {
this.repository = repository;
}
// メソッドインジェクション
public void doSomething(Repository repository) {
repository.save();
}
// プロパティインジェクション
public void setRepository(Repository repository) {
this.repository = repository;
}
}
11.4 フレームワークにおける依存
フレームワークでは、制御の反転(Inversion of Control)という概念が重要です。通常のアプリケーションでは開発者がライブラリを呼び出しますが、フレームワークではフレームワークが開発者のコードを呼び出します。
依存性の注入(DI)は、この制御の反転を実現するための重要なテクニックです。Spring Frameworkなどの多くの現代的なフレームワークは、DIをコア機能として提供しています。
@Component
class MyService {
// ...
}
@Component
class MyController {
@Autowired
private MyService myService;
// ...
}
この例では、Spring FrameworkがMyControllerにMyServiceのインスタンスを自動で注入します。