メソッド (計算機科学)

メソッド  あるいは メンバー関数  とはオブジェクト指向プログラミング言語において、あるクラスまたはオブジェクトに所属するサブルーチンを指す。

1. 概要
-----

オブジェクト指向プログラミングにおけるメソッドという用語は元々SmalltalkによってSimulaのメンバープロシージャー  をメッセージとメソッドに分けるために導入された。C++ ではメンバー関数と呼ばれるが、これはSimulaのメンバープロシージャーをC言語に流用したことに由来している。Javaのような言語やマイクロソフトなどの企業がメソッドという用語を使っているのは、date=2018年10月。date=2018年10月では言語設計やOS開発等で直接Smalltalkの影響を受けていない場合でも、Javaやマイクロソフト等の影響によりメンバープロシージャーやメンバー関数に当たるものをメソッドと呼ぶことが一般化している。

SmalltalkやSmalltalkの特色を色濃く受け継いだObjective-Cでは、メッセージとメソッドを明確に使い分けている。

メッセージとメソッドが分かれている言語では1個のメソッドに対しセレクターが異なる複数のメッセージを送ることができる。このためメンバー関数型の言語と違いメッセージとメソッドの分離を強く意識しておく必要がある。

メソッドと通常の関数の違いは主にインスタンス内部へのアクセスの有無である。クラスに所属するフィールドおよびメソッドにはそれぞれアクセシビリティを設定することができ、アクセス権がないコード領域からは参照したり呼び出したりすることができない。この機能は通常カプセル化と呼ばれており、クラス定義の抽象化に貢献する。また呼び出し時に操作の対象となるインスタンス  を this や self といったキーワード、あるいはメソッドに渡された引数によって参照することができる。C++ではクラスに属さない関数である大域関数と対比されることがある。

またメソッドはサブクラス化の際にオーバーライドされることがあり、実際に発生する動作がレシーバに依存するという特徴を持つ。これを多態性と呼ぶ。

統一モデリング言語  ではメソッドのことを操作  と呼ぶ。

2. インスタンスメソッドとクラスメソッド
---------------------

インスタンスメソッド  とはインスタンスに属するメソッドのことであり、インスタンスに対しメッセージを送信する事で実行される。インスタンス変数の操作に使われ、インスタンスメソッドはオブジェクト指向プログラミングの中核をなし、もっともよく使われる。

一方、クラスメソッド  とはクラスに属するメソッドのことであり、クラスに対しメッセージを送信する事で実行される。クラス変数の操作やオブジェクトの生成などに使われる。多くのオブジェクト指向言語はメタクラスをサポートしており、クラスオブジェクトの操作手段となる。C++やJavaといったオブジェクト指向言語では静的メンバー関数あるいは静的メソッド  とよばれ、その振る舞いはクラス変数の操作が許可される点を除き非オブジェクト指向言語における関数やプロシージャと変わらない。

2.1. インスタンスメソッドとクラスメソッドの例
-------------------------

2.1.1. Smalltalkによる例
--------------------

Smalltalkによるインスタンスメソッドとクラスメソッドの例を示す。

Object subclass: #MethodSample instanceVariableNames: 'name' "インスタンスオブジェクトに持たせるインスタンス変数" classVariableNames:// "クラスオブジェクトとクラスオブジェクト直属のインスタンスオブジェクトの間で共有するクラス変数"// poolDictionaries:// "クラスオブジェクトとインスタンスオブジェクトの間で共有するプール変数"// category: 'Example'.

"インスタンスオブジェクトのインスタンス変数を操作するメソッド" MethodSample methodsFor: 'accessing' ! givenName

^ name. ! givenName: aString

name := aString. !!

"インスタンスオブジェクトとクラスオブジェクトを操作するメソッド" MethodSample methodsFor: 'inter-accessing' ! name

^ self givenName, ' ', self class familyName. !!

