リスコフの置換原則(SOLIDの「L」)を、原則のすべての側面を簡略化した方法でカバーする優れたC#の例で説明できますか?それが本当に可能なら。
3 に答える
(この回答は2013年5月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
より明確にするために、いくつかの部分を言い換えました。
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つのデータアクセスクラスが設定されたら、次にクライアントに注意を向けます。Businessレイヤーには、顧客の詳細をクライアントに返す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を理解するための実用的なアプローチが得られることを願っています。
リスコフの置換原則を適用するためのコードは次のとおりです。
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は次のように述べています。「派生クラスは基本クラス(またはインターフェイス)の代わりに使用できる必要があります」および「基本クラス(またはインターフェイス)への参照を使用するメソッドは、派生クラスのメソッドを、それについて知らずに、または詳細を知らなくても使用できる必要があります。 。」