Liskov İkame İlkesini (SOLID'in 'L'si), ilkenin tüm yönlerini basitleştirilmiş bir şekilde kapsayan iyi bir C # örneği ile açıklayabilir misiniz? Gerçekten mümkünse.
Liskov İkame İlkesini (SOLID'in 'L'si), ilkenin tüm yönlerini basitleştirilmiş bir şekilde kapsayan iyi bir C # örneği ile açıklayabilir misiniz? Gerçekten mümkünse.
Yanıtlar:
(Bu cevap 2013-05-13 yeniden yazılmıştır, yorumların altındaki tartışmayı okuyun)
LSP, temel sınıfın sözleşmesini takip etmekle ilgilidir.
Örneğin, temel sınıfı kullananların bunu beklemeyeceği için alt sınıflarda yeni istisnalar atamazsınız. ArgumentNullException
Bir bağımsız değişken eksikse ve alt sınıf, bağımsız değişkenin boş olmasına izin veriyorsa, aynı zamanda bir LSP ihlali de temel sınıf atarsa geçerlidir.
LSP'yi ihlal eden bir sınıf yapısı örneği:
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; } }
}
Ve arama kodu
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
Gördüğünüz gibi iki ördek örneği var. Bir organik ördek ve bir elektrikli ördek. Elektrikli ördek ancak açıksa yüzebilir. Bu, IsSwimming
(aynı zamanda sözleşmenin bir parçası olan) temel sınıfta olduğu gibi ayarlanmayacağından yüzebilmek için açılması gerektiğinden LSP ilkesini ihlal eder.
Elbette böyle bir şey yaparak çözebilirsiniz.
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
Ancak bu, Açık / Kapalı ilkesini ihlal eder ve her yerde uygulanması gerekir (ve bu nedenle hala kararsız kod üretir).
Uygun çözüm, Swim
yöntemde ördeği otomatik olarak açmak ve bunu yaparak elektrikli ördeğin tam olarak IDuck
arayüz tarafından tanımlandığı gibi davranmasını sağlamak olacaktır.
Güncelleme
Birisi bir yorum ekledi ve onu kaldırdı. Ele almak istediğim geçerli bir nokta vardı:
Swim
Yöntemin içindeki ördeği açmanın çözümü , gerçek uygulama ( ElectricDuck
) ile çalışırken yan etkilere neden olabilir . Ancak bu, açık bir arayüz uygulaması kullanılarak çözülebilir . imho Swim
, IDuck
arayüzü kullanırken yüzmesi beklendiğinden, onu AÇMADAN sorun yaşamanız daha olasıdır.
Güncelleme 2
Daha net hale getirmek için bazı kısımları yeniden basıldı.
if duck is ElectricDuck
. Ben :) KATI geçen Perşembe hakkında bir seminer vardı
as
anahtar kelimenin farkında değildir , bu da onları birçok tür kontrolünden kurtarır. Aşağıdakine benzer bir şey düşünüyorum:if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
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).
LSP Pratik Bir Yaklaşım
LSP'nin C # örneklerini aradığım her yerde, insanlar hayali sınıfları ve arayüzleri kullandılar. İşte sistemlerimizden birinde uyguladığım LSP'nin pratik uygulaması.
Senaryo: Müşteri verilerini sağlayan 3 veritabanımız (Mortgage Müşterileri, Cari Hesaplar Müşterileri ve Tasarruf Hesabı Müşterileri) olduğunu ve verilen müşterinin soyadı için müşteri ayrıntılarına ihtiyacımız olduğunu varsayalım. Şimdi bu 3 veritabanından soyadına göre 1'den fazla müşteri ayrıntısı alabiliriz.
Uygulama:
İŞ MODELİ KATMANI:
public class Customer
{
// customer detail properties...
}
VERİ ERİŞİM KATMANI:
public interface IDataAccess
{
Customer GetDetails(string lastName);
}
Yukarıdaki arayüz soyut sınıf tarafından uygulanmaktadır
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
}
}
Bu soyut sınıf, aşağıda gösterildiği gibi veritabanı sınıflarının her biri tarafından genişletilen 3 veritabanının tümü için ortak bir "GetDetails" yöntemine sahiptir.
MORTGAGE MÜŞTERİ VERİ ERİŞİMİ:
public class MortgageCustomerDataAccess : BaseDataAccess
{
public MortgageCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetMortgageCustomerDatabase();
}
}
CARİ HESAP MÜŞTERİ VERİ ERİŞİMİ:
public class CurrentAccountCustomerDataAccess : BaseDataAccess
{
public CurrentAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetCurrentAccountCustomerDatabase();
}
}
TASARRUF HESABI MÜŞTERİ VERİ ERİŞİMİ:
public class SavingsAccountCustomerDataAccess : BaseDataAccess
{
public SavingsAccountCustomerDataAccess(IDatabaseFactory factory)
{
this.Database = factory.GetSavingsAccountCustomerDatabase();
}
}
Bu 3 veri erişim sınıfı belirlendikten sonra, şimdi dikkatimizi müşteriye çekiyoruz. İş katmanında, müşteri ayrıntılarını müşterilerine döndüren CustomerServiceManager sınıfımız var.
İŞ KATMANI:
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;
}
}
Şimdi zaten karmaşık hale geldiğinden, basit tutmak için bağımlılık enjeksiyonunu göstermedim.
Şimdi, yeni bir müşteri detay veritabanımız varsa, BaseDataAccess'i genişleten ve veritabanı nesnesini sağlayan yeni bir sınıf ekleyebiliriz.
Elbette, katılan tüm veritabanlarında aynı depolanan prosedürlere ihtiyacımız var.
Son olarak, CustomerServiceManager
sınıf istemcisi yalnızca GetCustomerDetails yöntemini çağırır, lastName'i iletir ve verilerin nasıl ve nereden geldiği ile ilgilenmemelidir.
Umarım bu size LSP'yi anlamak için pratik bir yaklaşım sunar.
İşte Liskov İkame İlkesini uygulama kodu.
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 şunu belirtir: "Türetilmiş sınıflar, temel sınıfları (veya arayüzleri) için ikame edilebilir olmalıdır" & "Temel sınıflara (veya arayüzlere) referanslar kullanan yöntemler, bunları bilmeden veya ayrıntıları bilmeden türetilmiş sınıfların yöntemlerini kullanabilmelidir. . "