MethodSample class instanceVariableNames: 'name'. "クラスオブジェクトに持たせるインスタンス変数"

"クラスオブジェクトのインスタンス変数を操作するメソッド" MethodSample class methodsFor: 'accessing' ! familyName ^ name. ! familyName: aString

name := aString. !!

MethodSample class methodsFor: 'instance creation' ! withGivenName: aString

^ self new givenName: aString; yourself. !!

<WtXmlEndTag />

インスタンスメソッドを実行するには、まずインスタンスオブジェクトを生成しなければならない: | objectA objectB |

objectA := MethodSample withGivenName: 'John'. objectB := MethodSample withGivenName: 'Joe'.<WtXmlEndTag /> 上の例では #withGivenName: により二つのインスタンスオブジェクトを生成し変数 objectA と objectB に代入している。この時点で、objectA とobjectB のインスタンス変数 name にはそれぞれ "John" と "Joe" が代入されている。

インスタンスメソッドを実行するには次のように記述する: objectA givenName. "'John' を返す" objectB givenName. "'Joe' を返す" 上の例では objectA と objectB それぞれのインスタンスオブジェクトに対し #givenName メッセージを送りインスタンスメソッドを実行している。それぞれのメソッドの返り値が異なることから、同じクラスオブジェクトに属するインスタンスオブジェクトでもインスタンス変数が持つ値は、インスタンスオブジェクト毎に異なることがわかる。

一方、クラスメソッドを実行するには、クラスオブジェクトに直接属しているため、インスタンスオブジェクトの代わりにクラスオブジェクトに対してメッセージを送る。 クラスメソッドを実行するには次のように記述する: type := MethodSample type familyName: 'Hillton'. type familyName. "'Hillton' を返す". 上の例では typeにクラスオブジェクトMethodSampleを代入して、#familyNameと#familyName:メッセージを送りクラスメソッドを実行している。

クラスオブジェクトのインスタンス変数 name は、インスタンスオブジェクトのインスタンス変数と異なり MethodSample に属する全てのインスタンスオブジェクトで共有される。 クラスオブジェクトのインスタンス変数が共有される例を示す: | type objectA objectB |

type := MethodSample

objectA := type withGivenName: 'John'. objectB := type withGivenName: 'Joe'.

"#nameはクラスオブジェクトに#familyNameを送っているため、異なるインスタンスオブジェクトでも'Hillton'が共有されている。" type familyName: 'Hillton'. objectA name. "'John Hillton'を返す" objectB name. "'Joe Hillton'を返す"<WtXmlEndTag />

クラスメソッドは、変数に代入せず直接クラス名を指定して呼び出すことが多い。特にクラスがオブジェクトではない言語においては、直接クラス名を指定する書き方しかできない。

直接クラス名を指定したクラスメソッドの呼び出しは次のように記述する: MethodSample familyName: 'Hillton'. MethodSample familyName. "'Hillton' を返す". なお、MethodSample のインスタンスオブジェクト生成するときに使用した new もクラスメソッドである。

一般的にインスタンスオブジェクトを生成する場合には new という特別な演算子を用いる言語が多い。しかし、Smalltalkの影響が強い言語やRuby等いくつかの言語ではクラスメソッドによりインスタンスオブジェクトを生成する。クラスメソッドによりインスタンスオブジェクトを生成する言語の場合、newを独自の実装に変更することができる。例えばnewを実行したとき、別のクラスオブジェクトに属するインスタンスオブジェクトを返すようにすることができる。C++のようにnewが演算子でありながら、クラスメソッド  としてnewを定義できる言語もある。

クラスがオブジェクトになっている言語の場合、インスタンスもクラスも同じオブジェクトとして扱われるため、インスタンスメソッドとクラスメソッドでメッセージの送り方に区別はない。インスタンスメソッドの代わりにクラスメソッドを呼び出すことも、クラスメソッドの代わりにインスタンスメソッドを呼び出すこともできる。どちらのメソッドを呼び出すかは、メッセージを送った変数にインスタンスとクラスのうち、どちらのオブジェクトを代入していたかで決まる。

