イラストで描かれたSOLID原則をコーディングして理解する

スポンサーリンク

はじめに

この記事は、イラストで描かれたSOLID原則をC#でコーディングして理解するための記事です。既に見たことがあるかと思いますが、以下のサイトではSOLID原則をイラストを使って説明しています。このサイトの和訳元はこちらです。

イラストで理解するSOLID原則 - Qiita
本記事は、掲載元で31K「いいね」を獲得したUgonna Thelma氏による「The S.O.L.I.D Principles in Pictures」(2020年5月18日公開)の和訳を、著者の許可を得て掲載しているものです。 ...

このサイトでは、かわいいロボットがSOLID原則の違反している例と即している例を説明してくれていますが、説明が概念的でよく分かりません。
そこで、このサイトでは、ロボットのイラストをC#でコードに落とし込んでみました。イラストは載せられないので、参考サイトを参照してください。
また、SOLID原則を理解する上で参考にしたサイトも最後に載せておきました。

単一責任の原則(Single Responsibility)

悪い例

1つのクラスがたくさんの役割を持っているため、修正する時にバグの温床になる。

public class Robot
{
    public void Cook() { }
    public void Cut() { }
    public void Paint() { }
    public void Drive() { }
}

良い例

1つのクラスに役割は1つとなることを意識する。

public class Chef
{
    public void Cook() { }
}

public class Gardener
{
    public void Cut() { }
}

public class Painter
{
    public void Paint() { }
}

public class Driver
{
    public void Drive() { }
}

オープン・クローズドの原則(Open-Closed)

前提として、Gardenerがロボットを所持していて、枝を切るときにはそのロボットに切らせるようにしているとします。
そして次に、Painterにロボットを販売するためにCut処理を削除して、Paint処理を追加しようとしています。

悪い例

Cutメソッドを削除すると、このメソッドを呼んでいる箇所でエラーが起きてしまう。

public class Robot
{
    //public void Cut() { }
    public void Paint() { }
}

public class Gardener
{
    private Robot _robot = new Robot();
    public void Cut()
    {
        //ここでエラーが起きる
        _robot.Cut();
    }
}

良い例

最初からICutインターフェイスを使って実装をし、機能を追加する場合はインターフェイスを追加するようにする。

public class Robot : ICut, IPaint
{
    //Cutメソッドは削除しなくて良い
    public void Cut() { }
    public void Paint() { }
}

public interface ICut
{
    void Cut();
}

public interface IPaint
{
    void Paint();
}

public class Gardener
{
    private ICut _robot = new Robot();
    public void Cut()
    {
        //エラーは起きない
        _robot.Cut();
    }
}

public class Painter
{
    private IPaint _robot = new Robot();
    public void Cut()
    {
        _robot.Paint();
    }
}

リスコフの置換原則(Liskov Substitution)

これが一番コードに落とし込むのが難しかったです。そもそも普通にクラスの継承をしていればエラーは起こせないので無理やり下のようにエラーが起きるように書きました。
MakeCoffeeメソッドの戻り値の方がDrinkなのが良くないと思います。何か他に良い例があれば教えてください。

悪い例

子クラスでオーバーライドしたメソッドを好き勝手振る舞いを変えたせいでエラーが起きてしまう。

public class Sam
{
    public virtual Drink MakeCoffee()
    {
        return new Coffee();
    }
}

public class Eden : Sam
{
    public override Drink MakeCoffee()
    {
        return new Water();
    }
}

public class User
{
    //Samは不在
    //private Sam _robot = new Sam();
    private Eden _robot = new Eden();
    public Coffee OrderCoffee()
    {
        //ここでエラーが起きる
        var coffee = (Coffee)_robot.MakeCoffee();
        return coffee;
    }
}

public abstract class Drink
{
    
}

public class Coffee : Drink
{
    
}

public class Water : Drink
{
    
}

良い例

public class Sam
{
    public virtual Drink MakeCoffee()
    {
        return new Coffee();
    }
}

public class Eden : Sam
{
    public override Drink MakeCoffee()
    {
        return new Cappuccino();
    }
}

public class User
{
    //Samは不在
    //private Sam _robot = new Sam();
    private Eden _robot = new Eden();
    public Coffee OrderCoffee()
    {
        //エラーは起きない
        var coffee = (Coffee)_robot.MakeCoffee();
        return coffee;
    }
}

public abstract class Drink
{
    
}

public class Coffee : Drink
{
    
}

public class Cappuccino : Coffee
{
    
}

インターフェイス分離の原則(Interface-Segregation)

悪い例

インターフェイスにメソッドを定義し過ぎている。
Robot2はアンテナを持っていないため空のメソッドを定義することになってしまっている。

public interface IRobot
{
    void SpinAround();
    void RotateArms();
    void WiggleAntennas();
}

public class Robot1 : IRobot
{
    public void SpinAround()
    {
        Console.WriteLine("SpinAround");
    }
    public void RotateArms()
    {
        Console.WriteLine("RotateArms");
    }
    public void WiggleAntennas()
    {
        Console.WriteLine("WiggleAntennas");
    }
}

