ひょんな事で、Java で抽象クラス ( Abstract class ) の中で Builderパターンを摘要してみたくなったので、その記録です。
Builderパターン
なんだかよくわからない表題ですね。
そもそも、Builder パターンとはどんなものかというと、GoF(Gang of four)のデザインパターンによれば、オブジェクトの生成過程を抽象化することによって、オブジェクトの動的生成を可能にする(Wiki)とあります。
また、これとは別に有名なのは、「Effective Java」による、Builderパターンでしょう。こちらは、コンストラクタに多数のコンストラクタ引数が必要になった場合に検討するパターンとして紹介されています。
GoF による Builderパターン
こらは、あるオブジェクトの生成を抽象化することで、生成器(ビルダー)を入れ替えることで、似た生成方法の別のオブジェクトの生成にも使用することができます。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
Builder builder = new CharacterBuilder();
Director director = new Director(builder);
director.construct();
}
}
class Character {
private String name;
private int age;
private String sex;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getSex() {
return this.sex;
}
@Override
public String toString() {
return "名前: " + this.name + ", 年齢: " + this.age + ", 性別: " + sex;
}
}
abstract class Builder {
public abstract void setName(String name);
public abstract void setAge(int age);
public abstract void setSex(String sex);
public abstract Character build();
}
class CharacterBuilder extends Builder {
private Character character;
public CharacterBuilder() {
character = new Character();
}
public void setName(String name) {
character.setName(name);
}
public void setAge(int age) {
character.setAge(age);
}
public void setSex(String sex) {
character.setSex(sex);
}
public Character build() {
return character;
}
}
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.setName("シンジ");
builder.setAge(14);
builder.setSex("男");
Character c = builder.build();
System.out.println(c.toString());
}
}
Effective Java
これは、Javaプログラマにとっては必携の一冊となっている、「Effective java」で、
「数多くのコンストラクタパラメータに直面した時にはビルダーを検討する」
で紹介されている内容です。
今回の表題は、こちらのパターンを使って、ということになります。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
Character c = new Character.Builder()
.setName("シンジ")
.setAge(14)
.setSex("男").build();
System.out.println(c.toString());
}
}
class Character {
/* ビルダー。staticなインナークラスとして実装 */
public static class Builder {
private String name;
private int age;
private String sex;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setSex(String sex) {
this.sex = sex;
return this;
}
public Character build() {
return new Character(this.name, this.age, this.sex);
}
}
/* インスタンスフィールド */
private String name;
private int age;
private String sex;
/* このクラスは直接 New できない。ビルダーを使用してインスタンスを生成 */
private Character(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public String getSex() {
return this.sex;
}
@Override
public String toString() {
return "名前: " + this.name + ", 年齢: " + this.age + ", 性別: " + sex;
}
}
抽象クラスに Builderパターン
通常の Builderパターンとの違いは?
Builder パターンは、オブジェクトの生成に必要な材料を一つ一つ組み上げていき、最後にそのオブジェクトを new
することで得ることが出来るようになっています。
ところが、この Builder パターンを、抽象クラスの中で定義すると、最後の「オブジェクトの生成」の部分で困ることになります。
なぜなら、抽象クラスは new
できない からです。
/** 抽象(abstract)クラス */
abstract class Character {
/* ビルダー */
public static class Builder {
private String name;
private int age;
private String sex;
public Builder setName(String name) {
:
:
public Character build() {
// ここでコンパイルエラー
return new Character(this.name, this.age, this.sex);
}
}
private String name;
private int age;
private String sex;
private Character(String name, int age, String sex) {
:
:
}
そこで、抽象クラスの具象クラス(実装クラス)側に最終的なオブジェクトの生成をさせないといけません。
実装例
以下が、その実装例になります。
抽象クラスのstaticなインナークラスとしての Builder はそのままですが、ここではこの BUilder 自身も build()
メソッドを abstract とするために、抽象クラスとしています。
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
PlayerCharacter p1 = new HumanPlayer.PlayerBuilder()
.name("シンジ")
.age(48)
.sex("男").build();
PlayerCharacter p2 = new DwarfPlayer.PlayerBuilder()
.name("ザクカラバス")
.age(38)
.sex("男").build();
PlayerCharacter p3 = new ElfPlayer.PlayerBuilder()
.name("ヤバ")
.age(28)
.sex("男").build();
System.out.println(p1.toString());
System.out.println(p2.toString());
System.out.println(p3.toString());
}
}
abstract class PlayerCharacter<T extends PlayerCharacter<T>> {
// 抽象クラス化した Builder
public static abstract class Builder<T> {
private String name;
private int age;
private String sex;
public Builder<T> name(final String name) {
this.name = name;
return this;
}
public Builder<T> age(final int age) {
this.age = age;
return this;
}
public Builder<T> sex(final String sex) {
this.sex = sex;
return this;
}
// 具象クラス (実装クラス) はオブジェクト生成のメソッドを実装する。
public abstract T build();
}
private String name;
private int age;
private String sex;
protected PlayerCharacter(final Builder<T> builder) {
// 実装されている Builder クラスからオブジェクト生成
this.name = builder.name;
this.age = builder.age;
this.sex = builder.sex;
}
@Override
public String toString() {
return "Name: " + this.name + ", Age: " + this.age + ", Sex: " + this.sex;
}
}
/* 抽象クラスの実装クラス */
class HumanPlayer extends PlayerCharacter<HumanPlayer> {
public HumanPlayer(final Builder<HumanPlayer> builder) {
super(builder);
}
/* 抽象 Builderクラスの実装 */
public static class PlayerBuilder extends PlayerCharacter.Builder<HumanPlayer> {
@Override
public HumanPlayer build() {
return new HumanPlayer(this);
}
}
@Override
public String toString() {
return "Race: 人間, " + super.toString();
}
}
class DwarfPlayer extends PlayerCharacter<DwarfPlayer> {
public DwarfPlayer(final Builder<DwarfPlayer> builder) {
super(builder);
}
public static class PlayerBuilder extends PlayerCharacter.Builder<DwarfPlayer> {
@Override
public DwarfPlayer build() {
return new DwarfPlayer(this);
}
}
@Override
public String toString() {
return "Race: ドワーフ, " + super.toString();
}
}
class ElfPlayer extends PlayerCharacter<ElfPlayer> {
public ElfPlayer(final Builder<ElfPlayer> builder) {
super(builder);
}
public static class PlayerBuilder extends PlayerCharacter.Builder<ElfPlayer> {
@Override
public ElfPlayer build() {
return new ElfPlayer(this);
}
}
@Override
public String toString() {
return "Race: エルフ, " + super.toString();
}
}
これで、抽象クラス側で、共通のインスタンス変数への値のセットを 抽象Builder で行い、具象クラス側でそのオブジェクトの生成を行うことが出来るようになりました。
まとめ
今回のこのネタ、またしても Stack Overflow さんにお世話になりました。
本当にいつもありがとうございます
いつもよりも文字サイズを大きめにお礼をさせていただきます。
私ほどのものがオブジェクト指向の何たるかを語るような資格がないのはわかっていますが、オブジェクト指向っていうのは本当に深いですね。
参考文献
今回、色々参考にさせていただいた文献です。
どちらも、Java プログラマー必携の書となっており、Web上でも多くの方が参考にされています。
増補改訂版Java言語で学ぶデザインパターン入門
GoF のデザインパターンを多くのサンプルプログラムで学習できる良書。
参考プログラムも色々示唆に飛んでいて面白いです。
Effective Java 第3版
これはもう、バイブルと言っても良いくらいに Java における様々なベストプラクティスが載っています。読破するというより、自分の気になる項目を読んでいくだけでも面白いですね。
現在は 第3版がこの間でたばかりみたいですが、私が参考にしているのは 第2版 (2014年発行)の方になります。第2版は Java5 時代の記述でいささかふるさを感じます。
新しい方も買うべきなんだろうなぁ。ていうか買いたい。
0件のコメント