क्या आप Liskov प्रतिस्थापन सिद्धांत को एक अच्छे C # उदाहरण के साथ समझा सकते हैं? [बन्द है]


91

क्या आप लिसकोव प्रतिस्थापन सिद्धांत (SOLID का 'L') एक अच्छे C # उदाहरण के साथ सिद्धांत के सभी पहलुओं को सरलीकृत तरीके से कवर कर सकते हैं? यदि यह वास्तव में संभव है।


9
संक्षेप में इसके बारे में सोचने का एक सरल तरीका है: यदि मैं एलएसपी का पालन करता हूं, तो मैं अपने कोड में किसी भी वस्तु को एक मॉक ऑब्जेक्ट के साथ बदल सकता हूं, और प्रतिस्थापन के लिए कॉलिंग कोड में कुछ भी समायोजित या बदलने की आवश्यकता नहीं होगी। LSP मॉक पैटर्न द्वारा टेस्ट के लिए एक मौलिक समर्थन है।
kmote

जवाबों:


128

(यह उत्तर 2013-05-13 को फिर से लिखा गया है, टिप्पणियों के तल में चर्चा पढ़ें)

एलएसपी बेस क्लास के अनुबंध का पालन करने के बारे में है।

उदाहरण के लिए, आप उप-वर्गों में नए अपवादों को नहीं फेंक सकते क्योंकि आधार वर्ग का उपयोग करने वाले को यह उम्मीद नहीं होगी। ArgumentNullExceptionयदि कोई तर्क गुम है तो बेस क्लास फेंकता है और उप वर्ग तर्क को शून्य होने की अनुमति देता है, तो एलएसपी उल्लंघन भी करता है।

यहाँ एक वर्ग संरचना का एक उदाहरण है जो एलएसपी का उल्लंघन करता है:

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();
}

जैसा कि आप देख सकते हैं, बतख के दो उदाहरण हैं। एक ऑर्गेनिक डक और एक इलेक्ट्रिक डक। इलेक्ट्रिक डक केवल तभी तैर सकता है जब वह चालू हो। यह एलएसपी सिद्धांत को तोड़ता है क्योंकि इसे चालू करने में सक्षम होना चाहिए क्योंकि IsSwimmingआधार वर्ग के रूप में (जो अनुबंध का हिस्सा भी है) सेट नहीं किया जाएगा।

आप निश्चित रूप से कुछ ऐसा करके इसे हल कर सकते हैं

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

लेकिन यह ओपन / बंद सिद्धांत को तोड़ देगा और इसे हर जगह लागू किया जाएगा (और यह अभी भी अस्थिर कोड उत्पन्न करता है)।

उचित समाधान Swimविधि में बत्तख को स्वचालित रूप से चालू करने के लिए होगा और ऐसा करने से विद्युत बत्तख ठीक वैसा ही व्यवहार करेगा जैसा कि IDuckइंटरफ़ेस द्वारा परिभाषित किया गया है

अपडेट करें

किसी ने एक टिप्पणी जोड़ी और उसे हटा दिया। यह एक वैध बिंदु था जिसे मैं संबोधित करना चाहता हूं:

Swimविधि के अंदर बत्तख को चालू करने के साथ समाधान वास्तविक कार्यान्वयन ( ElectricDuck) के साथ काम करते समय दुष्प्रभाव हो सकता है । लेकिन यह एक स्पष्ट इंटरफ़ेस कार्यान्वयन का उपयोग करके हल किया जा सकता है । imho यह अधिक संभावना है कि आपको इसे चालू नहीं करने से समस्याएं आती हैं Swimक्योंकि यह उम्मीद है कि IDuckइंटरफ़ेस का उपयोग करते समय यह तैर जाएगा

अपडेट २

इसे और अधिक स्पष्ट करने के लिए कुछ हिस्सों को फिर से तैयार किया।


1
@jgauffin: उदाहरण सरल और स्पष्ट है। लेकिन जो समाधान आप प्रस्तावित करते हैं, पहले: ओपन-क्लोज्ड सिद्धांत को तोड़ता है और यह अंकल बॉब की परिभाषा (उनके लेख का निष्कर्ष भाग देखें) पर फिट नहीं बैठता है जो लिखते हैं: "Liskov प्रतिस्थापन सिद्धांत (अनुबंध द्वारा AKA डिजाइन) एक महत्वपूर्ण विशेषता है सभी प्रोग्राम जो ओपन-क्लोज्ड सिद्धांत के अनुरूप हैं। " देखें: objectmentor.com/resource/articles/lsp.pdf
पेन्सिलके

1
मैं यह नहीं देखता कि समाधान कैसे खुलता है / बंद होता है। यदि आप if duck is ElectricDuckभाग की बात कर रहे हैं तो मेरे उत्तर को फिर से पढ़ें । मैंने पिछले गुरुवार को SOLID के बारे में एक सेमिनार किया :)
jgauffin

वास्तव में विषय पर नहीं, लेकिन क्या आप अपना उदाहरण बदल सकते हैं ताकि आप दो बार टाइप-चेकिंग न करें? बहुत सारे डेवलपर asकीवर्ड के बारे में नहीं जानते हैं , जो वास्तव में उन्हें बहुत प्रकार की जाँच से बचाता है। मैं कुछ इस तरह सोच रहा हूं:if var electricDuck = duck as ElectricDuck; if(electricDuck != null) electricDuck.TurnOn();
SIVers

