[Java] 14. オブジェクト指向プログラミング(OOP)の4つ特性(カプセル化、抽象化、継承、多相化)


Study / Java    作成日付 : 2019/08/22 20:08:37   修正日付 : 2021/01/14 16:14:12

こんにちは。明月です。


この投稿はJavaのオブジェクト指向プログラミング(OOP)の4つ特性(カプセル化、抽象化、継承、多相化)に関する説明です。


オブジェクト指向(Object-Oriented Programming)ということはプログラミングの開発方法の中で一つです。ここでプログラミングの開発方法とは、プログラムを開発する時にどの目的に関して開発することかについての方法です。

その中でオブジェクト指向(OOP)はオブジェクト(Object)の中心でプログラムを設計、開発するという意味です。


例えば、「業務計画書作成 -> 計画実行 -> テスト -> 結果確認 -> 報告書作成 -> 決済 -> 承認」ということの一つの業務プロセスを考えましょう。

ここでまず全体の業務単位(Controller)で構成して計画書データ、テストデータ、結果データ、報告書データ、決済データのオブジェクトのプロセスに配置します。

プログラム言語として考えると最小なオブジェクトの単位のクラスにして上の業務オブジェクトをクラスに特性を与えて管理することがオブジェクト指向プロジェクトになります。


このオブジェクト指向(OOP)には4つ原則がありますが、それがカプセル化、抽象化、継承、多相化ということです。

この原則に関しては部分的に説明したことがあります。

link - [Java] 9. アクセス修飾子とstatic

link - [Java] 12. インタフェース(interface)

link - [Java] 8. クラスの継承とthis、superキーワードの使い方

link - [Java] 6. 関数の使い方(関数のオーバーロードと再帰的な方法について)

この投稿ではその特性に関して詳細にまとめて説明します。

カプセル化

カプセル化とは言語的に表現は何かを包むという意味です。つまり、アクセス修飾子でクラスのデータと関数を秘匿化するという意味ですが、私の考えはクラスの特性を正確に区分する方法ではないかと思います。

例えば、一つクラス(組)で10人がいます。そして国語、英語、数学の成績のオブジェクトを作成しましょう。

import java.util.ArrayList;
import java.util.List;
// 国語クラス
class Japanese {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public Japanese(int score) {
    this.score = score;
  }
}
// 英語クラス
class English {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public English(int score) {
    this.score = score;
  }
}
// 数学クラス
class Math {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public Math(int score) {
    this.score = score;
  }
}
// 学生クラス
class People {
  // 名前
  private String name;
  // 国語成績
  private Japanese japanese;
  // 英語成績
  private English english;
  // 数学成績
  private Math math;
  // コンストラクタで名前と点数を受け取る。
  public People(String name, int japanese, int english, int math) {
    this.name = name;
    this.japanese = new Japanese(japanese);
    this.english = new English(english);
    this.math = new Math(math);
  }
}
// 組のクラス
class SchoolClass {
  // 組の人のリスト
  private final List<People> peoples = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // 学生を追加する。
    peoples.add(new People(name, japanese, english, math));
  }
}
// 実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
  }
}

最小単位で学生のオブジェクトが作り、各の国語、英語、数学のオブジェクトを作りました。そして学生は国語、英語、数学のオブジェクトを持っていて、組クラスは学生のリストを持っています。

私が考えでオブジェクト指向タイプでプログラムを作りました。オブジェクト指向タイプでプログラムを作らないと別にクラスの存在が必要かなと思います。


ただ、リストでプログラムを作りましょう。

import java.util.ArrayList;
import java.util.List;
// 組のクラス
class SchoolClass {
  // 学生リスト
  private final List<String> peoples = new ArrayList<>();
  // 国語リスト
  private final List<Integer> japanese = new ArrayList<>();
  // 数学リスト
  private final List<Integer> english = new ArrayList<>();
  // 英語リスト
  private final List<Integer> math = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // リストの配列位置で学生成績を探す。
    // 学生追加
    this.peoples.add(name);
    // 国語成績追加
    this.japanese.add(japanese);
    // 英語成績追加
    this.english.add(english);
    // 数学成績追加
    this.math.add(math);
  }
}

// 実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
  }
}

