【設計Tips】具体classへ情報を集約しInterface依存に切り替える
*社内で共有している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