3
@jgauffin - मैं उदाहरण से थोड़ा भ्रमित हूं। मैंने सोचा कि लिस्कोव सबस्टीट्यूशन सिद्धांत अभी भी इस मामले में मान्य होगा क्योंकि डक और इलेक्ट्रिकडक दोनों ही IDuck से निकलते हैं और आप ElectricDuck या Duck डाल सकते हैं कहीं भी IDuck का उपयोग किया जाता है। अगर ElectricDuck को तैरने से पहले ही चालू करना है, तो क्या ElectricDuck या कुछ कोड की जिम्मेदारी ElectricDuck नहीं है और तब IsTurnedOn प्रॉपर्टी को सही पर सेट करना है। यदि यह एलएसपी का उल्लंघन करता है, तो ऐसा लगता है कि एलएसवी का पालन करना बहुत कठिन होगा क्योंकि सभी इंटरफेस के लिए अलग-अलग तर्क होंगे।
Xaisoft

1
@MystereMan: imho एलएसपी सभी व्यवहारिक शुद्धता के बारे में है। आयत / वर्ग उदाहरण के साथ आपको सेट की जा रही अन्य संपत्ति का दुष्प्रभाव मिलता है। बतख के साथ आपको तैरना नहीं आने का दुष्प्रभाव मिलता है। 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

8

एलएसपी एक व्यावहारिक दृष्टिकोण

हर जगह मैं एलएसपी के सी # उदाहरणों की तलाश करता हूं, लोगों ने काल्पनिक कक्षाओं और इंटरफेस का उपयोग किया है। यहाँ एलएसपी का व्यावहारिक कार्यान्वयन है जिसे मैंने अपने सिस्टम में लागू किया है।

परिदृश्य: मान लीजिए कि हमारे पास 3 डेटाबेस (बंधक ग्राहक, चालू खाता ग्राहक और बचत खाता ग्राहक) हैं जो ग्राहक डेटा प्रदान करते हैं और हमें दिए गए ग्राहक के अंतिम नाम के लिए ग्राहक विवरण की आवश्यकता होती है। अब हम दिए गए अंतिम नाम के खिलाफ उन 3 डेटाबेस से 1 से अधिक ग्राहक विवरण प्राप्त कर सकते हैं।

कार्यान्वयन:

बिजनेस मॉडल लाइनर:

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 डेटाबेसों के लिए एक सामान्य विधि "गेटडेलेट्स" है जो कि नीचे दिखाए गए डेटाबेस वर्गों में से प्रत्येक द्वारा बढ़ाया गया है

पोर्ट्रेट ग्राहक डेटा पहुंच:

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;
   }
}

मैंने इसे पहले से जटिल होने के कारण इसे सरल रखने के लिए निर्भरता इंजेक्शन नहीं दिखाया है।

अब अगर हमारे पास एक नया ग्राहक विवरण डेटाबेस है, तो हम बस एक नया वर्ग जोड़ सकते हैं जो कि बेसडैटएक्सेस को बढ़ाता है और इसका डेटाबेस ऑब्जेक्ट प्रदान करता है।

बेशक हम सभी भाग लेने वाले डेटाबेस में समान संग्रहीत प्रक्रियाओं की आवश्यकता है।

अंत में, CustomerServiceManagerक्लास के लिए क्लाइंट केवल गेटकेंटरडेल विधि को कॉल करेगा, अंतिम नाम पास करेगा और यह ध्यान नहीं रखना चाहिए कि डेटा कैसे और कहां से आ रहा है।

आशा है कि यह आपको एलएसपी को समझने के लिए एक व्यावहारिक दृष्टिकोण देगा।


3
यह एलएसपी का उदाहरण कैसे हो सकता है?
somegeek

1
मुझे एलएसपी का उदाहरण दिखाई नहीं दे रहा है या तो ... इसमें इतने सारे बदलाव क्यों हैं?
स्टैनोव

1
@ रोशनगंगारे IDataAccess में 3 ठोस कार्यान्वयन हैं जिन्हें बिजनेस लेयर में प्रतिस्थापित किया जा सकता है।
यवर मुर्तजा

1
@YawarMurtaza जो भी उदाहरण आपने उद्धृत किया है, वह रणनीति पैटर्न का विशिष्ट कार्यान्वयन है। क्या आप स्पष्ट कर सकते हैं कि यह एलएसपी को कहां तोड़ रहा है और आप एलएसपी के उल्लंघन को कैसे हल करते हैं
योगेश

0

यहाँ Liskov सब्स्टीट्यूट सिद्धांत को लागू करने के लिए कोड है।

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());
    }
}

एलएसवी में कहा गया है: "व्युत्पन्न वर्गों को उनके आधार वर्गों (या इंटरफेस) के लिए प्रतिस्थापित किया जाना चाहिए" और "आधार कक्षाओं के संदर्भ (या इंटरफेस) का उपयोग करने वाले तरीकों को इसके बारे में जानने या विवरण के बिना व्युत्पन्न वर्गों के तरीकों का उपयोग करने में सक्षम होना चाहिए। । "

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.