🧗

【設計Tips】具体classへ情報を集約しInterface依存に切り替える

2021/09/22に公開

*社内で共有しているMEMO書きのような記事です。品質よりも、とにかく大枠を描き切ることをゴールとした記事です。

今、丸(Circle)、四角(Rectangle)、三角(Triangle)などの図形をHTML画面に描画するためのクラスShapeDrawerクラスを考えてみます。以下のように実装したとしましょう。

◇コード1

class Circle {
}

class Rectangle {
}

class Triangle {
}

class ShapeDrawer {
  drawHtml(shape: Circle | Triangle | Rectangle) {
    if (shape instanceof Circle) {
      const html = /* CircleからHtmlを構築 */
      document.append(html);
    } else if (shape instanceof Triangle) {
      const html = /* TriangleからHtmlを構築 */
      document.append(html);
    } else if (shape instanceof Rectangle) {
      const html = /* CircleからHtmlを構築 */
      document.append(html);
    }
  }
}

これでもShapeDrawerは動くのですが、以下のようにリファクタリングしてみましょう。

◇コード2

interface HtmlDrawable {
  drawHtml(): HTMLHtmlElement
}

class Circle implements HTMLHtmlElement {
  drawHtml(): HTMLHtmlElement {
    return; /* CircleからHtmlを構築 */
  }
}

class Rectangle implements HTMLHtmlElement {
  drawHtml(): HTMLHtmlElement {
    return; /* RectangleからHtmlを構築 */
  }
}

class Triangle implements HTMLHtmlElement {
  drawHtml(): HTMLHtmlElement {
    return; /* TriangleからHtmlを構築 */
  }
}

class ShapeDrawer {
  drawHtml(drawableObject: HtmlDrawable) {
    const html = drawableObject.drawHtml();
    document.append(html);
  }
}

HtmlDrawable interfaceを定義し、 HtmlDrawable.drawHtml を呼び出すことにします。この時、いくつかのメリットが生まれます。1.「具体クラスの個別の情報を、具体クラスの中に集約できる」、2.「ShapeDrawerクラスが抽象に依存するようになり変更されずらくなる」3. 「ShapeDrawerクラスを変更せずにShapeDrawerクラスが拡張される」4「ShapeDrawerクラスが抽象に依存するので汎用性が高まる」。5 「依存関係が逆転することで、具体に依存せずにShapeDrawerの開発を進められる」などの多数のメリットがあります。ここでは1について記述します。

1. 具体クラスの個別の情報を、具体クラスの中に集約できる。

「具体クラスの個別の情報を、具体クラスの中に集約できる」とは何を言っているかというと、リファクタリング前のコード1では、"Circleなど個別のクラスについての具体的な描写方法"に関する処理が、ShapeDrawer の中で定義されています。Circle クラスの個別の振る舞いについては、Circle クラスなどが保持したほうが直感的です。平易な言葉では、Circle クラスがどういうものかは、Circle クラスに関係するコードを見ればわかるのが直感的です。

また、Circle という図形をなくしたい、Circle を楕円したいなど、要求が変わった場合、コード1ではCircleクラスとShapeDrawer クラスの両方を修正しなければいけません。一箇所のクラスに情報を集約できていないと、Circleがコードのどこに影響を与えているか(依存範囲)を調べるか、実装者が覚えていなければいけません。

以下、リファクタリング前と後でどのように変化したか、簡単に図子します。

◇リファクタリング前

◇リファクタリング後

図のように、依存関係のグラフの線の数や点の数・情報の集約度合いをビジュアルでみていくと、コードの品質をある程度定量的に判断できます。

◇リファクタリング後に起きること

  • ShapeDrawerが、一個のInterfaceだけに依存するようになり
  • CircleはCircleの具体情報を、RectangleはRectangleの具体情報を、TriangleはTriangleの具体情報を持つように
  • HtmlDrawableを継承したLine,Squareなどの図形を足したり削除しても、他のクラスの設計になにも影響を与えない
  • Circle,Rectangle, Triangleは独立して削除・修正できるし、他のLineなどクラスと取り替えも容易

Memo

classDiagram
   ShapeDrawer ..> Circle
   ShapeDrawer ..> Rectangle
   ShapeDrawer ..> Triangle
   class ShapeDrawer {
       // Circleのdraw処理情報
       // Rectangleのdraw処理情報
       // Triangleのdraw処理情報
       +drawHtml()       
   }
classDiagram
   class HtmlDrawable {
       +drawHtml()
   }
   class HtmlDrawable
   <<interface>> HtmlDrawable
   ShapeDrawer ..> HtmlDrawable: dependes
   HtmlDrawable <.. Circle: dependes
   HtmlDrawable <.. Rectangle: dependes
   HtmlDrawable <.. Triangle: dependes
   class Circle {
       +具体drawHtml()
   }
   class Rectangle {
       +具体drawHtml()
   }
   class Triangle {
       +具体drawHtml()
   }

◇筆者について
アプリケーションデザインを情報構造から考えるということをやっています。これからもアプリケーションデザインについて書いて行こうと思うので興味ある方はフォローなどお願いします。
プロダクト開発仲間欲しい人です。

Discussion