オブジェクト指向: ポリモーフィズム

概要

多態性・多相性
オーバーライド(=基底クラスと同名の、しかし異なる機能のメソッドを定義する事)を用いてコーディング量を減らすテクニック。
インスタンスからはどのクラスか?を意識せず動的に多様な動作を実現できる。

メソッドテーブル

ポリモーフィズムの代表的テクニックの1つ。

//基底クラス
public class TextReader
{
  //共通メソッドを定義
  public virtual void open() { }
  public virtual void close() { }

  public virtual string read() { return ""; }
}

//派生クラス1
public class FileReader : TextReader
{
  protected string pathName;
  public FileReader(String pathName)
  {
    this.pathName = pathName;
  }

  //基底クラスの共通メソッドを継承
  public override void open() { }
  public override void close() { }

  public override string read() { return ""; }
}

//派生クラス2
public class NetworkReader : TextReader
{
  protected string id;
  protected string pass;
  public NetworkReader(String id, String pass)
  {
    this.id = id;
    this.pass = pass;
  }

  //基底クラスの共通メソッドを継承
  public override void open() { }
  public override void close() { }

  public override string read() { return ""; }
}

public partial class Form1 : Form
{
  private void Form1_Load(object sender, EventArgs e)
  {
    //ローカル上のファイルを読み込むクラス
    FileReader file = new FileReader(pathName: @"D:\Test");
    //ネットワーク上のファイルを読み込むクラス
    NetworkReader net = new NetworkReader(id: "yone", pass: "aaa");
    
    異なるクラスであっても同じ基底クラスを継承していれば引数として渡せる
    int charCountAtLocal = getCount(reader:file);
    int charCountAtNet = getCount(reader:net);

    異なる機能を持つメソッドを共通の記述で処理できている。
    ⇒ポリモーフィズム
  }
  
  private int getCount(TextReader reader)
  {
    ※引数:readerは基底クラスTextReader型
    TextReaderクラスだけでなく、FileReaderクラス、NetworkReaderクラスのインスタンスでも受け取り可能
    
    int charCount = 0;
    while (true)
    {
      //異なるクラス・異なる挙動であっても、
       メソッドがoverrideされているので共通の記述でメソッドを呼び出せる

      string myStr = reader.read();
      ※read()はFileReaderクラスのread()、NetworkReaderクラスのread()、どちらもOK
      
      //終了条件等、省略
      charCount++;
    }
    return charCount;
  }
}

<?php
namespace src\reader;

class textReader{
  public function open(){}
  public function close(){}
  public function read(){}
}

<?php
namespace src\reader;
require_once ('src/reader/textReader.php');

class fileReader extends textReader
{
  protected $path;

  public function __construct($prm_path){
    $this->path = $prm_path;
  }
  public function read(){
    ローカルファイルの読み込み処理
  }
}

<?php
namespace src\reader;
require_once ('src/reader/networkReader.php');

class networkReader extends textReader
{
  protected $id;
  protected $pass;

  public function __construct($prm_id, $prm_pass){
    $this->id = $prm_id;
    $this->pass = $prm_pass;
  }

  public function read(){
    ネットワークファイルの読み込み処理
  }
}

<?php
namespace src\reader;
require_once 'src/reader/textReader.php';

class util extends textReader{
  $readerにはfileReader/networkReaderのインスタンスが入る
  どちらがきても使える
  public function getCount($reader){
    $fileCnt = 0;
    while (true){
      fileReader/networkReaderのreadメソッドを呼び出し
      if ($reader->read()){
        $fileCnt++;
      }
    }
    return $fileCnt;
  }
}

<?php
require_once 'src\reader\util.php';
require_once 'src\reader\fileReader.php';
require_once 'src\reader\networkReader.php';
use src\reader\fileReader;
use src\reader\networkReader;
use src\reader\util;

$f = new fileReader("/test");
$n = new networkReader("testid", "testpass");