上のことはオブジェクト指向プログラミングではないです。でも結果は同じく出ると思います。そうすると人によってコーディングスタイルがあるので結果だけ出ることになれば別に大変に作成する必要があるかと思う人もいると思います。

それなら上のプログラムから機能を拡張しましょう。総点を計算して平均点数、席次について計算する機能を追加しましょう。

/*SchoolClassクラスに追加*/
// 総点を計算する関数
public int sum(int index) {
  // 国語、英語、数学の成績を合わせる。
  return this.japanese.get(index) + this.english.get(index) + this.math.get(index);
}
// 平均点数を計算する関数
public int avg(int index) {
  // 総点から3を割る。
  return sum(index) / 3;
}
// 組の席次を計算する。
public int getRank(int index) {
  // 1等から始める。
  int rank = 1;
  // 他の学生と比較する。
  for (int i = 0; i < peoples.size(); i++) {
    // 総点を取得
    int sum = sum(index);
    // 同じインデックスはスキップ
    if (i == index) {
      continue;
    }
    // 比較する学生の成績が上なら席次を落ちる。
    if (sum(i) > sum) {
      rank++;
    }
  }
  return rank;
}
// 総点と平均点数、席次を出力する。
public void print() {
  // 組の人をすべて出力する。
  for (int i = 0; i < peoples.size(); i++) {
    // 席次を計算する。
    int rank = getRank(i);
    // コンソール出力
    System.out.println(peoples.get(i) + " total = " + sum(i) + ", avg = " + avg(i) + ", ranking = " + rank);
  }
}
/*SchoolClassクラスに追加*/


総点、平均点数、席次だけ計算してもプログラムがすぐ複雑になります。


オブジェクト指向(OOP)タイプで作成しましょう。

/*Peopleクラスに追加*/
// 総点を計算する関数
public int sum() {
  // 国語、英語、数学の成績を合わせる。
  return this.japanese.getScore() + this.english.getScore() + this.math.getScore();
}
// 平均点数を計算する。
public int avg() {
  // 総点から3を割る。
  return sum() / 3;
}
/*Peopleクラスに追加*/
/*SchoolClassクラスに追加*/
// 組の席次を計算する。
public int getRank(People people) {
  // 1等から始める。
  int rank = 1;
  // 他の学生と比較
  for (int i = 0; i < peoples.size(); i++) {
    // 学生取得
    People other = peoples.get(i);
    // 同じインデックスはスキップ
    if (other == people) {
      continue;
    }
    // 比較する学生の成績が上なら席次を落ちる。
    if (other.sum() > people.sum()) {
      rank++;
    }
  }
  return rank;
}

// 総点と平均点数、席次を出力する関数
public void print() {
  // 組の人をすべて出力する。
  for (int i = 0; i < peoples.size(); i++) {
    // 学生取得
    People people = peoples.get(i);
    // 席次を計算する。
    int rank = getRank(people);
    // コンソール出力
    System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
  }
}
/*SchoolClassクラスに追加*/


オブジェクト指向ならクラスが多いからソースステップは長くなりますが、各オブジェクトから総点を計算して平均点数を計算することでソースを見やすくなります。

まだ、そんなにオブジェクト指向が何がいいかを感じられないです。続けて機能を追加しましょう。

今回は上位30%の成績と下位30%の成績の人を計算しましょう。


オブジェクト指向ではないプログラムです。

/* SchoolClassクラスに追加 */
// 上位30%人と下位30%の人を計算する関数
public void print2() {
  // 30%の該当するカウント
  int count = peoples.size() / 3;
  // 席次の区分で計算する。
  for (int i = 1, z = 1; i <= count; z++) {
    // 学生の数ほど
    for (int j = 0; j < peoples.size(); j++) {
      // ランクを計算する。
      int rank = getRank(j);
      // ランクと順位が同じなら
      if (rank == z) {
        // コンソール出力
        System.out.println("Top " + z + " - " + peoples.get(j));
        i++;
      }
    }
  }
  // 改行追加
  System.out.println();
  // 席次の区分で計算する。
  for (int i = 1, z = peoples.size(); i <= count; z--) {
    // 学生の数ほど
    for (int j = 0; j < peoples.size(); j++) {
      // ランクを計算する。
      int rank = getRank(j);
      // ランクと順位が同じなら
      if (rank == z) {
        // コンソール出力
        System.out.println("Sub " + z + " - " + peoples.get(j));
        i++;
      }
    }
  }
}
/* SchoolClassクラスに追加 */


