計画と改善

エンジニアのブログ

オブジェクト指向でなぜつくるのか前半まとめ

オブジェクト指向でなぜつくるのか。前半部分のみ主なポイントと解釈をまとめた。 なおサンプルコードは全てJava

オブジェクト指向(Object Oriented Programming)とは

物を中心にソフトウェアを組み上げる開発手法。個々の部品の独立性を高め、修正の影響範囲を限定する。独立性を高めることで、再利用性が高まり、それらを組み合わせてソフトウェアを作ることができる。

オブジェクト指向の三代要素として、クラス(カプセル化)、ポリモーフィズム、継承という概念がある。

OOPが誕生するまでの歴史的経緯

機械語アセンブラ言語

コンピュータは二進数で記述された機械語しか理解できない。 そのため、アセンブリ言語と呼ばれるアセンブラと呼ばれるコンパイラ機械語を生成するプログラミング言語が登場した。しかしアセンブリ言語は実行命令を一つ一つ記述していく形式を取っており、人間にとっては煩雑なプログラムであった。

高級言語の登場

アセンブラ言語のような人間ではわかりづらい命令を一つ一つ記述していく形式からより人間にわかりやすい計算式を使った文法表現に進化した。COBOLFORTRANなどの高級言語の登場によりプログラミングの生産性や品質が大きく登場した。

構造化プログラミングの登場

構造化プログラミングとは簡単に言えばプログラムの構造をわかりやすく記述しようというもの。

オランダ人の学者エドガー・ダイクストラ氏によって提唱された考え方。

GOTO文と呼ばれる所定の位置に処理を移動させる構文があったが、スパゲティコードになりやすかったため、それを廃止し、ロジックを順次進行、条件分岐、繰り返しの3つの構造だけで表現することが推奨された。現在で普通に利用されているif文やcase文やfor文などを利用してプログラミングを行うということ。

サブルーチンの独立性を高める動き

サブルーチンとは関数のこと。対して呼び出し側はメインルーチンと呼ぶ。

サブリーチンの独立性を高める動きとは、サブルーチンの中で共有する情報を少なくする動きのことを指す。ここでいう共有する情報とはグローバル変数のこと。

複数のサブルーチンで変数を共有してしまうと、変数がどこで書き変わっているのか突き止めるのが困難になってしまい、保守性が下がってしまう。

そこで、ローカル変数と引数の値渡しという仕組みが導入された。

Javaだと以下のように書ける。引数は値渡しとなるので、xTo999関数に引数としてxを渡し、xを変更してもメインルーチンには影響しない。(サブルーチンが独立している)

public class Main {
  public static void xTo999(int x) {
    x = 999;
  }

  public static void main(String[] args) {
    int x = 10;
    System.out.println(x);//10
    xTo999(x);
    System.out.println(x);//10
  }
}

貧弱な再利用の問題

構造化言語で再利用可能だったのはサブルーチンのみであった。この頃入出力計算、数値計算、文字列処理計算などの基本的な汎用ライブラリは存在していたが、莫大するアプリケーションの規模からすると、より大規模な再利用の必要性は高まっていた。

OOPはその解決策として登場した技術である。

クラス

まとめて隠してたくさん作る仕組み。

  1. サブルーチンと変数をまとめる
  2. クラス内部だけで使用するサブルーチンと変数を隠す
  3. 一つのクラスからインスタンスをたくさん作る

OOPではクラスに定義されたサブルーチンをメソッドと呼び、グローバル変数インスタンス変数と呼ぶ。実際にはメソッドやインスタンス変数は、公開するスコープを限定して利用することもできる。

public class Main {
  public static void main(String[] args) {
    Calculator calculator = new Calculator(10, 20);// インスタンスを作る
    int result = calculator.calc();
    System.out.println(result); // 30

    Calculator otherCalculator = new Calculator(30, 50);// 別のインスタンスを作る
    int otherResult = otherCalculator.calc();
    System.out.println(otherResult); // 80
  }
}
// classにサブルーチン(メソッド)と変数をまとめる
class Calculator {

