システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

FactoryはEnumで実装すると良さそう

Enum活用への提案

ヒントになったのは「現場で役立つシステム設計の原則」。

書評でも良かったところとして挙げた「ロジックをenumを使って表現する」のところ。

vermeer.hatenablog.jp

Enumにロジックを表現する、という例としては Strategyがあるようです。

【enum】メソッドの定義(3)−strategyパターンを使う方法 - THE HIRO Says

StrategyをEnumに持たせてはいけない理由 - にょきにょきブログ

後者では、気を付けておくべきこと、ということとしてEnumのStrategyを説明されているようですが、懸念されているところについては Factoryであれば問題ないように思います(たぶん)。

想定ケース

画面から入力された値に応じたクラスを生成したい。

生成されるクラス

生成クラス用インターフェース

public interface GenderInterface {

    public void action();
}

具象クラス

public class Male implements GenderInterface {

    @Override
    public void action() {
        System.out.println("Male!!");
    }

}
public class Female implements GenderInterface {

    @Override
    public void action() {
        System.out.println("Female!!");
    }

}

通常のFactory(Enumは列挙子のみ)

Enum

public enum GenderType {
    MALE, FEMALE
}

Factory

    public static GenderInterface create(GenderType gender) {

        switch (gender) {
            case MALE:
                return new Male();
            case FEMALE:
                return new Female();
            default:
                throw new IllegalArgumentException("gender code invalid");
        }
    }
}

使用例

public void orthodoxFactoryMethod() {
    GenderFactory.create(GenderType.valueOf("MALE")).action();
    GenderFactory.create(GenderType.valueOf("FEMALE")).action();
}

所感

画面側の値をEnumの列挙子名に変換することが必要になるので これだったら、そもそもEnumを使用しない方が良いと思う。

通常のFactory(EnumにStrategy)

Enumに判定用の定数とStrategyクラスを設定して、Factoryクラスは別に作成したケース*1

StrategyをEnumで保持していないところを除けば、今回の実験前の私の実装に近いものです。

Enum

public enum GenderTypeWithStrategy {
    MALE(0, new Male()),
    FEMALE(1, new Female());

    private final Integer genderCode;
    private final GenderInterface gender;

    private GenderTypeWithStrategy(Integer genderCode, GenderInterface gender) {
        this.genderCode = genderCode;
        this.gender = gender;
    }

    public Integer genderCode() {
        return this.genderCode;
    }

    public GenderInterface getGender() {
        return this.gender;
    }

}

Factory

public class GenderEnumStrategyFactory {

    public static GenderInterface create(Integer genderCode) {

        if (Objects.equals(GenderTypeWithStrategy.MALE.genderCode(), genderCode)) {
            return GenderTypeWithStrategy.MALE.getGender();
        }

        if (Objects.equals(GenderTypeWithStrategy.FEMALE.genderCode(), genderCode)) {
            return GenderTypeWithStrategy.FEMALE.getGender();
        }

        throw new IllegalArgumentException("gender code invalid");
    }
}

使用例

public void enumStrategyFactoryMethod() {
    GenderEnumStrategyFactory.create(0).action();
    GenderEnumStrategyFactory.create(1).action();
}

所感

Enumで区分値とStrategyを管理するので、情報の集約は出来ている感じ。

ただ Factoryの方をもう少しすっきり実装したい。

今回の実験前だと、私はreturnのところに 各インスタンス生成ロジックを記述していました。

EnumでFactory(パラメータ無し)

Enum Factory

public enum Gender {
    MALE(0, new Male()),
    FEMALE(1, new Female());

    private final Integer genderCode;
    private final GenderInterface gender;

    private Gender(Integer genderCode, GenderInterface gender) {
        this.genderCode = genderCode;
        this.gender = gender;
    }

    public static GenderInterface of(Integer genderCode) {
        for (Gender genderType : Gender.values()) {
            if (Objects.equals(genderType.genderCode, genderCode)) {
                return genderType.gender;
            }
        }
        throw new IllegalArgumentException("gender code invalid");
    }
}

使用例

public void enumFactoryMethod() {
    Gender.of(0).action();
    Gender.of(1).action();
}

所感

情報と振る舞いが集約された。

区分値が増えても生成ロジック部分(of)に手を加えなくて良い。

EnumでFactory(パラメータあり)

(2017/11/4 追記)

生成されるクラス

生成クラス用インターフェース

public interface SexInterface {

    public <T extends SexInterface> T create(String name);

    public void action();
}

具象クラス

public class Man implements SexInterface {

    private String name;

    protected Man() {
    }