ソースが少しずつ暗号化になります。


オブジェクト指向タイプに作成しましょう。

/* SchoolClassクラスに追加 */
// 上位30%人と下位30%の人を計算する関数
public void print2() {
  // 成績順で再分類するためのソート変数
  List<People> sort = new ArrayList<>();
  // 順位別繰り返す。
  // ソートアルゴリズムを使ってもよいが、理解しやすいため順位別ソートする。
  for (int i = 0; i < peoples.size(); i++) {
    // 学生取得
    for (int j = 0; j < peoples.size(); j++) {
      // 学生取得
      People people = peoples.get(j);
      // 順位がi+1、つまり0が1
      if (getRank(people) == i + 1) {
        // 順位別に再整列
        sort.add(people);
      }
    }
  }
  // 30%の該当するカウント
  int count = sort.size() / 3;
  // 上位30%出力
  for (int i = 0; i < count; i++) {
    // コンソール出力
    System.out.println("Top " + (i + 1) + " - " + sort.get(i).getName());
  }
  // 改行出力
  System.out.println();
  // 下位30%出力
  for (int i = sort.size() - 1; i >= sort.size() - count; i--) {
    // コンソール出力
    System.out.println("Sub " + (i + 1) + " - " + sort.get(i).getName());
  }
}
/* SchoolClassクラスに追加 */


オブジェクト指向の関数の場合がソースの可読がしやすいです。

ここでオブジェクト指向はただソートアルゴリズムで作成したので、簡単になります。オブジェクト指向ではない場合も、ソートを使うことにできますが、ソート値を格納する変数が成績別、名前別など4つ以上が必要です。

そしてsumとavg関数はメンバー変数に関する値なので、上位30%と下位30%のためのsumとavg関数が必要です。つまり、仕様が追加するために修正する範囲も増えるし複雑になります。


上のソースを見れはメンバー変数はすべてprivateでアクセスを制限しました。実はオブジェクト指向ではメンバー変数は必ずprivateに設定しましょうというのは暗黙的なルールです。

なぜなら、クラスを最小のオブジェクト単位ですが、その中のデータをクラス外部で勝手に修正するのは、オブジェクト指向らしくないでしょう。


今回は席次を計算することでデータ量が多すぎて、席次データを返却するたびに計算することではなく、初期化機能を入れて一回でデータを格納しましょう。

返却するたびに計算することは10人ごろは問題なく計算できますが、学生が1満人だと思えば、簡単ではないデータでしょう。

import java.util.ArrayList;
import java.util.List;

// 国語クラス
class Japanese {
  // 点数
  private int score;

  // コンストラクタで点数を受け取る。
  public Japanese(int score) {
    this.score = score;
  }

  // 点数をリターン
  public int getScore() {
    return this.score;
  }
}
// 英語クラス
class English {
  // 点数
  private int score;

  // コンストラクタで点数を受け取る。
  public English(int score) {
    this.score = score;
  }

  // 点数をリターン
  public int getScore() {
    return this.score;
  }
}
// 数学クラス
class Math {
  // 点数
  private int score;

  // コンストラクタで点数を受け取る。
  public Math(int score) {
    this.score = score;
  }