多くの言語ではインスタンスメソッドとクラスメソッドは同じシグネチャ  を定義できる。SmalltalkやObjective-Cなどメッセージが存在する言語ではメソッドの多重定義ができないため一見無理なように見えるが、メソッドが所属するオブジェクトがインスタンスとクラスで異なるため、同一のシグネチャでインスタンスメソッドとクラスメソッドを定義することができる。

2.1.2. Javaによる例
---------------

Javaで記述したインスタンスメソッドと静的メソッドの例を示す。Java では static 修飾子がついたメソッドが静的メソッドであり、ついていなければインスタンスメソッドである。

public class MethodSample { / インスタンスフィールド */ private String name;

/ クラスフィールド */ private static int number;

/ インスタンスを生成するためのコンストラクタ */ public MethodSample { this.name = name; }

/ インスタンスメソッド、getter */ public String getName { return this.name; }

/ 静的メソッド、getter */ public static int getNumber { return MethodSample.number; }

/ 静的メソッド、setter */ public static void setNumber { MethodSample.number = number; } }<WtXmlEndTag />

Smalltalk同様インスタンスメソッドを呼び出すには、まずコンストラクタを呼び出してインスタンスを生成しなければならない: MethodSample objectA = new MethodSample; MethodSample objectB = new MethodSample; 上記の例は、Smalltalkの例におけるインスタンスオブジェクトの生成と同様に動作する。C++の表記を踏襲したJavaでは、new演算子によってインスタンスを生成する。

インスタンスメソッドを呼び出すには次のように記述する: objectA.getName; // "John" を返す objectB.getName; // "Joe" を返す 上記の例は、Smalltalkの例におけるインスタンスメソッドの呼び出しと同様に動作する。

クラスメソッドを呼び出すには次のように記述する: MethodSample.setNumber; MethodSample.getNumber; // 100 を返す 上記の例は、Rubyの例における直接クラス名を指定したクラスメソッドの呼び出しと同様に動作する。

Javaではメタクラスとしてname=java.lang.Classクラスをサポートする。name=java.lang.Object.getClassメソッドによりClass型オブジェクトを取得できる。また、クラス名.classという構文でClass型オブジェクトを取得することもできる。さらにリフレクションを使うことで、Class型オブジェクトからメソッドを呼び出したり、フィールドにアクセスしたりすることもできる。

.NET FrameworkではメタクラスとしてSystem.Typeクラスをサポートする。System.Object.GetTypeメソッドによりType型オブジェクトを取得できる。C#ではtypeof演算子により型シンボルからType型オブジェクトを取得することもできる。また、リフレクションもサポートしている。

C++はクラス型オブジェクトやリフレクションをサポートせず、クラス自体を何らかの変数に代入するようなことはできない。

3. メッセージ送信とメソッド呼び出し
-------------------

オブジェクト指向を解説した書籍などでメソッド呼び出しについてオブジェクトにメッセージを送信すると表現されることがある。C++系統の言語ではオブジェクトの操作は単なるメンバー関数呼び出しに過ぎず比喩として捉えられる場合が多い。SmalltalkやObjective-Cにおいては、メッセージ送信は単なる比喩ではなく実体のある機構であり、メソッド呼び出しとは別物であるため注意が必要である。

4. 仮想メソッド、抽象メソッドと具象メソッド
-----------------------

4.1. 仮想メソッド
-----------

仮想メソッド  とは、サブクラスでオーバーライド  し、動作を変更することのできるメソッドのことである。C++では仮想関数と呼ばれる。

