#あすみかんの上にあすみかん

#たのしいことしかかかないことをここに決意します

「失敗例から学ぶSOLID原則」デメテルの法則の違反を解消する【完結編?】

今までのあらすじ

speakerdeck.com

asumikam.com

スライド中の解法では、ストーリーを進めた時にまだつらくなりそうな雰囲気があることがわかりました。

ブログ中では、これは「単一責任の原則」に違反しているため、「責任」を分けていきました。 「使われるクラス」側はうまくいっていそうですが、「使うクラス」側(ここではUseCase)がデメテルの法則に反していそうなことがわかりました。

これを解消していくぞ!完結編!!!

前提として「デメテルの法則に反しているから全てダメ」という訳ではない

この「せっかく隠蔽されているはずの詳細の深掘り」は、層の異なるアーキテクチャ要素の密結合を招く要因になります。デメテルの法則はgetterで得たオブジェクトのメソッドを呼ぶコードを気軽に書かないよう戒めるために、メソッドチェーンを禁止事項に挙げています。

(ちょうぜつソフトウェア設計入門 3-2 カプセル化

これを単に「メソッドチェーンのほうが便利だからデメテルの法則は時代遅れ」と考えるのは誤解です。その意図はあくまで、「カプセルの中をほじくり出すと設計の意味がなくなる」ということ、さらには、「そのようにする必要があるオブジェクトをそもそも設計するな」ということです。

(ちょうぜつソフトウェア設計入門 3-2 カプセル化

1番大事なのは「そのメソッドチェーン、色々ほじくり出していない?」というところであり、メソッドチェーン自体の禁止でないところを気を付ける必要がありそうです。

思いがけずメソッドチェーンを使用している時は、一度立ち止まって設計を見直す機会、とすると良さそうです。

今回のUseCaseを直していく

問題点がどこかを考えてみる

<?php

/**
 * @param MyItem $myItem
 * @param EcSite|VariationConvertable[] $ecSiteList
 * @return void
 */
public function uploadVariationsUseCase(MyItem $myItem, array $ecSiteList): void
{
    foreach ($ecSiteList as $ecSite) {
        $ecVariation = $ecSite->getVariationConverter()->convertVariation($myItem);

        $ecSite->getUploader()->upload($ecVariation);
    }
}

もろに「getterでもらってきて無理やり聞いてる感」「ほじくり出している感」がありそうです😨

UseCaseが本当にしたいのは「バリエーションの変換」「アップロードすること」なのですが、その手前に「くーださい!(getVariationConvertergetUploader)」が挟まっています。

「Tell,Don't Ask」を守れていません。

解消してみる

<?php
class VariationConvertService {
    public function exec(MyItem $myItem, VariationConverter $converter)
    {
        $converter->convert($myItem);
    }
}

class UploadService {
    public function exec(MyItem $myItem, Uploader $converter)
    {
        $converter->upload($myItem);
    }
}

class UseCase {
    public function __construct(
        private readonly ConvertService $convertService,
        private readonly UploadService $uploadService,
    ) {
    }

    public function uploadVariationsUseCase(MyItem $myItem, array $ecSites)
    {
        $myItem = [];

        foreach($ecSites as $ecSite) {
            $ecVariation = $variationService->exec($myItem, $ecSite->getVariationConverter());

            $uploaderService->exec($ecVariation, $ecSite->getUploader());
        }
    }
}

このように、「何が来るかはわからないけど変換(アップロード)があるやつが来るからそいつを呼ぶ」クラスを作ることでデメテルの法則の解消自体はできていそうです。

これで本当にいいのかな?🧐

デメテルの法則自体は解消できましたが「XXXService」のクラスのやっていることが少ないため、低凝集な感じがします...。 根本的に全ていい感じにしようとしたら、そもそものEcSiteの設計を見直したりした方が良かったりするのかもしれません。

色々考えてみて思うのは、原理・原則に全て反していない綺麗なコードを作るのが1番の理想ですが、やはりそれはめちゃくちゃ難しい。

ただ、「なんかおかしいな?」と思えるきっかけとして原理・原則はめちゃくちゃ役立つので理解していきたいですね。

まとめ

設計めっちゃむずい。

3年後くらいに自分の記事読んで「ここが違う」ってブッ叩けるようになります。