Skip to main content
技術

Factory Methodパターンを学んで理解した「抽象メソッドはどこで実行されるのか」

PR この記事にはアフィリエイト広告が含まれます。 Amazonのアソシエイトとして、DevLife Hacksは適格販売により収入を得ています。

Factory Methodパターンを学んでいて、「抽象メソッドはどこで実行されるのか?」と疑問に感じました。この記事では、FactoryとConcreteFactoryの関係、抽象メソッドの役割、実行時にどのメソッドが呼ばれるのかを整理します。

Factory Methodパターンを学んで理解した「抽象メソッドはどこで実行されるのか」

Javaのデザインパターンを学ぶ中で、Factory Methodパターンについて勉強しました。

「インスタンス生成をサブクラスに任せるパターン」という説明自体は理解できたものの、サンプルコードを追っていると一つ疑問が出てきました。

mainからはスーパークラスのメソッドを呼んでいるように見えるのに、なぜサブクラス側のインスタンス生成処理が実行されるのか?

今回は『Java言語で学ぶデザインパターン入門』でFactory Methodを学ぶ中で、この疑問を整理して理解できたことをまとめます。

対象読者

この記事は次のような方を想定しています。

  • Javaのクラスや継承について基本的な知識がある
  • デザインパターンを学び始めた
  • Factory Methodの構造はなんとなく分かる
  • 抽象クラスと抽象メソッドの関係がまだ曖昧
  • Factory factory = new IDCardFactory();の意味がいまひとつ分からない

Factory Methodを網羅的に解説するというより、私自身がつまずいた「抽象クラスと実際に動くメソッド」に焦点を当てます。

Factory Methodとは

Factory Methodはインスタンスを生成する具体的な処理をサブクラスに任せるデザインパターンです。

今回学習した例では、大きく次の4つのクラスが登場します。

クラス

役割

Factory

Productを生成する大枠を定義する

Product

生成されるオブジェクトの共通部分を定義する

IDCardFactory

Productの具体的な生成方法を実装する

IDCard

実際に生成されるProduct

関係としては次のようになります。

Factory
  ↑
  │ 継承
  │
IDCardFactory
  │
  │ 生成
  ▼
IDCard
  │
  │ 継承
  ▼
Product

FactoryとProductが抽象側、IDCardFactoryとIDCardが具体側です。

最初は、

FactoryがIDCardを作るパターン

くらいの理解でした。

しかし、正確には少し違います。

Factoryは「どのような手順でProductを作るか」を決め、具体的に「何をnewするか」はIDCardFactoryに任せています。

ここがFactory Methodの重要な部分でした。

Factoryでは「実装」ではなく「宣言」している

最初に私が勘違いしていたのが、抽象メソッドについてです。

Factoryを次のように考えます。

public abstract class Factory {

    public final Product create(String owner) {
        Product product = createProduct(owner);
        registerProduct(product);
        return product;
    }

    protected abstract Product createProduct(String owner);

    protected abstract void registerProduct(Product product);
}

ここには、

protected abstract Product createProduct(String owner);

というメソッドがあります。

私は当初、

スーパークラスで抽象メソッドを実装している

と考えていました。

しかし、これは正確には実装ではなく宣言です。

Javaの抽象メソッドは、メソッドのシグネチャを定義する一方で具体的な処理を持ちません。OracleのJava Language Specificationでも、abstractメソッドはメソッドをメンバーとして導入するものの、実装は提供しないと定義されています。

つまり、

protected abstract Product createProduct(String owner);

は、

Productを作るためにcreateProductというメソッドを用意してください。具体的な作り方はサブクラスで決めてください。

というルールを定義していると考えると分かりやすくなりました。

ConcreteFactoryで具体的な生成方法を実装する

Factoryを継承したIDCardFactoryでは、抽象メソッドを実装します。

public class IDCardFactory extends Factory {

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void registerProduct(Product product) {
        // 登録処理
    }
}

ここではじめて、

new IDCard(owner)

が登場します。

つまり役割を分けると、

Factory
「Productを作る処理が必要」

        ↓

