1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

拡張性のあるクラス設計を考えてみる

Last updated at Posted at 2025-01-28

拡張性のあるクラス設計とは何かを考えてみる

オブジェクト指向プログラミングやクラス設計に興味を持ち始めた方に向けて、拡張性のあるクラス設計について考えてみたいと思います。

対象読者 

下記の状態や疑問を持っている方

  • プログラムはある程度書けるようになった
  • クラス設計ってなに?
  • オブジェクト指向を学んでいるけど、何が便利なの?
  • インターフェースって使う必要ある?

概要

車を表現するクラスを例に3つの異なる設計パターンを紹介し、それぞれのメリットとデメリットを考えてみたいと思います

クラスの中身やディレクトリ構成は雑に作成...
また、フレームワークを使用していればデメリットが消える部分はあるが今回は度外視

ソースコードはこちらのgithubにあります


パターン1: 単純なクラス構造

CarクラスはBodyとWheelクラスをメンバ変数として持っており(コンポジション ※)
クライアントであるMainから使用される。

※コンポジションについて
(記事お借りしますm(_ _)m)

メリット:
 ・クラス構成がシンプル
 ・ファイルが少ない

デメリット:
 ・クラス設計をほとんど行わず拡張性がない構成
 ・carパッケージが密結合であり、Body、Wheelクラスの修正がCarクラスにもモロに受ける

クラス図

ディレクトリ構成

com/
└── pattern1/
    ├── car/
    │   ├── Body.java
    │   ├── Car.java
    │   └── Wheel.java
    │
    └── main/
        └── Main.java

コード

Main.java
package com.pattern1.main;

import com.pattern1.car.Car;

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.drive();
    }
}
Car.java
package com.pattern1.car;

public class Car {
    private Body body = new Body();
    private Wheel wheel = new Wheel();

    public void drive(){
        wheel.spin();
        System.out.println("車が走りました");
    }
}
Wheel.java
package com.pattern1.car;

public class Wheel {
    void spin(){
        System.out.println("タイヤを回します");
    }
}

Body.java
package com.pattern1.car;

public class Body {

}

メインメソッドの実行結果

タイヤを回します
車が走りました

パターン2: BodyとWheelクラスを抽象化し、部品の修正がCarクラスに影響を与えないように変更

CarクラスのBodyとWheelはMainクラスから依存性の注入(※)を行い使用する

※依存性の注入について
(記事お借りしますm(_ _)m)

メリット:
 ・車の部品であるBodyとWheelがインターフェースになったことで拡張性UP(部品の付け替えが可能となった)

デメリット:
 ・少し複雑
・クライアント側で依存性を注入する必要があり利用者側でcarパッケージの利用方法を知っておく必要がある

クラス図

ディレクトリ構成

com/
└── pattern2/
    ├── car/
    │   ├── BodySedan.class
    │   ├── Car.class
    │   ├── IBody.class
    │   ├── IWheel.class
    │   ├── WheelStudless.class
    │   └── WheelSummer.class
    │
    └── main/
        └── Main.class

コード

Main.java
package com.pattern2.main;

import com.pattern2.car.BodySedan;
import com.pattern2.car.Car;
import com.pattern2.car.WheelStudless;

public class Main {
    public static void main(String[] args) {
        Car car = new Car(new BodySedan(), new WheelStudless());
        car.drive();
    }
}
Car.java
package com.pattern2.car;

public class Car {
    private IBody body;
    private IWheel wheel;

    public Car(IBody body, IWheel wheel) {
    	this.body = body;
    	this.wheel = wheel;
    }
    
    public void drive(){
        wheel.spin();
        System.out.println("車が走りました");
    }

}

IBody.java
package com.pattern2.car;

public interface IBody {
}
BodySedan.java
package com.pattern2.car;

public class BodySedan implements IBody{

}

IWheel.java
package com.pattern2.car;

public interface IWheel {
	void spin();
}
WheelSummer.java
package com.pattern2.car;

public class WheelSummer implements IWheel{
	
	@Override
	public void spin(){
        System.out.println("高温に耐えながらタイヤを回します");
    }
}

WheelStudless.java
package com.pattern2.car;

public class WheelStudless implements IWheel{

	@Override
	public void spin() {
		System.out.println("滑りにくく回ります");
	}
}

メインメソッドの実行結果

滑りにくく回ります
車が走りました

パターン3: CarクラスをInterfaceとし抽象化、さらにFactory Methodパターンの採用でクライアント側の依存性の注入を簡略化

※Factory Methodについて
(記事お借りしますm(_ _)m)

メリット:
 ・Carクラスを含め、拡張性が高い
 ・クライアント側の依存性注入の手間をFactoryクラスであるCarFactoryが簡略化

デメリット:
 ・構成が複雑でコード理解に時間を要する
 ・修正を行う人も設計思想を理解している必要がある

クラス図

ディレクトリ構成

com/
└── pattern3/
    ├── car/
    │   ├── BodySedan.java
    │   ├── Car.java
    │   ├── CarFactory.java
    │   ├── IBody.java
    │   ├── ICar.java
    │   ├── IWheel.java
    │   ├── Wheel.java
    │   ├── WheelStudless.java
    │   └── WheelSummer.java
    │
    └── main/
        └── Main.java


コード

Main.java
package com.pattern3.main;

import com.pattern3.car.CarFactory;
import com.pattern3.car.ICar;

public class Main {
    public static void main(String[] args) {
        ICar car = CarFactory.getSedanStudlessCar();
        car.drive();
    }
}
ICar.java
package com.pattern3.car;

public interface ICar {
	void drive();
}

Car.java
package com.pattern3.car;

public class Car implements ICar{
    private IBody body;
    private IWheel wheel;

    public Car(IBody body, IWheel wheel) {
    	this.body = body;
    	this.wheel = wheel;
    }
    
    @Override
    public void drive(){
        wheel.spin();
        System.out.println("車が走りました");
    }

}
CarFactory.java
package com.pattern3.car;

public class CarFactory {
	public static ICar getSedanStudlessCar() {
		return new Car(new BodySedan(), new WheelStudless());
	}
	
	public static ICar getSedanSummerCar() {
		return new Car(new BodySedan(), new WheelSummer());
	}
}

以下pattern2と同様のため割愛

メインメソッドの実行結果

滑りにくく回ります
車が走りました

まとめ

・クラス構成によってメリットとデメリットが存在する
・最低限の拡張性を持った構成にしておくと後々楽になる反面、修正コストもかかる
・プロジェクトの規模やメンバの理解度を考慮したクラス設計が必要
・複雑なクラス設計を行う際は、設計思想のドキュメント化など対応が必要

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?