Liskov置換原則と継承設計の堅牢性

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) {}
}

タグ: SOLID Liskov置換原則 オブジェクト指向設計 契約プログラミング 共変性

5月25日 15:50 投稿