$u = new util();
fileReaderのreadメソッドを呼び出し
$fileCntLocal = $u->getCount($f);
networkReaderのreadメソッドを呼び出し
$fileCntNet = $u->getCount($n);

暗黙の変換/キャスト

object_cast1
クラスにおいては、
基底クラスの型 ← 派生クラスのインスタンス :OK
派生クラスの型 ← 基底クラスのインスタンス :NG
cast2
機能は、基底クラス < 派生クラス だが、
メモリ使用容量は、派生クラス < 基底クラス
プログラム言語 変数/暗黙の型変換」参照
プログラム言語 変数/キャスト」参照

なおインターフェイスとその実装クラスにおけるキャストも上記と同じ
object_cast2

アップキャスト

基底クラスの変数に派生クラスのインスタンスを格納する事。
(暗黙の型変換)
class Super {}
class Sub : Super {}
Super super = new Sub();

class Base {
  public static final String FOO = "Base";
}
class Sub extends Base {
  public static final String FOO = "Sub";
}
public static void main(String[] args) {
  Base b = new Base();
  Sub s = new Sub();
  
  System.out.print(Base.FOO); ⇒Base
  System.out.print(Sub.FOO); ⇒Sub
  ※FOOはstaticなのでインスタンス化無しで参照可能
  
  System.out.print(b.FOO); ⇒Base
  System.out.print(s.FOO); ⇒Sub
  
  System.out.print(((Base)s).FOO); ⇒Base
  System.out.print(((Sub)b).FOO); ⇒コンパイルエラー
  
}

ダウンキャスト

派生クラスの変数に基底クラスのインスタンスを格納する事
(キャスト)
class Super {}
class Sub : Super {}
Sub sub = (Sub) new Super();

ポリモーフィズムの実装確認

public interface I {
  void Method();
}

public class A implements I
{
  public void Method() {
    System.out.println("Method");
  }
  public void MethodA(){
    System.out.println("MethodA");
  }
}

public class B extends A
{
  public void MethodB(){
    System.out.println("MethodB");
  }
}

public static void main(String[] args) {
  I i = new B();
  A a = new B();
  B b = new B();
  
  i.Method();
  i.MethodA(); // ×
  i.MethodB(); // ×
  
  a.Method();
  a.MethodA();
  a.MethodB(); // ×
  
  b.Method();
  b.MethodA();
  b.MethodB();
  
  静的な型(クラス)で定義されているメソッドしか呼び出せない
}

動的クラス操作

型判定

public class A
{
  public virtual void method()
  {
    Console.WriteLine(“A”);
  }
}

public class B : A
{
  public override void method()
  {
    Console.WriteLine(“B\n”);
  }
}

static void Main(string[] args)
{

  静的な型/動的な型
  A a1 = new A();   //静的:A / 動的:A
  A a2 = new B();   //静的:A / 動的:B
  B b = new B();   //静的:B / 動的:B
  
  
  変数と動的な型が同じ時にtrue
  if (a1 is B) { /* 通らない */ }
  if (a2 is B) { /* 通らない */ }
  if (b1 is A) { /* 通らない */ }
  if (a1 is A) { /* 通る */ }
  if (a2 is A) { /* 通る */ }
  if (b1 is B) { /* 通る */ }
}

RTTI
RunTimeTypeIdentification
実行時型識別
ポリモーフィズムを実装しているクラスに対して動的に行う型取得
型取得の為の情報が埋め込まれるには仮想関数を含む必要がある。

#include <typeinfo>

class MyParent{
public:
 仮想関数を定義
 virtual void myFunc() = 0;
};

class MyChild1 : public MyParent
{
public:
 void myFunc();
};
仮想関数の実装(=ポリモーフィズムの実装)
void MyChild1::myFunc(){};

class MyChild2 : public MyParent
{
public:
 void myFunc();
};
仮想関数の実装(=ポリモーフィズムの実装)
void MyChild2::myFunc(){};

