Liskov Substitution Principleを良いC#の例で説明できますか?[閉まっている]


91

Liskov置換原理(SOLIDの「L」)を、原理のすべての側面を簡単にカバーする優れたC#の例で説明できますか?それが本当に可能であれば。


9
簡単に言うと、LSPに従うと、コード内のオブジェクトをMockオブジェクトに置き換えることができ、置換を考慮して、呼び出しコードの何も調整または変更する必要がなくなります。LSPは、Test by Mockパターンの基本的なサポートです。
kmote 2014年

回答:


128

(この回答は2013-05-13に書き直されました。コメントの下にある説明をお読みください)

LSPは基本クラスの契約に従うことです。

たとえば、基本クラスを使用するものはそれを期待しないので、サブクラスで新しい例外をスローしないようにすることができます。ArgumentNullException引数が欠落している場合に基本クラスがスローし、サブクラスが引数をnullにすることを許可する場合も同様で、LSP違反となります。

LSPに違反するクラス構造の例を次に示します。

public interface IDuck
{
   void Swim();
   // contract says that IsSwimming should be true if Swim has been called.
   bool IsSwimming { get; }
}

public class OrganicDuck : IDuck
{
   public void Swim()
   {
      //do something to swim
   }

   bool IsSwimming { get { /* return if the duck is swimming */ } }
}

public class ElectricDuck : IDuck
{
   bool _isSwimming;

   public void Swim()
   {
      if (!IsTurnedOn)
        return;

      _isSwimming = true;
      //swim logic            
   }

   bool IsSwimming { get { return _isSwimming; } }
}

そして呼び出しコード

void MakeDuckSwim(IDuck duck)
{
    duck.Swim();
}

ご覧のとおり、アヒルの例が2つあります。1つの有機アヒルと1つの電気アヒル。電気アヒルは、電源が入っている場合にのみ泳ぐことができます。これは、LSPの原則に違反します。これは、IsSwimming(これもコントラクトの一部です)が基本クラスのように設定されないため、泳げるようにするためにオンにする必要があるためです。

もちろん、このようなことをすることで解決できます

void MakeDuckSwim(IDuck duck)
{
    if (duck is ElectricDuck)
        ((ElectricDuck)duck).TurnOn();
    duck.Swim();
}

しかし、それはOpen / Closedの原則に違反し、どこにでも実装する必要があります(したがって、まだ不安定なコードが生成されます)。

適切な解決策は、Swimメソッドでアヒルを自動的にオンにすることです。そうすることで、電気アヒルがIDuckインターフェイスで定義されたとおりに動作するようになります。

更新

誰かがコメントを追加して削除しました。それは私が対処したい有効なポイントを持っていました:

Swimメソッド内でアヒルをオンにするソリューションは、実際の実装で作業するときに副作用が発生する可能性があります(ElectricDuck)。しかし、それは明示的なインターフェース実装を使用することで解決できます。インターフェースSwimを使用するときに泳ぐことが期待されているので、オンにしないと問題が発生する可能性が高くなりIDuckます

アップデート2

わかりやすくするために一部を言い換えました。


1
@jgauffin:例はシンプルで明確です。しかし、あなたが提案する解決策は、最初に:オープン-クローズドの原則を破り、それは次のように書いているボブおじさんの定義(彼の記事の結論部分を参照)に適合しません:「リスコフ代替原則(別名契約による設計)は重要な機能ですオープンクローズドの原則に準拠するすべてのプログラムの。」参照:objectmentor.com/resources/articles/lsp.pdf
pencilCake

1
ソリューションがどのようにオープン/クローズを解除するのかわかりません。そのif duck is ElectricDuck部分について言及している場合は、もう一度私の答えを読んでください。私は先週の木曜日にSOLIDについてのセミナーをしました:)
jgauffin

あまり話題にはなりませんが、型チェックを2回実行しないように例を変更していただけませんか?多くの開発者はこのasキーワードに気づいていないため、実際には多くの型チェックからそれらを節約できます。私は、次のようなものを考えている:if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
Siewers

3
@jgauffin-例に少し混乱しています。ダスコとエレクトリックダックはどちらもIDuckから派生しており、IDuckが使用されている場所ならどこでもElectricDuckまたはDuckを配置できるため、リスコフ代替原則はこの場合でも有効であると思いました。ElectricDuckがアヒルが泳ぐ前にをオンにする必要がある場合、ElectricDuckまたはElectricDuckをインスタンス化してIsTurnedOnプロパティをtrueに設定するコードの責任ではありません。これがLSPに違反している場合、すべてのインターフェイスにそのメソッドの異なるロジックが含まれているため、LSVを遵守するのは非常に難しいようです。
Xaisoft