  // 点数をリターン
  public int getScore() {
    return this.score;
  }
}
// 学生クラス
class People {
  // 名前
  private String name;
  // 国語成績
  private Japanese japanese;
  // 英語成績
  private English english;
  // 数学成績
  private Math math;
  // 総点
  private int sum;
  // 平均点数
  private int avg;
  // 席次
  private int rank;
  // コンストラクタで名前と点数を受け取る。
  public People(String name, int japanese, int english, int math) {
    this.name = name;
    this.japanese = new Japanese(japanese);
    this.english = new English(english);
    this.math = new Math(math);
  }
  // 名前取得関数
  public String getName() {
    return this.name;
  }
  // 総点取得関数
  public int sum() {
    return this.sum;
  }
  // 平均取得関数
  public int avg() {
    return this.avg;
  }
  // 席次取得関数
  public int getRank() {
    return this.rank;
  }
  // 総点と平均点数を計算を初期化する。
  public void init() {
    // 国語、英語、数学を合わせる。
    this.sum = this.japanese.getScore() + this.english.getScore() + this.math.getScore();
    // 総点で3を割る。
    this.avg = this.sum / 3;
  }
  // 席次を計算する。
  public void initRank(List<People> peoples) {
    // 1等から始まる。
    int rank = 1;
    // 別の学生と比較する。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People other = peoples.get(i);
      // 本人ならスキップ
      if (other == this) {
        continue;
      }
      // 比較する学生成績が上なら席次を落ちる。
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 組のクラス
class SchoolClass {
  // 組の人のリスト
  private final List<People> peoples = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // 学生を追加する。
    peoples.add(new People(name, japanese, english, math));
  }
  // 初期化関数
  public void init() {
    // 総点は平均点数を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 席次を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 総点、平均点数、席次を出力する。
  public void print() {
    // 組の人のすべてを出力する。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People people = peoples.get(i);
      // 席次を計算する。
      int rank = people.getRank();
      // コンソール出力
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
  // 上位30%人と下位30%の人を計算する関数
  public void print2() {
    // 成績順で再分類するためのソート変数
    List<People> sort = new ArrayList<>();
    // 順位別繰り返す。
    // ソートアルゴリズムを使ってもよいが、理解しやすいため順位別ソートする。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      for (int j = 0; j < peoples.size(); j++) {
        // 学生取得
        People people = peoples.get(j);
        // 順位がi+1、つまり0が1
        if (people.getRank() == i + 1) {
          // 順位別に再整列
          sort.add(people);
        }
      }
    }
    // 30%の該当するカウント
    int count = sort.size() / 3;
    // 上位30%出力
    for (int i = 0; i < count; i++) {
      // コンソール出力
      System.out.println("Top " + (i + 1) + " - " + sort.get(i).getName());
    }
    // 改行出力
    System.out.println();
    // 下位30%出力
    for (int i = sort.size() - 1; i >= sort.size() - count; i--) {
      // コンソール出力
      System.out.println("Sub " + (i + 1) + " - " + sort.get(i).getName());
    }
  }
}

// 実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 総点と平均点数、席次を初期化する。
    schoolclass.init();
    // 成績を出力する。
    schoolclass.print();
    // 改行
    System.out.println();
    // 上位30%と下位の30%を出力する。
    schoolclass.print2();
  }
}


init関数とinitRank関数を追加しました。このデータは各学生を入力して一括で計算することです。それなら席次のデータは返却するたびに計算することではなく、データだけ取得すればよいです。

でも、ここでその変数をクラス内部ではなく、クラス外部で修正することができれば?席次のデータ整合性が可笑しくなります。

それなら修正しても問題ない変数はpublicして整合性が必要な変数だけprivateすればどうでしょう。開発はルールがあります。メンバー変数はprivateで宣言しましょう。

継承

継承は似ているクラス別に親クラスとインタフェースを定義して共通化します。その後で親クラスを継承することです。その利点はソースの可読をしやくしするし管理的にはよいことにすることです。

上の例だけみてもオブジェクト指向で作成しようと思ってもソースステップが多くなります。成績オブジェクトを共通化しましょう。

import java.util.ArrayList;
import java.util.List;

