この記事を書こうとと思ったきっかけ
レガシーコードを日々改善し続けているのですが、あまりにもレガシーコードの改善しか仕事がないので、オブジェクト指向について学んでみたいと思いこの記事をアウトプットします。
オブジェクト指向とは
システムを実現するために必要な処理を部品化し、それを組み合わせること
なぜオブジェクト思考が必要か
- コードを書く上での問題
処理が長くなりすぎて人間の頭では何をしているかわからないものになっていく。
一旦、動くものを作ることができてもできても保守等をするときに
読み返したとき、何をどこで行なっているかわからない処理になってしまう。
→開発の一番のボトルネック
オブジェクト指向のプログラミングの考え方
- どこに何の機能を持たせるか
- その機能を組み合わせてどういう風にシステムを作っていくか
オブジェクト指向の定義
ソフトウェアを開発するプロセスを部品化し組み立てていく考え方
例えば、電車というモノ(オブジェクト)があったときに、
どのような動きをするものが電車であるかを考える。
- 電車が動く
- 電車が止まる
- 電車の扉が開く
- 電車の扉が閉じる
上記のように、電車というモノを作ると考えた時に
どういった処理をすることで電車として構成されているのかを考える
オブジェクト思考で大切な3大要素
- カプセル化
- 継承
- 多態性(ポリモーフィズム)
プログラムの一行一行が何をしているかではなく、オブジェクト(処理)をどう作り、どのように連携させていくかを軸に考えるために上記の3大要素を活用していく
3大要素をそれぞれ解説していく。
カプセル化
以下のようなVehicleクラスがあるとしてmoveOnというメソッドが用意されている。
- 呼び出し元はmoveOnというメソッドを呼んでいることしか分からない
- メソッド内で使用しているisMoveは外部から隠蔽されていると言える。
このような作りをカプセル化という。
public class Vehicle {
private Boolean isMove = false;
public void moveOn() {
this.isMove = true;
System.out.println("乗り物が動きます");
}
public void stop() {
this.isMove = false;
System.out.println("乗り物が停まります");
}
}
カプセル化は、フィールドへの読み書きやメソッドへの呼び出しを制限して、実装をするため、オブジェクト内部の変更が外部に対して予期しない干渉・影響を与えて壊れにくくなる。
アクセス制御は以下の4段階がある。
制限の強さ(黒星が多いほど厳しい) | 名前 | 記載方法 | アクセス許可範囲 |
---|---|---|---|
★★★★ | private | privateと記載 | 自分自身のクラスのみ |
★★★☆ | package private | 何も書かない | 自分と同じパッケージに属するクラス |
★★☆☆ | protected | protectedと記載 | 自分と同じパッケージに属するか、自分を継承した子クラス |
★☆☆☆ | public | publicと記載 | すべてのクラス |
継承とはなにか
以下のようにVehicleクラスとそれを継承したTrainクラスがある。
この場合、次のように呼称することがある。
- Vehicle: 親クラス、スーパークラス
- Train: 子クラス、サブクラス
ざっくりいうと、親クラスを引き継ぐ(extends)ことを継承という。
下記のTrainはmoveOnはOverrideして独自に実装しているが、stopは親クラスのメソッドを使用している。
public class Vehicle {
protected Boolean isMove = false;
public void moveOn() {
this.isMove = true;
System.out.println("乗り物が動きます");
}
public void stop() {
this.isMove = false;
System.out.println("乗り物が停まります");
}
public void setIsMove(Boolean isMove) {
this.isMove = isMove;
}
public void info() {
System.out.println("乗り物(Vehicle)です");
}
}
public class Train extends Vehicle {
private Boolean isOpenDoor;
@Override
public void moveOn() {
if (this.isOpenDoor) {
throw new RuntimeException("例外エラーです");
}
super.moveOn();
}
public void closeDoor() {
this.isOpenDoor = false;
}
public void openDoor() {
if (super.isMove) {
throw new RuntimeException("例外エラーです");
}
System.out.println("扉を開けました");
}
public void info() {
System.out.println("電車(Train)です");
}
}
実際に動かしてみる。
public class Main {
public static void main(String[] args) {
Train train = new Train();
train.closeDoor();
train.moveOn();
train.stop();
}
}
【結果】
Vehicleを継承しているため、stopも問題なく呼び出せていることがわかる。
乗り物が動きます
乗り物が停まります
Process finished with exit code 0
多態性(Polymorphism)とは何か
多態性とは、物事を大きく捉えることにより、違うものを同じように扱うこと。
簡単に言うと、これまで登場したTrainをVehicleとして扱うこと。
新たにCarクラスを作成し、Vehicleとして扱ってみる。(※継承の時に利用したTrainとVehicleを再利用して...)
public class Car extends Vehicle {
private Boolean isStartEngine = false;
private String gearMode = "NEUTRAL";
@Override
public void moveOn() {
if (!isStartEngine) {
throw new RuntimeException("エンジンがかかっていません");
}
if (gearMode.equals("NEUTRAL")) {
throw new RuntimeException("ニュートラルでは進めません");
}
super.moveOn();
}
public void setGear(String mode) {
switch (mode) {
case "NEUTRAL":
case "DRIVE":
case "BACK":
gearMode = mode;
default:
throw new RuntimeException("指定されたモードが存在しません");
}
}
public void info () {
System.out.println("車(Car)です");
}
}
public class Main {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
Vehicle vehicleCar = new Car();
Vehicle vehicleTrain = new Train();
vehicle.info();
vehicleCar.info();
vehicleTrain.info();
}
}
【結果】 Vehicleを親クラスとしているのでCar・TrainはVehicleとして扱えており、多態性を満たしていると言える。
オブジェクト指向のメリット
- ブログラムを容易に変更しやすくなる
- プログラムの一部を簡単に転用できる
手続き型プログラミングとオブジェクト指向のプログラミングの違い
- プログラムの先頭から順番に命令として記述していくのが手続き型
- 実現しようとするプログラムを部品で考え、処理を分け組み合わせるのがオブジェクト指向
感想
使われ続けていくシステムを作るために、見やすく、わかりやすく、安全性の高いコードを書くためにオブジェクト指向の基本的なことを知ることはとても大切だと感じた。
概要を難しい言葉を使わずに記載しましたが、実際にソースコードを書く際はそれぞれの機能にもルールがあるのでそれを確認しながら活用していきたい。