なぜデザインパターンを理解できないのか


本記事はうるる Advent Calendar 2019 14日目の記事です。

はじめに

エンジニアなら一度は必ず耳にする「デザインパターン」
ただし、「デザインパターン」の勉強って何をすればいいのかや、勉強したけどあまり理解できないという人は少なくないのではないでしょうか?
そこで自分が1ヶ月必死に勉強をした際に思った、必ず抑えとくべきことを紹介したいと思います。
※上級者向きではないので悪しからず...

デザインパターンとは

ソフトウェアをより修正・保守しやすくするもの
つまり、目的は柔軟性(再利用性)を保つこと

抑えとくべき事

  1. デザインパターンはただの考え方に過ぎない
  2. 継承とコンポジションの理解

デザインパターンは考え方の違い

デザインパターンと聞いて難しそうだなと感じてしまう人がいると思いますが、
デザインパターンは単にオブジェクト指向開発での設計思想です。
なので、デザインパターンの1つ1つには必ず、「前提・目的・解決策・メリット・デメリット」が存在していると思っています。
ここではTemplate Methodパターンを例に「前提・目的・解決策・メリット・デメリット」「サンプルコード」を書いてみます。

Template Method

クラス図

コードを記述する前に「前提・目的」を抑え、どのような考え方でパターンを適用するのかをイメージする

前提:
・コードの重複があれば、それは設計の整理が必要であることを示している

目的:
・重複の行を無くしたい
・一つのメソッドが長くなるのを防ぎたい

解決策:
・アルゴリズムが手順を定義し、一つ以上の手順の実装をサブクラスで提供する

メリット:
・再利用性が高くなる

デメリット:
継承を使用する分「柔軟性」が少なくなる

上記の「クラス図」「前提・目的」を見てもらえば分かると思うのですが、
Template Methodは単に別のメソッドで重複するコードがあれば、抽象クラスに処理を移動させましょうっと言っているだけです。
ここでコードを見てみましょう!!

Template Method サンプルコード

<?php

// 抽象クラスの定義はしてないが、共通の処理をメソッドに分ける
class BasicTraining
{
    private function exercise()
    {
        return "準備運動をします";
    }

    private function afterTraining()
    {
        return "プロテインを補給します";
    }

    public function doTraining()
    {
        echo $this->exercise()."\n";
        echo $this->mainTraining()."\n";
        echo $this->afterTraining()."\n";
    }
}

// サブクラスにそれぞれの実装を記述
class ChestTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "チェストプレスをする";
    }
}

// サブクラスにそれぞれの実装を記述
class LegTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "レッグプレスをする";
    }
}

// サブクラスにそれぞれの実装を記述
class AbsTraining extends BasicTraining
{
    public function mainTraining()
    {
        return "腹筋をする";
    }
}

// Template Methodを使用する際はFactoryパターンも使用することが多い
class TrainingFactory
{
    public static function create($menu)
    {
        switch ($menu) {
            case "胸":
                return new ChestTraining();
            case "足":
                return new LegTraining();
            case "腹":
                return new AbsTraining();
            default:
                throw new Exception("不適切なメニューです");
        }
    }
}

try {

    $training = TrainingFactory::create("胸");
    $training->doTraining();

    $training = TrainingFactory::create("足");
    $training->doTraining();

    $training = TrainingFactory::create("腹");
    $training->doTraining();

} catch (Exception $e) {
    echo $e->getMessage();
}

デザインパターンでは「前提・目的・解決策」この3つは必ず存在する。
この3つを意識することによって理解するスピードが変わってくるので、ぜひ3点意識するようにしてください!!

継承・コンポジション

中にはコンポジションって何なの?と思う人もいると思います。
その場合は必ずオブジェクト指向開発の勉強を再度行ってください。
なぜなら、「コンポジションを理解していないとデザインパターンは理解することができない」と思うからです。

デザインパターンの目的

「柔軟性(再利用性)を保つこと」でしたよね。
コンポジションでシステムを作成すると柔軟性がかなり向上します。
何で柔軟性が向上するのかは、長くなるので別の機会で説明します。
コンポジションの理解には、こちらの記事がおすすめ ※ 引用失礼します

つまり、デザインパターンではコンポジションを多用しているのです。
逆にコンポジションを理解・使用できると一気にデザインパターンの理解力が向上します。
ここで先ほどのTemplate Methodを継承からコンポジションにシステム設計を変更して見ましょう!

サンプルコード 上記のコードをコンポジションで作成

<?php

// __constractでコンポジションを実現
class BasicTraining
{
    private $training;

    public function __construct($mainTraining)
    {
        $this->training = $mainTraining;
    }

    private function exercise()
    {
        return "準備運動をします";
    }

    private function afterTraining()
    {
        return "プロテインを補給します";
    }

    public function doTraining()
    {
        echo $this->exercise()."\n";
        echo $this->training->mainTraining()."\n";
        echo $this->afterTraining()."\n";
    }
}

// 継承をしていない
class ChestTraining
{
    public function mainTraining()
    {
        return "チェストプレスをする";
    }
}

// 継承をしていない
class LegTraining
{
    public function mainTraining()
    {
        return "レッグプレスをする";
    }
}

// 継承をしていない
class AbsTraining
{
    public function mainTraining()
    {
        return "腹筋をする";
    }
}

class TrainingFactory
{
    public static function create($menu)
    {
        switch ($menu) {
            case "胸":
                return new BasicTraining(new ChestTraining());
            case "足":
                return new BasicTraining(new LegTraining());
            case "腹":
                return new BasicTraining(new AbsTraining());
            default:
                throw new Exception("不適切なメニューです");
        }
    }
}

// ここの処理は変更していない
try {

    $training = TrainingFactory::create("胸");
    $training->doTraining();

    $training = TrainingFactory::create("足");
    $training->doTraining();

    $training = TrainingFactory::create("腹");
    $training->doTraining();

} catch (Exception $e) {
    echo $e->getMessage();
}

コンポジションに変更して

気づいた方もいるかもしれないのですが、Template Methodパターンを継承からコンポジションに変更すると
Strategyパターンにクラス図が変更します。
デザインパターン面白い!

Strategy

こういう風にパターン・使用する目的は違うけど、コードが似ているパターンはたくさん存在します。
なので、コンポジションを使用できるようになっていれば理解も加速すると思いますし、
あまりオブジェクト指向が分からない人は、もう一度オブジェクト指向の勉強から始めるといいと思います。

まとめ

今回自分が作成したサンプルコードはどっちのパターンがいいのかは正直どうでもいいのですが、
開発者同士が「前提・目的・解決策」を理解してシステムを作成することによって開発効率が劇的に向上すると思います。
ぜひ、下記のことを意識しながらデザインパターンの理解を深めていきましょう!!
- デザインパターンはただの考え方に過ぎない
- 継承とコンポジションの理解

参考文献

  • Head First デザインパターン
  • オブジェクト指向でなぜつくるのか 第2版
  • オブジェクト指向のこころ
  • 増補改訂版Java言語で学ぶデザインパターン入門