C++およびC#のメソッドはデフォルトで非仮想であり、メソッドにvirtual修飾子をつけることで仮想メソッドとすることができる。なお、C#のクラスは仮想メソッドおよび非仮想メソッドの両方を定義することができるが、構造体は仮想メソッドを定義することができない。一方Javaのメソッドは常に仮想であり、final修飾子をつけることでオーバーライドを禁止できるが、非仮想メソッドとなるわけではない。finalメソッドを非finalメソッドに変更しても、バイナリ互換性は維持される。

4.1.1. 仮想と非仮想
-------------

C++では、一般に仮想関数はコンパイル時にどのメンバー関数を呼び出すかを確定できないため、通常の非仮想なメンバー関数呼び出しよりもパフォーマンスが悪いというデメリットがある。そのため、パフォーマンスを気にするC++プログラマには、継承する必要がないクラスのメンバー関数にvirtual修飾子をつけることを非常に嫌う傾向がある。また、C++にはtemplateという機能が存在し、date=2018年12月。ただし、デストラクタが非仮想の場合、派生クラスのインスタンスのポリモーフィックなdeleteが不可能となる、という利便性および安全上のデメリットも発生する。

メソッドがデフォルトで非仮想というC++に準ずる設計選択をしたC#においても、仮想メソッドの呼び出しには非仮想メソッドよりもコストがかかることを念頭に置いて利用する必要がある。

Javaのインスタンスメソッドは常に仮想であるが、クラスメソッドはオーバーライドすることのできない非仮想であるため、静的メソッドのほうが呼び出しコストが小さく、パフォーマンス上のメリットがある。

Javaのfinal修飾子は、パフォーマンス上の理由というよりはむしろ、派生クラスでの不用意なオーバーライドを禁止してバグを未然に防止することにある。「Javaではメソッドをfinal修飾することでコンパイラの最適化によりパフォーマンスが向上する」という神話があるが、一方で、Java仮想マシンの性能によってはメソッドをfinalと宣言したからといって優れたパフォーマンスが得られるとは限らないという指摘もある。なお、OracleのHotSpot VMは、finalメソッドを検出して非常に効率よく実行できるように最適化されていると説明されている。

4.2. 抽象メソッドと具象メソッド
------------------

抽象メソッド  とは仮想メソッドの一種で、メソッドの実装が無く、宣言だけされているもののことである。C++では純粋仮想関数  と呼ばれる。このメソッドを利用するには、このメソッドを含むクラスを継承し、そこでこのメソッドをオーバーライドして実装する必要がある。従って、抽象メソッドを含むクラスは継承しない限りインスタンス化できない。このようなクラスを抽象クラスと呼ぶ。

具象メソッド  は抽象メソッドの逆で、実装をもつメソッドのことである。主に抽象メソッドをオーバーライドしたインスタンスメソッドのことを意味するために使われる。

JavaおよびC#ではabstract修飾子を用いて抽象メソッドを宣言できる。抽象メソッドを持つクラス自体もまた、必ずabstract修飾子を使って抽象クラスとして定義しなければならない。

抽象メソッドはデザインパターンの一つTemplate Method パターンで主要な役割を果たす概念であり、ソフトウェアの拡張性、再利用性、汎用性を高めるのに役立つ。

また、抽象メソッドのみを持つ抽象型として、JavaおよびC#ではインターフェイス  を定義できる。JavaおよびC#においてクラスは多重継承できないが、インターフェイスを複数実装することはできる。

4.3. オーバーライド
------------

5. アクセサ
-------

アクセサ  とは、特にオブジェクトが持つフィールドに間接的にアクセスするために定義されるメソッドの総称である。フィールドに値の設定  をするメソッドをsetter、フィールドから値の取得  をするメソッドをgetterと呼ぶ。通例、個々のフィールドに対して、個別のアクセサが用意される。つまり、setterは単一の入力を持ち、getterは単一の出力を持つ。1つのフィールドに対してsetter/getterが対称的に両方用意されることもあれば、片方だけが用意されることもある。日本語ではアクセサー、アクセッサあるいはアクセッサーとも表記する。C# 9では、オブジェクト構築時にのみ呼び出せるinitアクセサを定義することもできる。