// 科のインタフェース
interface ISubject {
  public int getScore();
}
// 科の抽象クラス
abstract class AbstractSubject implements ISubject {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 点数取得関数(再定義)
  public int getScore() {
    return this.score;
  }
}
// 国語クラス
class Japanese extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Japanese(int score) {
    super(score);
  }
}
// 英語クラス
class English extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public English(int score) {
    super(score);
  }
}
// 数学クラス
class Math extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Math(int score) {
    super(score);
  }
}
// 学生クラス
class People {
  // 名前
  private String name;
  // 0 - 国語, 1 - 英語, 2 - 数学
  final private List<ISubject> subjects = new ArrayList<>();
  // 総点
  private int sum;
  // 平均点数
  private int avg;
  // 席次
  private int rank;
  // コンストラクタで名前と点数を受け取る。
  public People(String name, int japanese, int english, int math) {
    this.name = name;
    // 0 - 国語
    this.subjects.add(new Japanese(japanese));
    // 1 - 英語
    this.subjects.add(new English(english));
    // 2 - 数学
    this.subjects.add(new Math(math));
  }
  // 名前取得関数
  public String getName() {
    return this.name;
  }
  // 総点取得関数
  public int sum() {
    return this.sum;
  }
  // 平均点数取得関数
  public int avg() {
    return this.avg;
  }
  // 席次取得関数
  public int getRank() {
    return this.rank;
  }
  // 総点、平均点数を初期化する。
  public void init() {
    // 国語、英語、数学成績を合わせる。
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 総点から3を割る。
    this.avg = this.sum / this.subjects.size();
  }
  // 席次を計算する。
  public void initRank(List<People> peoples) {
    // 1等から始まる。
    int rank = 1;
    // 学生と比較
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People other = peoples.get(i);
      // 本人はスキップ
      if (other == this) {
        continue;
      }
      // 比較する学生成績が上なら席次を落ちる。
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
//組のクラス
class SchoolClass {
  // 組の人のリスト
  private final List<People> peoples = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // 学生を追加する。
    peoples.add(new People(name, japanese, english, math));
  }
  // 初期化関数
  public void init() {
    // 総点は平均点数を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 席次を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 総点、平均点数、席次を出力する。
  public void print() {
    // 組の人のすべてを出力する。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People people = peoples.get(i);
      // 席次を計算する。
      int rank = people.getRank();
      // コンソール出力
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg()
          + ", ranking = " + rank);
    }
  }
}

//実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 総点と平均点数、席次を初期化する。
    schoolclass.init();
    // 成績を出力する。
    schoolclass.print();
  }
}


上で共通になる科目に関して国語、英語、数学クラスはインタフェースを宣言して抽象クラスで共通化しました。始めから科目クラスのサイズが小さかったのでソースがそんなに減ったと思われます。でも実務ではクラスを共通化するとソースがずいぶん減ることを見えます。

また、仕様変更でソース修正があるときにコピペだとクラスのすべてを修正しなければならないですが、共通化すると一つのところを修正することで解決ができます。

抽象化

上の継承を説明する時に抽象化に関しても含めていますが、ここではメソッド抽象化と再定義(Override)に関する説明を続けていきます。

ここで我々は国語、英語、数学が100点基準で計算しましたが、仕様変更で国語は100点から120点、英語は100点から80点、数学は100点から100点の点数配点を修正しなければならないです。

import java.util.ArrayList;
import java.util.List;