    protected Man(String name) {
        this.name = name;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends SexInterface> T create(String name) {
        return (T) new Man(name);
    }

    @Override
    public void action() {
        System.out.println("Man:" + this.name);
    }

}
public class Woman implements SexInterface {

    private String name;

    protected Woman() {
    }

    protected Woman(String name) {
        this.name = name;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends SexInterface> T create(String name) {
        return (T) new Woman(name);
    }

    @Override
    public void action() {
        System.out.println("Woman:" + this.name);
    }

}

Enum Factory

public enum Sex {
    MAN(0, new Man()),
    WOMAN(1, new Woman());

    private final Integer sexCode;
    private final SexInterface sex;

    private Sex(Integer sexCode, SexInterface sex) {
        this.sexCode = sexCode;
        this.sex = sex;
    }

    public static SexInterface of(Integer sexCode, String name) {
        for (Sex sexType : Sex.values()) {
            if (Objects.equals(sexType.sexCode, sexCode)) {
                return sexType.sex.create(name);
            }
        }
        throw new IllegalArgumentException("sex code invalid");
    }

    public boolean isAssignableFrom(SexInterface sex) {
        return this.sex.getClass().isAssignableFrom(sex.getClass());
    }

}

使用例

public void enumFactoryMethodWithParam() {
    Sex.of(0, "taro").action();
    Sex.of(1, "hanako").action();
}

所感

生成時に情報を付与してインスタンスを生成することもできます。

どちらかというと、パラメータを使っている この例の方が よりFactory感がある気がしますので追記しました*2

追記(2017/11/5)

メソッドisAssignableFrom を追加しました*3

比較ロジックもEnum側で実装しておけば、コード内の比較ロジックも読みやすくなるかな?と思います。

例えば男女インスタンスをリストに格納して、あとから逐次判定をしたいときとかに使うイメージです。

ドメイン情報の漏れ出しにつながりやすいところだとは思いますが、個々のロジックに比較を実装するよりは統一的な操作を提供するやり方の方が前向きな気がします。

少なくとも、このやり方であれば 区分値の追加時にEnum列挙子の追加だけで実装側に比較ロジックを実装する必要はなくなります。

インスタンス型判定のメソッド

生成したインスタンスを比較したい場合に使用する共通的なメソッドです。

使用例

public void isAssignableFrom() {
    SexInterface man = Sex.of(0, "taro");
    SexInterface woman = Sex.of(1, "hanako");

    Assert.assertThat(Sex.MAN.isAssignableFrom(man), is(true));
    Assert.assertThat(Sex.WOMAN.isAssignableFrom(woman), is(true));
}

Code

https://bitbucket.org/vermeer_etc/enum-factory

さいごに

EnumでFactoryを実装しようと思った動機としては、区分値判定を減らしたいな、もしくは集約させたいな、と思ったからです。

言い方は良くないですが、集約すること自体を目的とした実験をしてみよう、というのを動機として やってみたら悪くは無さそうな実装例だったので公開したという感じです。

*1:Factoryクラス名が変なのは違いを表現するためなのでスルーしてください

*2:というか、自分の実装の実験としてはパラメータ有りである必要があっただけですが それを失念していただけです

*3:あとtypoも…

Optionalへの考察

nullを安全に扱うために使用するOptionalの使い方について、自分なりの指針を整理しておこうかなっと。

戻り値にのみ使用する

以上、おわり。

いや、、それだけではだめですよね。。

使用側に通知するため

使用側にとって戻り値がnull可能性の有無が分かれば防御的実装の有無について判断がやりやすくなるでしょう。結果としてNPEの発生リスクを軽減させることができるようになります。

多分、これがOptionalの基本指針だと思っています。

引数に使うのはダメなの?

使わない設計思想が良さそうに考えています。

nullかもしれないのだったらオーバーロードを使おう

引数がnullかもしれないというのであれば、明確に使用しないケースのメソッドを定義する方が使用側にとって分かりやすいメソッドになると思います。

Optionalを引数にしてしまうと、引数指定が汎用的な巨大なメソッドを作りやすくなってしまう。結果として、巨大でなんでもできるメソッドを1つになってしまって、使用側にとって分かりにくい状態になりそう。

でも型だよね?

型なんだから引数にOptionalを使ってもいいじゃないか、というところについての考察。

具体的には、戻り値をそのまま次のメソッドの引数に指定したいケースがあるよね、というところについての考察でもあります。

確かに、そういうことはやりたいのですが、でもそれはロジックが外に漏れだしつつある傾向なのかも。

「受け取ったものを何も操作せずに他に渡すだけ」となっているのでは?

「至る処で対象の戻り値に対して ifPresentisPresentがあって、しかもその分岐内の実装が類似」となっているのでは?

こういうケースの場合は、そもそもクラスにまとめた方が良いのかもしれません。

また、nullについては 例外と同じように、それを検知した時点で 塗りつぶさず対応をすることを繰り返しておけば 不意のNPEに悩まされるリスクは減るのではないかなと思っています。

参考

【Java】Optionalの正しい使い方を学ぶ #Java - Qiita

ちゃんと読んでいるわけではありません。

とりあえず、指針に則って実装してみて、なんかやりにくいなと思ったら指針の見直しのための読むためのリンクです。

2024.2.12 追記

https://blogs.oracle.com/otnjp/post/recipes-for-using-the-optional-class-as-its-meant-to-be-used-ja

さいごに

「お前はできてんのか?」

いえ出来ていないですよ(笑)

この指針に則って今後リファクタリングをしよう、という希望を込めた意思表明みたいなものです。

クライアントのメモ

SPAとかJavascriptとかクライアント関連のメモ

SPAを構築するときに知っておいた方がいい7つの課題 | I am mitsuruog

Yeomanを使った簡単SPA開発手順 - albatrosary's blog

ブログをシングルページアプリケーション(SPA)にするメリット・デメリット | 綺麗に死ぬITエンジニア

www.slideshare.net

https://www.fieldexit.com/forum/display?threadid=208

SPAでhistory backでのスクロール位置 - chocotakaの日記

Java Web Startを久々に使ってみた - torutkの日記

speakerdeck.com

メッセージングのメモ

vermeer.hatenablog.jp

の流れで、ざっくりと拾い集めたもの。

Yahooの取り組み

www.slideshare.net

Kafka

sssslide.com

www.slideshare.net

www.slideshare.net

Pulsar

techblog.yahoo.co.jp

www.slideshare.net

www.slideshare.net

JJUG ナイトセミナー 「メッセージングミドルウェア特集」に参加してきました #jjug

まとめ

togetter.com


メモ

実運用して分かったRabbit MQの良いところ・気をつけること

www.slideshare.net