1
@MystereMan:imho LSPはすべての行動の正確さに関するものです。四角形/四角形の例では、設定されている他のプロパティの副作用が得られます。アヒルを使用すると、泳げないという副作用が発生します。LSP:if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).
jgauffin 2013年

8

LSPは実用的なアプローチ

LSPのC#の例を探しているところはどこでも、人々は架空のクラスとインターフェースを使用しています。これは、私たちのシステムの1つに実装したLSPの実際の実装です。

シナリオ:顧客データを提供する3つのデータベース(住宅ローンの顧客、当座預金の顧客、および普通預金口座の顧客)があり、特定の顧客の姓の顧客詳細が必要であるとします。これで、名字に対してこれら3つのデータベースから複数の顧客の詳細を取得できます。

実装:

ビジネスモデルレイヤー:

public class Customer
{
    // customer detail properties...
}

データアクセスレイヤー:

public interface IDataAccess
{
    Customer GetDetails(string lastName);
}

上記のインターフェースは抽象クラスによって実装されます

public abstract class BaseDataAccess : IDataAccess
{
    /// <summary> Enterprise library data block Database object. </summary>
    public Database Database;


    public Customer GetDetails(string lastName)
    {
        // use the database object to call the stored procedure to retrieve the customer details
    }
}

この抽象クラスには、3つのデータベースすべてに共通のメソッド「GetDetails」があり、以下に示すように各データベースクラスによって拡張されています。

住宅ローンの顧客データへのアクセス:

public class MortgageCustomerDataAccess : BaseDataAccess
{
    public MortgageCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetMortgageCustomerDatabase();
    }
}

現在のアカウントの顧客データへのアクセス:

public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
    public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetCurrentAccountCustomerDatabase();
    }
}

節約アカウントの顧客データへのアクセス:

public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
    public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
    {
        this.Database = factory.GetSavingsAccountCustomerDatabase();
    }
}

これらの3つのデータアクセスクラスが設定されたら、クライアントに注意を向けます。ビジネス層には、顧客の詳細をクライアントに返すCustomerServiceManagerクラスがあります。

事業層:

public class CustomerServiceManager : ICustomerServiceManager, BaseServiceManager
{
   public IEnumerable<Customer> GetCustomerDetails(string lastName)
   {
        IEnumerable<IDataAccess> dataAccess = new List<IDataAccess>()
        {
            new MortgageCustomerDataAccess(new DatabaseFactory()), 
            new CurrentAccountCustomerDataAccess(new DatabaseFactory()),
            new SavingsAccountCustomerDataAccess(new DatabaseFactory())
        };

        IList<Customer> customers = new List<Customer>();

       foreach (IDataAccess nextDataAccess in dataAccess)
       {
            Customer customerDetail = nextDataAccess.GetDetails(lastName);
            customers.Add(customerDetail);
       }

        return customers;
   }
}

依存関係の注入は、既に複雑になっているため、単純にするために示していません。

新しい顧客詳細データベースがある場合は、BaseDataAccessを拡張し、そのデータベースオブジェクトを提供する新しいクラスを追加するだけです。

もちろん、参加するすべてのデータベースに同一のストアドプロシージャが必要です。

最後に、クライアント CustomerServiceManagerクラス GetCustomerDetailsメソッドのみを呼び出し、lastNameを渡します。データがどこからどのように取得されるかを気にする必要はありません。

これがLSPを理解するための実用的なアプローチになることを願っています。


3
これはLSPの例ですか?
somegeek

1
LSPの例もその中にはありません...なぜそれが非常に多くの賛成票を持っているのですか?
StaNov 2017年

1
@RoshanGhangare IDataAccessには、ビジネスレイヤーで置き換えることができる3つの具体的な実装があります。
Yawar Murtaza 2017年

1
@YawarMurtazaあなたが引用した例は何でも、それだけの戦略パターンの典型的な実装です。LSPを壊している場所と、LSPの違反をどのように解決するかを明確にしていただけますか
Yogesh

0

Liskov Substitute Principleを適用するためのコードは次のとおりです。

public abstract class Fruit
{
    public abstract string GetColor();
}

public class Orange : Fruit
{
    public override string GetColor()
    {
        return "Orange Color";
    }
}

public class Apple : Fruit
{
    public override string GetColor()
    {
        return "Red color";
    }
}

class Program
{
    static void Main(string[] args)
    {
        Fruit fruit = new Orange();

        Console.WriteLine(fruit.GetColor());

        fruit = new Apple();

        Console.WriteLine(fruit.GetColor());
    }
}

LSVの状態:「派生クラスは、その基本クラス(またはインターフェース)の代わりに使用できる」&「基本クラス(またはインターフェース)への参照を使用するメソッドは、それについて知らないか、詳細を知らなくても、派生クラスのメソッドを使用できる必要があります」

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.