// 科のインタフェース
interface ISubject {
  public int getScore();
}
// 科の抽象クラス
abstract class AbstractSubject implements ISubject {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 点数取得関数(再定義)
  public int getScore() {
    // int型をfloat型にキャスティング
    float buffer = (float) this.score;
    // 配列計算
    return (int) (buffer * getRate());
  }
  // 配列を取得する。
  protected abstract float getRate();
}
// 国語クラス
class Japanese extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Japanese(int score) {
    super(score);
  }
  // 国語倍率は1.2
  @Override
  public float getRate() {
    return 1.2f;
  }
}
// 英語クラス
class English extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public English(int score) {
    super(score);
  }
  // 英語倍率は0.8
  @Override
  public float getRate() {
    return 0.8f;
  }
}
// 数学クラス
class Math extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Math(int score) {
    super(score);
  }
  // 数学倍率は1
  @Override
  public float getRate() {
    return 1f;
  }
}
// 学生クラス
class People {
  // 名前
  private String name;
  // 0 - 国語, 1 - 英語, 2 - 数学
  final private List<ISubject> subjects = new ArrayList<>();
  // 総点
  private int sum;
  // 平均点数
  private int avg;
  // 席次
  private int rank;
  // コンストラクタで名前と点数を受け取る。
  public People(String name, int japanese, int english, int math) {
    this.name = name;
    // 0 - 国語
    this.subjects.add(new Japanese(japanese));
    // 1 - 英語
    this.subjects.add(new English(english));
    // 2 - 数学
    this.subjects.add(new Math(math));
  }
  // 名前取得関数
  public String getName() {
    return this.name;
  }
  // 総点取得関数
  public int sum() {
    return this.sum;
  }
  // 平均点数取得関数
  public int avg() {
    return this.avg;
  }
  // 席次取得関数
  public int getRank() {
    return this.rank;
  }
  // 総点、平均点数を初期化する。
  public void init() {
    // 国語、英語、数学成績を合わせる。
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 総点から3を割る。
    this.avg = this.sum / this.subjects.size();
  }
  // 席次を計算する。
  public void initRank(List<People> peoples) {
    // 1等から始まる。
    int rank = 1;
    // 学生と比較
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People other = peoples.get(i);
      // 本人はスキップ
      if (other == this) {
        continue;
      }
      // 比較する学生成績が上なら席次を落ちる。
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 組のクラス
class SchoolClass {
  // 組の人のリスト
  private final List<People> peoples = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // 学生を追加する。
    peoples.add(new People(name, japanese, english, math));
  }
  // 初期化関数
  public void init() {
    // 総点は平均点数を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 席次を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 総点、平均点数、席次を出力する。
  public void print() {
    // 組の人のすべてを出力する。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People people = peoples.get(i);
      // 席次を計算する。
      int rank = people.getRank();
      // コンソール出力
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
}
// 実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 総点と平均点数、席次を初期化する。
    schoolclass.init();
    // 成績を出力する。
    schoolclass.print();
  }
}


抽象クラスに抽象メソッドのgetRate関数を追加して継承されたクラスからその配列を強制に再定義して値を受け取ることにしますた。

この利点は今回は逆に修正部分が増えることになりましたが、abstractを使うことでデバッガーから修正部分を発見することができます。なので、ミスに修正する確率が低いです。

オブジェクト指向で作成すればどの仕様変更でも修正がしやすくなります。もし、オブジェクト指向ではない場合に、上みたいに仕様変更があれば、どのぐらいに修正するか思われないですね。

多相化

多相化は同じメソッド名でパラメータが違う構造で作る関数です。

今回は選択科目を追加しましょう。

import java.util.ArrayList;
import java.util.List;

// 科のインタフェース
interface ISubject {
  public int getScore();
}
// 科の抽象クラス
abstract class AbstractSubject implements ISubject {
  // 点数
  private int score;
  // コンストラクタで点数を受け取る。
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 点数取得関数(再定義)
  public int getScore() {
    // int型をfloat型にキャスティング
    float buffer = (float) this.score;
    // 配列計算
    return (int) (buffer * getRate());
  }
  // 配列を取得する。
  protected abstract float getRate();
}
// 国語クラス
class Japanese extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Japanese(int score) {
    super(score);
  }
  // 国語倍率は1.2
  @Override
  public float getRate() {
    return 1.2f;
  }
}
// 英語クラス
class English extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public English(int score) {
    super(score);
  }
  // 英語倍率は0.8
  @Override
  public float getRate() {
    return 0.8f;
  }
}
// 数学クラス
class Math extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Math(int score) {
    super(score);
  }
  // 数学倍率は1
  @Override
  public float getRate() {
    return 1f;
  }
}
// 選択クラス
class Select extends AbstractSubject {
  // 親コンストラクタを呼び出す。
  public Select(int score) {
    super(score);
  }
  // 選択科の倍率は1
  @Override
  public float getRate() {
    return 1f;
  }
}
// 学生クラス
class People {
  // 名前
  private String name;
  // 0 - 国語, 1 - 英語, 2 - 数学
  final private List<ISubject> subjects = new ArrayList<>();
  // 総点
  private int sum;
  // 平均点数
  private int avg;
  // 席次
  private int rank;
  // コンストラクタで名前と点数を受け取る。
  public People(String name, int japanese, int english, int math) {
    this.name = name;
    // 0 - 国語
    this.subjects.add(new Japanese(japanese));
    // 1 - 英語
    this.subjects.add(new English(english));
    // 2 - 数学
    this.subjects.add(new Math(math));
  }
  // 多相化で選択科を追加した。
  public People(String name, int korean, int english, int math, int select) {
    // 基本コンストラクタを呼び出す。
    this(name, korean, english, math);
    // 3 - 選択科目を追加
    this.subjects.add(new Select(select));
  }
  // 名前取得関数
  public String getName() {
    return this.name;
  }
  // 総点取得関数
  public int sum() {
    return this.sum;
  }
  // 平均点数取得関数
  public int avg() {
    return this.avg;
  }
  // 席次取得関数
  public int getRank() {
    return this.rank;
  }
  // 総点、平均点数を初期化する。
  public void init() {
    // 国語、英語、数学成績を合わせる。
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 総点から3を割る。
    this.avg = this.sum / this.subjects.size();
  }
  // 席次を計算する。
  public void initRank(List<People> peoples) {
    // 1等から始まる。
    int rank = 1;
    // 学生と比較
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People other = peoples.get(i);
      // 本人はスキップ
      if (other == this) {
        continue;
      }
      // 比較する学生成績が上なら席次を落ちる。
      if (other.avg() > this.avg()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 組のクラス
class SchoolClass {
  // 組の人のリスト
  private final List<People> peoples = new ArrayList<>();
  // 学生を追加する関数。名前、国語、英語、数学成績を受け取る。
  public void addPeople(String name, int japanese, int english, int math) {
    // 学生を追加する。
    peoples.add(new People(name, japanese, english, math));
  }
  // 多相化で同じ関数名で学生追加関数、名前、国語、英語、数学、選択科目成績を受け取る。.
  public void addPeople(String name, int korean, int english, int math, int select) {
    // 学生を追加する。
    peoples.add(new People(name, korean, english, math, select));
  }
  // 初期化関数
  public void init() {
    // 総点は平均点数を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 席次を計算する。
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 総点、平均点数、席次を出力する。
  public void print() {
    // 組の人のすべてを出力する。
    for (int i = 0; i < peoples.size(); i++) {
      // 学生取得
      People people = peoples.get(i);
      // 席次を計算する。
      int rank = people.getRank();
      // コンソール出力
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg()
          + ", ranking = " + rank);
    }
  }
}
// 実行関数があるクラス
public class Example {
  // 実行関数
  public static void main(String... args) {
    // 組のインスタンスを生成する。
    SchoolClass schoolclass = new SchoolClass();
    // 学生を追加する。
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 二人は選択科目を追加した。
    schoolclass.addPeople("K", 60, 70, 30, 70);
    schoolclass.addPeople("L", 60, 70, 30, 100);
    // 総点と平均点数、席次を初期化する。
    schoolclass.init();
    // 成績を出力する。
    schoolclass.print();
  }
}


多相化は既存のクラスの関数名の修正がなしで同じ関数名を追加したのでクラスの目的のままで表現ができます。


オブジェクト指向プログラミングはオブジェクト別でデータを区分するのでプログラムの可読性がよいです。そしてメンテナンスをするところでも仕様変更や追加する領域が少ないのでコーディングミスでバグが発生する確率も少ないです。

でも、このオブジェクト指向でも短所があります。上の例はすべてデータタイプで説明したので、ずいぶん効率的に見えますが、すべてもプログラムがデータタイプで作ることではないです。

例えばロードバランシングやビデオチャットツールを開発すると思えば、これはデータ中心ではなくプロセス、つまり行動中心の流れなので、逆にオブジェクト指向タイプで開発すると複雑になります。


例でSpringというWebプロジェクトフレームワークあります。Web Siteを開発すると思えばデータベースからデータを受け取ってクライアント(ブラウザ)にデータを送信することは確かにデータ中心で動きます。

でも、url別でページ移動する時にControllerというクラスが呼び出しますが、データではなく行動中心なので、逆にクラスがすごく増える短所があります。

そのため、Springプロジェクトをすれば、データベースのModelクラスやフォームデータのBeanクラスの場合は整理がよくできますが、Controllerの場合は各ページによってクラスやメソッドが生成することで意味なしでクラスだけが増えます。

それでSpringプロジェクトをみれば、Controllerクラスだけすごく見えることになります。それもデベロッパーの能力によってrouteからどのようにbindingすることでその形も変わりますが。。。


こんな簡単なソースでもこの差異が見えますが、実務ではどうでしょう。オブジェクト指向で作成してないプログラムはほぼ理解できない可笑しいプロジェクトになります。


ここまでJavaのオブジェクト指向プログラミング(OOP)の4つ特性(カプセル化、抽象化、継承、多相化)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