Liskov置換原則の基本概念
Liskov置換原則(LSP)は継承階層構築のための指針です。この原則に従うことで、クライアントコードは基底クラスと派生クラスを相互置換可能に使用でき、期待される動作が保証されます。
正式定義
Barbara Liskovによる定義:「SがTの派生型である場合、T型のオブジェクトはすべてS型オブジェクトで置換可能であり、プログラムの整合性を損なわない」
LSP実装の三要素:
- 基底型: クライアントが参照する基本型
- 派生型: 基底型から継承したクラス群
- コンテキスト: クライアントと派生型の相互作用
契約規則と変形規則
契約規則
- 派生型は事前条件を強化できない
- 派生型は事後条件を弱化できない
- 派生型は不変条件を維持しなければならない
変形規則
- メソッド引数は反変性を保持
- 戻り値は共変性を保持
- 新規例外を導入禁止
契約プログラミング実装
事前条件の実装例
public decimal ComputePrice(
float itemWeight,
Dimensions itemSize,
Region deliveryRegion)
{
if (itemWeight <= 0f)
throw new ArgumentOutOfRangeException(nameof(itemWeight),
"重量は正の値でなければなりません");
if (itemSize.Width <= 0f || itemSize.Height <= 0f)
throw new ArgumentOutOfRangeException(nameof(itemSize),
"寸法は正の値でなければなりません");
return CalculateFinalPrice();
}
事後条件の実装例
public virtual decimal ComputePrice(...)
{
// 事前条件チェック
decimal finalPrice = CalculateFinalPrice();
if(finalPrice <= decimal.Zero)
throw new InvalidOperationException("計算結果が無効です");
return finalPrice;
}
不変条件の実装例
public class PricingModel
{
private decimal baseRate;
public decimal BaseRate
{
get => baseRate;
set
{
if(value <= 0m)
throw new ArgumentOutOfRangeException(nameof(value),
"基本料金は正の値でなければなりません");
baseRate = value;
}
}
}
LSP契約規則の適用
事前条件強化の禁止例
public class GlobalPricing : PricingModel
{
public override decimal ComputePrice(..., Region deliveryRegion)
{
// 基底クラスより強い条件(違反事例)
if(deliveryRegion == null)
throw new ArgumentNullException(nameof(deliveryRegion));
return base.ComputePrice(...);
}
}
事後条件弱化の禁止例
public class LocalPricing : PricingModel
{
public override decimal ComputePrice(...)
{
decimal price = base.ComputePrice(...);
// 地域判定で価格変更(事後条件弱化の可能性)
if(deliveryRegion.IsLocal)
price = decimal.Zero;
return price;
}
}
変性規則の実践
共変性の適用例
public interface IRepository<out T>
{
T GetById(Guid id);
}
public class UserRepository : IRepository<User>
{
public User GetById(Guid id) => new User();
}
反変性の適用例
public interface IComparator<in T>
{
bool Compare(T item1, T item2);
}
public class EntityComparator : IComparator<Entity>
{
public bool Compare(Entity a, Entity b) => a.Id == b.Id;
}
例外処理の原則
public class CustomException : DomainException
{
public CustomException(string message) : base(message) {}
}