public class Robot2 : IRobot
{
    public void SpinAround()
    {
        Console.WriteLine("SpinAround");
    }
    public void RotateArms()
    {
        Console.WriteLine("RotateArms");
    }
    public void WiggleAntennas()
    {
        // I don't have antennas.
    }
}

良い例

public interface ISpinAround
{
    void SpinAround();
}
public interface IRotateArms
{
    void RotateArms();
}
public interface IWiggleAntennas
{
    void WiggleAntennas();
}

public class Robot1 : ISpinAround, IRotateArms, IWiggleAntennas
{
    public void SpinAround()
    {
        Console.WriteLine("SpinAround");
    }
    public void RotateArms()
    {
        Console.WriteLine("RotateArms");
    }
    public void WiggleAntennas()
    {
        Console.WriteLine("WiggleAntennas");
    }
}

public class Robot2 : ISpinAround, IRotateArms
{
    public void SpinAround()
    {
        Console.WriteLine("SpinAround");
    }
    public void RotateArms()
    {
        Console.WriteLine("RotateArms");
    }
    //出来ないことは定義しない
    //public void WiggleAntennas() { }
}

依存性逆転の原則(Dependency-Inversion)

インスタンス化で他のクラスを呼び出すときは、呼び出すクラスが呼び出されるクラスに依存してはいけないということです。どちらのクラスもインターフェイスをもとに実装するべきだということです。
また、インターフェイスはより抽象的に表現し、メソッドを実装するクラスがインターフェイスに寄り添うように実装しようということです。

悪い例

PizzaCuuterArmが変更されたらRobotも変更しないといけない。

public class Robot
{
    public Object Cut(Object @object)
    {
        var pizzaCutterArm = new PizzaCutterArm();
        return pizzaCutterArm.Cut(@object);
    }
}

public class PizzaCutterArm
{
    public Object Cut(Object @object)
    {
        @object.IsCut = true;
        return @object;
    }
}

public abstract class Object
{
    public bool IsCut { get; set; }
}

public class Pizza : Object
{

}

良い例

ロボットの切るためのツールの変更は、UserのSetCutToolで簡単に行えるようになりました。
ここではDependency Injection(依存性の注入)を使っています。

using Microsoft.Extensions.DependencyInjection;

public class User
{
    public void SetCutTool()
    {
        //Dependency Injection
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<ICut, Knife>();
        Provider.Build(serviceCollection);
    }
}

public static class Provider
{
    public static ServiceProvider ServiceProvider { get; set; }
    public static void Build(ServiceCollection serviceCollection)
    {
        ServiceProvider = serviceCollection.BuildServiceProvider();
    }
}

public class Robots
{
    private ICut _tool = Provider.ServiceProvider.GetService<ICut>();
    public Object Cut(Object @object)
    {
        return _tool.Cut(@object);
    }
}

public class PizzaCutterArm : ICut
{
    public Object Cut(Object @object)
    {
        @object.IsCut = true;
        return @object;
    }
}

public class Knife : ICut
{
    public Object Cut(Object @object)
    {
        @object.IsCut = true;
        return @object;
    }
}

public abstract class Object
{
    public bool IsCut { get; set; }
}

public class Pizza : Object
{

}

public interface ICut
{
    Object Cut(Object @object);
}

参考サイト

The S.O.L.I.D Principles in Pictures

イラストで理解するSOLID原則の原文です。

The S.O.L.I.D Principles in Pictures
If you are familiar with Object-Oriented Programming, then you’ve probably heard about the SOLID principles.

イラストで理解するSOLID原則

『The S.O.L.I.D Principles in Pictures』を和訳したものです。
ロボットのイラストはこのサイトを見ると良いでしょう。

イラストで理解するSOLID原則 - Qiita
本記事は、掲載元で31K「いいね」を獲得したUgonna Thelma氏による「The S.O.L.I.D Principles in Pictures」(2020年5月18日公開)の和訳を、著者の許可を得て掲載しているものです。 ...

SOLID原則を勉強する

SOLID原則のコーディング例があるので分かりやすいです。
参考文献が豊富に載っているのもおすすめなポイントです。

SOLID原則を勉強する その1~単一責任の原則 (SRP)~ - Qiita
目次 SOLID原則を勉強する その1~単一責任の原則 (SRP)~ ←いまここ SOLID原則を勉強する その2~オープン・クローズド原則(OCP)~ SOLID原則を勉強する その3~リスコフの置換原則(LSP)~ SOL...

SOLIDについて

SOLID原則を一言で表現しています。とても簡潔で頭の中が整理されました。

SOLIDについて - Qiita
SOLIDって? OODの設計で心がけること。 以下、SOLIDってつまりこういうことでしょって自己解釈。 Single Responsibility 1クラス、1つの役割、多すぎると抽象化しにくい。 Open Clo...

コメント

タイトルとURLをコピーしました