5.1. アクセサの表記
------------

記法は言語により異なる。

Java系統では、getXXX, setXXXの様に語幹部にアクセス対象の名詞が入る。Smalltalk系統ではXXX, XXX:というようにアクセス対象の名前だけを記述し、入力と出力を引数の有無で区別する。Objective-Cでは、XXX, setXXX:と片方にだけsetをつける命名規則が用いられる。DelphiやC#などのプロパティ機能を持つ多くの言語では、プロパティ自体がアクセサにあたり、専用構文を使用する。Rubyでは、メソッド呼び出しの際の引数を囲むカッコが省略できるため、引数を持たないメソッド呼び出しが読み出し用アクセサに相当するが、書き込み用アクセサにはXXX=という構文が存在する。C++には決まった規則はなくライブラリや開発環境に左右される。

5.2. アクセサとRADツール
----------------

アクセサが他のメソッドと区別される大きな理由としてRADツールの連携を想定していることにある。多くのRADツールでは、アクセサとして定義されたメソッドを特別扱いし、グラフィカルユーザインタフェース  のデザイン時にデザイン画面からアクセサの引数を指定できるようになっている。

Javaには元々、get/setの接頭辞を記述するという規則はなかったが、RAD支援ライブラリのJavaBeans  がset/getから始まるメソッドを値設定用のメソッドとして特別に扱うため、Beansが登場して以降、Javaのクラスライブラリ全体に渡ってset/getの接頭辞を使用する命名規則が導入された。現在でも初期の名残としてset/getを接頭辞として持たないアクセサメソッドが存在する。

5.3. アクセサと公開フィールド
-----------------

多くの言語では、フィールドを公開  状態にすることで、オブジェクトのフィールドをメソッドを介さず直接読み書きできる。 しかし、不特定多数のアプリケーションから利用されるライブラリなどではカプセル化の観点から、実際にはアクセサを介してフィールドを読み書きすることがほとんどであり、フィールドを公開状態にして直接読み書きすることは避けられる。アクセサを用意することで、ライブラリの仕様に則した使い方を強制することができるというメリットがある。

フィールドとアクセサが1対1の状態であれば、公開状態のフィールドの読み書きとアクセサを介したフィールドの読み書きはほとんど変わらない。それにも関わらず、わざわざアクセサを介してフィールドを読み書きする理由は、アクセサの実装はクラス実装者の自由であり、アクセサ  が受け取った値をどう処理するかはクラスの実装に委ねられるからである。例えば、アクセサ  が値を返す際、フィールドを参照せず常に0を返すという実装も有りうる。

公開フィールドへの直接アクセスと比較した際のアクセサの利点としては以下のようなものがある。

意図的に setter メソッドを実装しないことで、フィールドの値を外部から勝手に変更させないようにすることができる。 例えばコンストラクタで指定した初期値を常に保持し続けるイミュータブル  なクラスを定義することも可能となる。
getter/setter でフィールドにアクセスするときにデータを加工してから取得したり設定したりすることができる。 例えば内部データの単位とは異なる単位の値に換算して返す、などである。
setter でフィールドにデータを設定するときにバリデーション  を実行できる。 例えば設定したいデータを整数のみ、自然数のみ、偶数のみ、あるいは日付形式文字列のみ、などに限定して、範囲外あるいはフォーマット外の値が渡された場合に異常系とみなして例外をスローするといった実装ができる。また、アクセサを使う最も重要な理由として、多態性の恩恵を受けられるという点がある。