IDCardFactory
「今回はIDCardをnewしてProductを作る」

という関係になっています。

抽象クラスであるFactoryは「何かProductを作る」というルールだけを持っています。

一方で、

具体的にIDCardを作る

という知識を持っているのはIDCardFactoryです。

疑問:mainではFactoryのメソッドを呼んでいるように見える

ここまで理解したところで、次のコードに疑問を持ちました。

public class Main {

    public static void main(String[] args) {

        Factory factory = new IDCardFactory();

        Product card = factory.create("Taro");

        card.use();
    }
}

特に気になったのがこの部分です。

Factory factory = new IDCardFactory();

そして、

factory.create("Taro");

としています。

変数の型はFactoryです。

そのため最初は、

Factoryのメソッドを実行しているなら、どうやってIDCardFactory側の処理まで到達するのだろう?

と疑問に思いました。

ここを理解するには、変数の型と実際のインスタンスを分けて考える必要があります。

Factory型だが実体はIDCardFactory

次のコードを分解してみます。

Factory factory = new IDCardFactory();

左側は、

Factory factory

なので、変数factoryの型はFactoryです。

しかし、右側では、

new IDCardFactory()

としています。

つまり、

変数としての型
Factory

実際に生成されたオブジェクト
IDCardFactory

です。

factoryはFactory型として扱われていますが、実際に入っているオブジェクトはIDCardFactoryなのです。

create()自体はFactoryの処理が実行される

次に、

factory.create("Taro");

を実行します。

create()はFactoryに具体的な処理が書かれています。

public final Product create(String owner) {

    Product product = createProduct(owner);

    registerProduct(product);

    return product;
}

そのため、まずFactoryに定義されたcreate()が実行されます。

ここまでは自分の最初の理解とも一致していました。

問題はその次です。

create()の内部では、

createProduct(owner);

を呼び出しています。

しかし、Factory側のcreateProduct()は、

protected abstract Product createProduct(String owner);

と宣言されているだけです。

処理の中身がありません。

では、何が実行されるのでしょうか。

実際に動くのはIDCardFactoryのcreateProduct()

ここで重要になるのが、Javaのオーバーライドとポリモーフィズム(多態性)です。

factoryの実際のオブジェクトは、

new IDCardFactory()

です。

そのためcreateProduct()が呼ばれると、IDCardFactoryによってオーバーライドされた、

@Override
protected Product createProduct(String owner) {
    return new IDCard(owner);
}

が実行されます。

OracleのJava Tutorialsでも、サブクラスはスーパークラスから継承したインスタンスメソッドをオーバーライドできることが説明されています。

処理の流れを並べると次のようになります。

main
  │
  │ factory.create("Taro")
  ▼
Factory#create()
  │
  │ createProduct("Taro")
  ▼
IDCardFactory#createProduct()
  │
  │ new IDCard("Taro")
  ▼
IDCardのインスタンス

ここでようやく、

Factoryからサブクラスを間接的に呼び出している

という自分の理解がかなり近かったことが分かりました。

ただし、より正確には、

Factoryが明示的に「IDCardFactoryを呼び出そう」と判断しているわけではありません。

実際のインスタンスがIDCardFactoryなので、オーバーライドされたIDCardFactoryのメソッドが実行されています。

「抽象メソッドを実行している」わけではない

今回、一番整理できたのがこの点です。

私は当初、

mainからスーパークラスの抽象メソッドを実行しているのでは?

と考えていました。

しかし、抽象メソッドそのものには処理が存在しません。

OracleのJava Tutorialsでも、abstractクラスはインスタンス化できず、abstractメソッドは実装を持たないメソッドとして説明されています。具体的なサブクラスがその実装を提供します。

そのため、

Factory#createProduct()

の処理を実行しているわけではありません。

Factoryにあるのは、

protected abstract Product createProduct(String owner);

という「このメソッドを持つ」という約束です。

実行時には、

IDCardFactory#createProduct()

が動いています。

この違いを理解すると、抽象メソッドの役割がかなり分かりやすくなりました。