  • ブローカー無しのメッセージングはダメ!メッセージングミドル導入は絶対
  • メッセージは標準仕様が使えるよ
  • ロードバランサーはいらないよ(というか それが原因でメッセージロストして大変だったよ)
  • トラフィック統計と大量処理はトレードオフ
  • ディスクIO対策でマスタはHDD、スレーブはメモリ。2年運用しているけど 今のところ大丈夫

40分弱でわかるApache Kafka

CCCでも聞かせていただきましたが、今回は「製品の特長」に特化していた印象です。

  • ストリーミング処理に強みがあるよ
  • 事例が多いよ

以前、話を聞いたところもあり、メモは薄いですが 発表は相変わらず良かったですし、製品の特長も分かりやすかったです。ストリーミング処理基盤が必要だったら迷わずKafkaを選ぶと良さそうだ、と思います。

メッセージキュー「Pulsar」の紹介

www.slideshare.net

登壇者はコミッター。

新卒7年目でコミッター。

  • 3つの中では一番新しい製品だよ
  • Yahooが開発元だよ
  • 高いスループットに耐えられるよ
  • スケーリングに強いよ
  • マルチテナントだよ
  • 環境構築については3つの中では、一番手間かもしれないよ
  • 設定が階層的に継承できるよ
  • Kafkaクライアントラッパーがあるので、Kafkaからの移行も楽だよ*1

感想

ざっくり

  • 手始めにメッセージングを試してみたいなら、標準仕様を備えているRabbit MQ
  • メッセージングによるストリーミング処理を検討しているならKafka
  • 高いスループットが想定するならPulsar
  • 高いスループットを想定しなくても、メッセージングだったらPulsar
  • スケーリングが容易なのはPulsar
  • マルチテナントならPulsar
  • 個人的には使い勝手が良さそうな印象なのはKafka
  • Kafkaクライアントで実装しておいて、状況によりバックヤードはPulsarにするとか、っていうのはアリかも

Yahooという1つの企業で、メッセージングだけでも色々な製品を使っているというのが面白いですね。

なんとなくですがRabbit MQPulsarのマルチテナントの1つに まとまる方向になる気がしました。Kafkaはストリーミング処理への強み(便利さ)などを考えると 下手にまとめない方が良いという印象を受けました。

というような感じで、一言にメッセージングミドルで片づけてしまうところについて、製品特徴を鑑みて「ここには これを適用してみたら?」と思えるようになったのが最大の収穫だったと思います。

関連

www.slideshare.net

*1:そこまでは言っていないけど、そう言っているように思えた