例えばSmalltalkの#value, #contents, #name, #sizeといったセレクターが良い例である。#valueは値を示すセレクターで下記のようにアクセサであったりアクセサでなかったり様々な実装が存在するが、#valueメッセージを送る手順はその違いを区別する必要が無いため下記のようにどの実装に対しても共通の手順を適用することができる。これがアクセサによる多態性の恩恵である。アクセサを用意せず直接変数を公開しているクラスや、直接オブジェクト内の変数を参照しているような手順はこれらの枠組みから外れてしまうため共通の手順を使うことはできず変数の参照方法だけが異なる似たような手順をいくつも作る事となる。  "アクセサ目的以外の#value" [ 1 ] value. "-> 1"  value. "-> 1"

"純粋なアクセサとしての#value"  asValueHolder value. "-> 1"  value. "-> 1"

<WtXmlEndTag /> "共通の手順" block := [ :valueHolder | self traceCr: valueHolder value printString. ].

"手順の適用" block value:  asValueHolder. block value: . block value: [ 1 ]. block value: .<WtXmlEndTag />#value以外の実例 "アクセサ以外:1と10を元に計算している。"  size. "アクセサ"

 size."アクセサ以外:'text.txt'を全部読み込んだ結果を返す" 'text.txt' asFileRefarence readStream contents. "アクセサ以外:self valueの結果を返す" 1 asValueHolder contents. "アクセサ"

 contents."アクセサ以外: Class名を生成して返す。" Class name. "アクセサ" CmCommand new name.<WtXmlEndTag />

純粋なオブジェクト指向環境として知られるSmalltalkでは、アクセサによる多態性がMVCの依存性辞書date=2018年10月}}の管理を始めあらゆる箇所で活用されている。

5.4. アクセサ論争
-----------

Smalltalkのような言語ではインスタンスおよびクラス内の変数を外部から操作することはできず必ずメッセージを使った操作が必要となる。一方 C++ などでは public 変数への直接操作が往々にして利用されることがある。

効率の問題。メソッドをいちいち呼び出すコストを避けたい場合。ただしほとんどはコンパイラ最適化によるインライン化で解決できる。
記述量の問題。単純に値を設定／取得するだけのことに全てメソッドを記述するのは間違っているという考え方。問題となるのは後者である。

まずカプセル化や多態性の観点から、アクセサを用いない変数参照は将来にわたっての変更耐久性や拡張性が著しく劣る。また記法の一貫性からアクセサを指示する向きもある。一方否定派は「強力な IDE を用いればリファクタリングは可能であり、むしろフィールドへの直接アクセスを用いる方が意味が明確となる」という主張を展開し、時にフレームに発展する場合がある。これは現代の「goto文論争」ともいうべき、半ば宗教的な対立関係である。

そのほか、C++ではC言語との相互運用時に構造体を用いることがあるが、構造体のメンバー変数は隠蔽しないことが多い。C++におけるclassキーワードとstructキーワードは、デフォルトのアクセシビリティが異なるという違いしかないが、メンバー変数を隠蔽しないC言語互換の構造体として利用するPOD  型を定義する際に、classキーワードではなくstructキーワードが使われることもある。

Object Pascal はこの反省から、変数を後からアクセサ化できる仕組みとしてプロパティを導入した。また Ruby など、アクセサを簡単に定義できるメソッドや構文を備えている言語もある。

6. 多重定義
-------

引数の数、型、順序などが異なる同じ名前のメソッドを定義することをメソッドの多重定義といい、多くのオブジェクト指向言語ではメソッドやコンストラクタを多重定義できる機能を持つ。オーバーロード  とも呼ばれるが、オーバーライドとの混同に注意が必要である。

ただし、PHPやPerlのように、プログラミング言語によっては、型の曖昧さが原因によりメソッドを多重定義できないものもある。この場合はメソッドの引数をメソッド先頭で読み取り、引数の型を判定する条件分岐で対応する。

7. 参考文献・脚注
----------

8. 関連項目
-------

ソフトウェア工学
オブジェクト指向プログラミング
カプセル化
継承 
ポリモーフィズム
クラス 
コンストラクタ
多重定義 
オーバーライド
インスタンス
インスタンス変数
クラス変数