なぜわざわざFactory型で扱うのか

ここまで理解すると、今度は、

IDCardFactory factory = new IDCardFactory();

と直接書けばよいのではないかと思います。

それでも動作自体はします。

しかし、

Factory factory = new IDCardFactory();

とすることで、利用する側は具体的なFactoryではなく、Factoryという抽象的な型に依存できます。

たとえば将来、

MembershipCardFactory

というFactoryを追加したとします。

Factory側で定めたルールに従っていれば、

Factory factory = new MembershipCardFactory();

として扱えます。

利用する側からすると、

「Factoryならcreate()を呼べる」

ということだけ知っていればよくなります。

具体的なインスタンスの生成方法を利用側から切り離せるわけです。

Factory Methodを単純に、

newをサブクラスに移動するパターン

と覚えるより、

生成処理の共通ルールと具体的な生成方法を分離するパターン

と考えたほうが、自分には理解しやすく感じました。

Template Methodとの関係も見えてきた

今回の例では、

public final Product create(String owner) {
    Product product = createProduct(owner);
    registerProduct(product);
    return product;
}

という処理があります。

create()では、

  1. Productを生成する
  2. Productを登録する
  3. Productを返す

という全体の流れが決まっています。

一方、

createProduct()
registerProduct()

の具体的な処理はサブクラスに任せています。

つまり、

全体の処理手順
        ↓
Factoryで決める

具体的な処理
        ↓
IDCardFactoryで決める

という構造です。

Factory Methodを学んでいましたが、ここにはTemplate Methodにも通じる考え方があることが分かります。

単に「Factoryという名前のクラスを作ること」がFactory Methodなのではなく、抽象クラスと継承を使って、変更する部分をサブクラス側へ分離していることが重要なのだと理解しました。

今回理解できたこと

Factory Methodを学び始めた時点では、

Factory Methodはインスタンス生成をサブクラスに任せるパターン

という理解でした。

これは間違いではありませんが、コードを追うには少し情報が足りませんでした。

今回整理して理解できたことは次の通りです。

  • 抽象クラスでは抽象メソッドを「実装」するのではなく「宣言」する
  • 抽象メソッドの具体的な処理はサブクラスが実装する
  • Factory factory = new IDCardFactory()では、変数の型はFactoryでも実体はIDCardFactory
  • Factoryの具体メソッドcreate()はそのまま実行される
  • create()から抽象メソッドを呼ぶと、実際のインスタンス側でオーバーライドされたメソッドが実行される
  • これはJavaのポリモーフィズムによって実現されている
  • Factory Methodでは、生成処理の共通ルールと具体的な生成方法を分離できる

特に重要だったのは、

Factory factory = new IDCardFactory();

を、

Factory型の変数
        +
IDCardFactoryのインスタンス

と分けて考えることでした。

まとめ

Factory Methodのクラス図だけを見ると、

Factory
    ↑
ConcreteFactory

Product
    ↑
ConcreteProduct

という継承関係は比較的理解しやすいです。

しかし、実際のコードになると、

Factory factory = new IDCardFactory();
factory.create(...);

のように抽象側の型を通して処理を実行するため、最初は「結局どのクラスのメソッドが動いているのか」が分からなくなりました。

今回の学習で、Factory Methodを理解するにはデザインパターンだけを見るのではなく、

抽象クラス・継承・オーバーライド・ポリモーフィズム

を合わせて考えることが重要だと分かりました。

Factoryは「こうやってProductを作る」という大枠を定義する。

ConcreteFactoryは「具体的に何を作るか」を実装する。

そして実行時には、変数の型ではなく実際のオブジェクトに応じたオーバーライド先の処理が呼ばれる。

この流れまで理解することで、「インスタンス生成をサブクラスに任せる」というFactory Methodの説明が、ようやくコードと結びつきました。

参考文献

  • 『Java言語で学ぶデザインパターン入門』
  • Oracle Java Tutorials「Abstract Methods and Classes」
  • Oracle Java Tutorials「Overriding and Hiding Methods」
  • Java Language Specification - Classes