  private int num1; //privateでスコープを限定して隠す
  private int num2; //privateでスコープを限定して隠す

  public Calculator(int num1, int num2) {
    this.num1 = num1;
    this.num2 = num2;
  }

  public int calc() {
    return this.num1 + this.num2;
  }
}

ポリモーフィズム

共通メインルーチンを作る仕組み。共通サブルーチンは呼び出されるロジックを一つにまとめるが、共通メインルーチンは呼び出すロジックを一本化する。

呼び出す側としては実際に呼び出す処理に関係なく、同じように呼び出す。

public class Main {
  public static void main(String[] args) {
    Calculator calculator = new Add();//ここをnew Multipleに変更してもgetResultのロジックは変わらない
    int result = getResult(calculator, 10, 20);
    System.out.println(result);

  }

  public static int getResult(Calculator calculator, int num1, int num2) {
    return calculator.calc(num1, num2);
  }
}
class Calculator {
  public int calc(int num1, int num2) {
    return 0;
  }
}
class Add extends Calculator {
    @Override
  public int calc(int num1, int num2) {
    return num1 + num2;
  }
}
class Multiple extends Calculator {
    @Override
  public int calc(int num1, int num2) {
    return num1 * num2;
  }
}

継承

クラスの共通部分を別クラスにまとめる仕組み。共通メインルーチンならぬ共通クラス。

継承される共通クラスをスーパークラスと呼び、継承するクラスをサブクラスと呼ぶ。

以下のようにスーパークラスのメソッドをサブクラスで定義していなくても継承(extends)することで利用可能。

public class Main {
  public static void main(String[] args) {
    Computer computer = new Computer("MacBook Pro", 280000, "8core", "16GB", "512GB SSD");
    String name = computer.getName();
    System.out.println(name); // MacBook Pro
  }
}
class Product {
  private String name;
  private int price;

  public Product(String name, int price) {
    this.name = name;
    this.price = price;
  }

  public String getName() {
    return this.name;
  }

  public int getPrice() {
    return this.price;
  }
}
class Computer extends Product {
  private String cpu;
  private String memory;
  private String storage;

  public Computer(String name, int price, String cpu, String memory, String storage) {
    super(name, price);
    this.cpu = cpu;
    this.memory = memory;
    this.storage = storage;
  }
}

OOPにおけるメモリの動作

プログラム動作時にはインスタンスが生成される前にクラス情報がメモリにロードされる。

ここでいうクラス情報とは個々のインスタンスに依存しないクラス固有の情報のこと。

クラス情報のロードは事前に全てのクラス情報をロードする一括ロードの方法と、新しいクラスのコードを実行するたびに、クラス情報をロードする逐次ロードする方法の2種類がある。

インスタンスが生成されるたびにヒープ領域が使用され、そのタイミングでインスタンスからメソッドエリアにあるクラス情報との関連付けが行われる。

メモリ内部

メモリ内部 多態性を利用した場合、メモリ内部の構造が若干変わる。

インスタンスは各クラスに定義されたメソッドがメモリに展開されている場所を示したポインタ情報を格納している。これをメソッドテーブルと呼び、スーパークラスと同様の形式のメソッドテーブルを持つ。詳しくは書籍参照。

ガーベッジコレクション

ヒープ領域に残ったインスタンスを自動的に削除する仕組み。ヒープ領域の空きが少なくなったタイミングで処理を起動する。

OOPを使って作られたアプリケーションはインスタンスが他のインスタンスを参照したり、インスタンス同士が参照しあったりして全体のネットワークを構成するが、インスタンスの中にはメソッドエリア(静的領域)やスタック領域から辿ることのできないインスタンスが存在する場合があり、これがガーベッジコレクトの対象となる。