Adaの日本語情報を増やす(8)・OOPその3

続き。ほんとは「継承」って用語は正しくないんだよね。

今回はClass Wide型について解説する。その前に、メソッドの継承について若干確認を行う。まずはコードを。

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure OOP3 is
   package Foo_Package is
     type Foo is record -- 中身は適当
        Val_X : Integer;
     end record;
     procedure In_Procedure(X : Foo);
   end Foo_Package;

   package body Foo_Package is
      procedure In_Procedure(X : Foo) is
      begin
         Put("In_Procedure called."); New_Line;
      end In_Procedure;
   end Foo_Package;
   use Foo_Package;

   -- パッケージ外にあるメソッド
   procedure Out_Procedure(X : Foo) is
   begin
         Put("Out_Procedure called."); New_Line;
   end Out_Procedure;

   -- 継承
   type Bar is new Foo;

   X : Bar;
begin
   In_Procedure(X);
-- Out_Procedure(X); -- パッケージの外なので継承されてない
end OOP3;

Adaにおける「クラス」はパッケージに相当する、という解説がよく行われる。実際、前回までの記事でも「Classes.Class」という命名規則を使用し、インスタンス変数のホルダーであるrecordと、メソッドとなる副プログラムをパッケージで区切って定義してきた。命名規則だけを見るとわざわざパッケージを定義するのは冗長に見えるが、これは派生型の性質上避けることが出来ない。以前解説したとおり、派生型が継承する副プログラムは、その派生型が定義されているパッケージ内で同時に定義された物に限られる。このため、副プログラム(メソッド)の継承を行いたければ、型(クラス)ごとにパッケージを分割する必要があるのだ。逆にパッケージを分割しない場合、副プログラムの継承は行われた無いため、コメントアウトされた行の様な呼び出しを行うとコンパイルエラーとなる。これらの理由から、Adaにおけるクラスの定義は基本的にはパッケージを分割することで行われる。

ところで、このようにして継承を行った場合、メソッドのオーバーライドには特に制限がない。しかし、状況によっては安全のため、サブクラスによるオーバーライドを防止したい場合もあるだろう。そのような際に便利なのがClass Wide型だ。

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure OOP4 is
   package Super_Classes is
     type Super_Class is tagged record -- 面倒なのでprivateにしない
        Val_X : Integer; -- 中身は適当
     end record;
     procedure Derived_Procedure(O : Super_Class);
     procedure CW_Procedure(O : Super_Class'Class); -- これがClass Wide型
   end Super_Classes;

   package body Super_Classes is
      procedure Derived_Procedure(O : Super_Class) is
      begin
         Put("Derived_Procedure called."); New_Line;
      end Derived_Procedure;
      procedure CW_Procedure(O : Super_Class'Class) is
      begin
         Put("CW_Procedure called."); New_Line;
      end CW_Procedure;
   end Super_Classes;

   package Sub_Classes is
      type Sub_Class is new Super_Classes.Super_Class with null record;
      procedure Derived_Procedure(O : Sub_Class); -- オーバーライド
   end Sub_Classes;

   package body Sub_Classes is
      procedure Derived_Procedure(O : Sub_Class) is
      begin
         Put("Overrided Derived_Procedure called."); New_Line;
      end Derived_Procedure;
   end Sub_Classes;

   Super : Super_Classes.Super_Class;
   Sub : Sub_Classes.Sub_Class;
begin
   Super_Classes.Derived_Procedure(Super);
   Super_Classes.CW_Procedure(Super);
   Sub_Classes.Derived_Procedure(Sub);
   Super_Classes.CW_Procedure(Sub);
-- Sub_Classes.CW_Procedure(Sub); -- Class Wide型を持つ副プログラムは継承されないのでエラー
end OOP4;

-- Derived_Procedure called.
-- CW_Procedure called.
-- Overrided Derived_Procedure called.
-- CW_Procedure called.

Class Wide型は自身と自身を継承する全ての型を許容する型だと言っていい。ただし、Class Wide型自体は派生される型そのものではないため、仮にパラメタに該当する型のClass Wide型が存在したとしても、その副プログラムは継承されない。上のコードの場合、Sub_Classes.CW_Procedureは存在しない。

その代わり、Class Wide型は1つの副プログラムで自身から派生した全ての型を受け入れることが出来るため、Super_Classes.CW_ProcedureのパラメータにSub_Class型の変数を与えてもエラーとは成らない。

Class Wide型を引数に取る副プログラムにおいても、実際のところオーバーライドは可能である。しかし、通常は継承されないClass Wide型を使用することにより、凡ミスによるメソッドの変更は防止できる、と、「Rational for Ada 2005」に書いてあるが、怪しい個人的には怪しいと考える。

むしろClass Wide型が活躍するのは後で説明するaccess型との組み合わせであろうと思われる。

なお、Class Wide型には派生型による継承のようなパッケージの制約がないので、このようなコードも書ける。

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;

procedure OOP5 is
   -- パッケージで分割しない
   type Super_Record is tagged record
      Val_X : Integer;
   end record;

   procedure Super_Method(O : Super_Record) is
   begin
      Put("Super_Method called."); New_Line;
   end Super_Method;

   type Sub_Record is new Super_Record with null record;

   procedure Class_Wide_Super_Method(O : Super_Record'Class) is
   begin
      Put("Class_Wide_Super_Method called."); New_Line;
   end Class_Wide_Super_Method;

   Super : Super_Record;
   Sub : Sub_Record;

begin
   Super_Method(Super);
   Class_Wide_Super_Method(Super);
-- Super_Method(Sub); -- パッケージがないので継承されていない
   Class_Wide_Super_Method(Sub); -- Class Wide型なのでパッケージに関係なく受け入れられる
end OOP5

-- Super_Method called.
-- Class_Wide_Super_Method called.
-- Class_Wide_Super_Method called.