Tagbangers Blog

Springの学習日記Part2: デザインパターン

もう春の兆し

最近暖かくなってきたのものあって朝は清々しくて気持ちいいですね(夜は寒いけど)。こういう日はランニングとかしたら気持ちいいだろうなーなんて思いながらいつもゴロゴロしてます笑。

とはいえ運動不足も嫌なのでスクワッドは毎日欠かさずやるようにしてます。なんかふくらはぎが第二の心臓らしい?(のでちょっと調べてみた)

ふくらはぎの筋肉を動かすことで、足に滞っている血液を心臓の方へ押し戻すことができます。 このことから、ふくらはぎは別名「第二の心臓」 と呼ばれており、とても大切な働きをしているのです。
なるほど、血液を心臓に押し戻さないといけないとどうなる?・・・って想像したらなんとなく
茶番はこの辺にしておいて、今回はソフトウェアのデザインパターンについて、アンチパターンを交えながら学んだことを徒然書いていきたいと思います。

デザインパターンとは

そもそもデザインパターンの定義とは、

調べたところによると、デザインパターンはベストプラクティスで書かれたより良いソフトウェアを作るのに役立つ設計手法のことで、プログラミングでよく遭遇する一般的な問題の解決策である「設計パターン」を提供するそう。

なるほど、まず問題にぶち当たって見ないとわからないやつか・・・ふむふむ

ってことで早速アンチパターンについて見てみます。

アンチパターン

オブジェクト指向プログラミングにおけるアンチパターンの種類としては例えば以下のようなものが挙げられます。

  • God Object: 特定のクラスやオブジェクトがあまりにも多くの責任を持ち、あまりにも多くの機能やメソッドを提供しすぎる状態のこと。
    God オブジェクトが発生すると、そのクラスがプログラム全体であらゆる機能を制御し、あらゆる情報にアクセスできるため、コードの理解や保守が難しくなる。また、God オブジェクトに多くの責任が積み重なると、変更が困難になり、コードの柔軟性が低下する。
  • Sequential coupling(連続的な結合): 異なる部分やモジュールが強く依存し合い、順序に依存している状態を指します。これは、一連の処理が特定の順序で実行されなければならず、それによってモジュール間の結合が生じている場合に発生します。あるモジュールの変更が他のモジュールに影響を与え、その順序が崩れると、予期せぬ問題が発生する可能性があります。
  • Circular dependency: 複数のモジュールやクラスが循環的に依存し合っている状態を指します。

次に、下記のようなサンプルコードで考えてみます。

Runner

public class Runner {
    public static void main(String... args) {
        TextRenderer textRenderer = new TextRenderer(new DefaultFontStyleRenderer(), new DefaultColorRenderer(), new DefaultFontWeightRenderer());
        textRenderer.render("Default Rendering");

        TextRenderer redRenderer = new TextRenderer(new DefaultFontStyleRenderer(), new RedColorRenderer(), new DefaultFontWeightRenderer());
        redRenderer.render("Red Color Rendering");

        TextRenderer boldRenderer = new TextRenderer(new DefaultFontStyleRenderer(), new DefaultColorRenderer(), new BoldFontWeightRenderer());
        boldRenderer.render("Bold Rendering");
    }
}

このサンプルコードでは、

あるテキストのフォントスタイル、カラー、フォントの太さについて変更したい場合に、のインスタンスを作成し、

1つ目でデフォルトのテキストレンダリングを行い、2つ目で赤文字のテキストレンダリング、3つ目で太字のテキストレンダリングを行っています。

この書き方だと、実際にそこまでアンチパターンではないです。

しかし、異なるテキストレンダラーを使いたい場合に、変更されるプロパティは1つだけであるにも関わらず、毎回全てのプロパティとレンダラーをこのクラスに提供する必要があり、冗長であるといえます。

TextRenderer

class TextRenderer {

    private final FontStyleRenderer fontStyleRenderer;
    private final FontColorRenderer fontColorRenderer;
    private final FontWeightRenderer fontWeightRenderer;

    TextRenderer(FontStyleRenderer fontStyleRenderer, FontColorRenderer fontColorRenderer, FontWeightRenderer fontWeightRenderer) {
        this.fontStyleRenderer = fontStyleRenderer;
        this.fontColorRenderer = fontColorRenderer;
        this.fontWeightRenderer = fontWeightRenderer;
    }

    void render(String text) {
        String renderedText = applyRendering(text, fontStyleRenderer, fontColorRenderer, fontWeightRenderer);

        System.out.println(renderedText);
    }

    private String applyRendering(String text, Renderer... renderers) {
        for (Renderer renderer : renderers) {
            text = renderer.render(text);
        }
        return text;
    }
}

では、これらの重複を避けるにはどうするべきかを考えてみます。

ビルダー・パターンの使用

今回このケースで使えるビルダーパターンについて紹介します。

ところでビルダーパターンってなんぞ?って人のために、

ビルダーパターンは、オブジェクトの複雑な構築過程を抽象化して、柔軟性を提供するためのデザインパターンの一つです。

このパターンは、パラメータが多くあるオブジェクトの作成や構築に有用です。

先ほどのコードを下記のように書き換えることができます。

Runner

public class Runner {
    public static void main(String... args) {
        TextRenderer textRenderer = new TextRendererBuilder().build();
        textRenderer.render("Default Rendering");

        TextRenderer redRenderer = new TextRendererBuilder()
                .withFontColorRenderer(new RedColorRenderer())
                .build();
        redRenderer.render("Red Color Rendering");

        TextRenderer boldRenderer = new TextRendererBuilder()
                .withFontWeightRenderer(new BoldFontWeightRenderer())
                .build();
        boldRenderer.render("Bold Rendering");
    }
}

TextRenderer textRenderer = new TextRendererBuilder().build(); で表現しているように、TextRendererBuilderを使用することで、テキストレンダラーに全てのデフォルト値を提供するので、デフォルトのレンダラーを提供する必要がなくなりました。

最初のtextRendererでは、何も指定せずにbuild()メソッドを呼ぶことで、デフォルトの設定が使用されており、

その後、redRendererではwithFontColorRendererメソッドを使用して、赤い色のフォントレンダラーを指定しています。

同様に、boldRendererではwithFontWeightRendererメソッドを使用して、太字のフォントウェイトレンダラーを指定しています。


改めてデザインパターンについて

今回はBuilderを例にしましたが、

GoFで紹介されているデザインパターンについては他にも下記のようなものがあります。

  • Factory Method
  • Template Method
  • Strategy
  • Observer
  • Visitor

GoF「Gang of Four」(フォー・オブ・ア・カインド)

ソフトウェア開発における再利用可能な設計パターンを提供しています。これらのデザインパターンは、実用的な問題に対処するための一般的な解決策であり、柔軟性、拡張性、保守性などのソフトウェア品質を向上させるための指針として広く受け入れられています。

今後機会があれば他のデザインパターンについても記していきたいと思っています。

ご精読ありがとうございました。次回はインターフェースの活用について書きたいと思います。