void myFunc(MyParent *p)
{
 if (typeid(*p) == typeid(MyChild1))
 {
  cout << "MyChild1" << '\n';
 }
 else if (typeid(*p) == typeid(MyChild2))
 {
  cout << "MyChild2" << '\n';
 }
 
 return;
}

int _tmain(int argc, _TCHAR* argv[])
{

 MyParent *p1 = new MyChild1();
 MyParent *p2 = new MyChild2();
 
 myFunc(p1);
 →MyChild1
 myFunc(p2);
 →MyChild2
 
 return 0;
}

String myStr = "abc";

Boolean boolS = (myStr instanceof String);
⇒ boolS : true
Boolean boolO = (myStr instanceof Object);
⇒ boolO : true
※String型はObject型を継承しているので
Boolean boolI = (myStr instanceof Integer);
⇒ コンパイルエラー

class Parent{}
class Child extends Parent{}
class Friend{}

public static void main(String[] args) {
  Parent p = new Parent();
  Child c = new Child();
  Friend f = new Friend();

  Boolean boolP = (c instanceof Parent);
  ⇒ boolP : true
  Boolean boolC = (c instanceof Child);
  ⇒ boolC : true
  Boolean boolF = (c instanceof Friend);
  ⇒ boolF : false
}

$a = new A();
$bool = $a instanceof A;
→ true
$bool = $a instanceof B;
→ false

$b = new B();
$bool = $b instanceof A;
→ true
$bool = $b instanceof B;
→ false

型情報、クラス情報

ポリモーフィズム型インスタンスの実行時の挙動
//動的な型の仮想メソッドが呼ばれる
a1.method();  // ⇒ A
a2.method();  // ⇒ B
b1.method();  // ⇒ B

静的なクラス情報取得
Type 変数 = typeof(クラス名);
⇒ 変数 : クラス情報

System.Type typeA = typeof(A);
System.Type typeB = typeof(B);

動的なクラス情報取得
Type 変数= インスタンス.GetType();
⇒ 変数 : クラス情報

System.Type DinamicTypeA1 = a1.GetType();
System.Type DinamicTypeA2 = a2.GetType();
System.Type DinamicTypeB = b.GetType();

プロパティ情報を取得
PropertyInfo myProperty = a1.GetType().GetProperty(name: "prop");
PropertyInfo[] myProperties = a1.GetType().GetProperties();

メソッド情報を取得(C#)
MethodInfo myMethod = a1.GetType().GetMethod(name: "method");
MethodInfo[] myMethods = a1.GetType().GetMethods();

Objectクラスメソッド

package myPackage;

public class Parent {
 public void method(){}
}

public class Main {

 public static void main(String[] args) {
  Parent p = new Parent();
  System.out.println(p.getClass().toString());
  // → class myPackage.Parent

  System.out.println(p.hashCode());
  // → 2110121908

  System.out.println(p.toString());
  // → myPackage.Parent@7dc5e7b4

  System.out.println(p.equals(p));
  // → true

 }

}

動的なクラス情報取得
パッケージ名を含むクラス名
String s = Thread.currentThread().getStackTrace()[1].getClassName();
String s = Thread.currentThread().getStackTrace()[1].getClass().getName();

クラス名
String s = Thread.currentThread().getStackTrace()[1].getClass().getSimpleName();

プロパティ情報を取得
メソッド名
String s = Thread.currentThread().getStackTrace()[1].getMethodName();

拡張子(.java)付きファイル名
String s = Thread.currentThread().getStackTrace()[1].getFileName();

行数
String s = Thread.currentThread().getStackTrace()[1].getLineNumber();

$a = new A();
print get_class($a);
→ A

$b = new B();
print get_class($b);
→ B

動的インスタンス化

文字列→インスタンス
型 変数名 = (型) Class.forName("名前空間\クラス名").newInstance();

インスタンス→インスタンス
class 型 {
 public 型 メソッド名(){
  return (型) clone();
 }
}

$ref = new ReflectionClass('TestClass');
$instance = $ref->newInstance(コンストラクタへの引数);
$instance->method();
$instance = null;