2009-06-03 16 views
9

निम्न नमूना कोड को ध्यान में रखते:सी #/नेट में 'नई' गुणों के पेशेवरों और विपक्ष?

// delivery strategies 
public abstract class DeliveryStrategy { ... } 
public class ParcelDelivery : DeliveryStrategy { ... } 
public class ShippingContainer : DeliveryStrategy { ... } 

और निम्न नमूना आदेश वर्ग:

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

जब मैं आदेश वर्ग के एक नए प्रकार निकाले जाते हैं, यह प्रकार DeliveryStrategy की डिलिवरी संपत्ति का वारिस होगा।

अब, जब यह दिया जाता है कि CustomerOrders ParcelDelivery रणनीति का प्रयोग दिया जाना चाहिए, हम विचार कर सकते हैं 'नई' CustomerOrder कक्षा में प्रसव संपत्ति ing:

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    // 'new' Delivery property 
    public new ParcelDelivery Delivery 
    { 
     get { return base.Delivery as ParcelDelivery; } 
     set { base.Delivery = value; } 
    } 
} 

(CustomerOrder स्पष्ट रूप से करने की जरूरत है सुनिश्चित करें कि ऑर्डर के साथ संगत (पॉलिमॉर्फ) है

यह ग्राहक ऑर्डर पर पार्सल डिलीवरी रणनीति का सीधे उपयोग करने की आवश्यकता के बिना सीधे उपयोग की अनुमति देता है।

क्या आप इस पैटर्न का उपयोग करने पर विचार करेंगे? क्यों नहीं?

अद्यतन: जेनिक्स का उपयोग करने के बजाय, मैं इस पैटर्न के साथ आया, क्योंकि मैं इसे कई गुणों के लिए उपयोग करना चाहता हूं। मैं इन सभी गुणों के लिए सामान्य प्रकार के तर्कों का उपयोग नहीं करना चाहता

+2

जब तक आपकी छायांकन विधि में कोई कठोर पूर्व शर्त नहीं है और कोई कमजोर पोस्टकंडिशन नहीं है, मुझे लगता है कि यह अभ्यास पूरी तरह से स्वीकार्य है। –

+0

पहले उदाहरण में, आपको डिलिवरीस्ट्रेटी को वैसे भी क्यों डालना होगा? रणनीति पैटर्न का उपयोग करने का मुद्दा यह है कि व्यवहार एक इंटरफ़ेस को लागू करते हैं, और इसलिए सभी समान विधियां हैं, इसलिए उन्हें डालने की आवश्यकता नहीं है। –

उत्तर

6

मुझे लगता है कि यह एक अच्छा पैटर्न है।परिणाम को कास्ट करने की आवश्यकता को हटाकर व्युत्पन्न प्रकारों का स्पष्ट रूप से उपयोग करना आसान बनाता है, और यह बेस क्लास व्यवहार को 'तोड़' नहीं देता है।))

  • DbConnection.CreateCommand (एक DbCommand
  • SqlConnection.CreateCommand (रिटर्न का उपयोग कर आधार कार्यान्वयन छुपाता है: वास्तव में, एक समान पैटर्न बीसीएल में कुछ कक्षाओं में प्रयोग किया जाता है, उदाहरण के लिए DbConnection वर्ग पदानुक्रम को देखो एक SqlCommand वापस करने के लिए 'नया'।
  • (अन्य DbConnection कार्यान्वयन भी ऐसा ही)

इसलिए, यदि आप एक DbConnection चर के माध्यम से कनेक्शन वस्तु हेरफेर, CreateCommand एक DbCommand प्राप्त होगा; यदि आप इसे SqlConnection चर के माध्यम से कुशल बनाते हैं, तो CreateCommand एक SqlCommand को वापस कर देगा, अगर आप इसे SqlCommand चर में निर्दिष्ट कर रहे हैं तो कास्ट से परहेज करें।

+0

नेट फ्रेमवर्क से सिमुलेटर मामलों को दिखाने के लिए उत्तर के रूप में स्वीकृत (हालांकि वे SqlConnection में 'नया' का उपयोग नहीं करते हैं!) –

+1

"हालांकि वे SqlConnection में 'नया' का उपयोग नहीं करते हैं: वास्तव में मैं लगभग निश्चित हूं करें: बेस क्लास में समान नाम और पैरामीटर के साथ एक विधि को परिभाषित करने का यह एकमात्र तरीका है, लेकिन एक अलग रिटर्न प्रकार के साथ। मुझे लगता है कि आपने प्रतिबिंबक के साथ जांच की है, लेकिन प्रतिबिंब द्वारा दिखाया गया कोड वास्तविक स्रोत कोड नहीं है: यह केवल आईएल में वास्तव में जेनरेट किया गया दिखा सकता है, और स्पष्ट रूप से 'नया' संशोधक किसी भी आईएल को उत्सर्जित नहीं करता है (या कम से कम परावर्तक नहीं करता है इसे दिखाओ नहीं)। –

+0

स्रोत कोड में नए का उपयोग बस संकलक से आपको चेतावनी से रोकता है कि यह आपके लिए यह कर रहा है (जो पाठ्यक्रम बदल सकता है) – ShuggyCoUk

5

आप जेनिक्स का उपयोग कर सकते हैं।

// order (base) class 
public abstract class Order<TDeliveryStrategy> where TDeliveryStrategy : DeliveryStrategy 
{ 
    private TDeliveryStrategy delivery; 

    protected Order(TDeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public TDeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order<ParcelDelivery> 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 
} 
11

मैं प्रकार सामान्य बनाने के लिए पसंद करते हैं:

public abstract class Order<TDelivery> where TDelivery : Delivery 
{ 
    public TDelivery Delivery { ... } 
    ... 
} 

public class CustomerOrder : Order<ParcelDelivery> 
{ 
    ... 
} 

यह संकलन समय पर प्रकार सुरक्षा सुनिश्चित करता है, बल्कि निष्पादन समय के लिए इसे छोड़ने से। यह स्थिति को भी रोकता है:

CustomerOrder customerOrder = new CustomerOrder(); 
Order order = customerOrder; 
order.Delivery = new NonParcelDelivery(); // Succeeds! 

ParcelDelivery delivery = customerOrder.Delivery; // Returns null 

ओच।

मैं आमतौर पर अंतिम उपाय के रूप में new पर विचार करता हूं। यह कार्यान्वयन और उपयोग के मामले में अतिरिक्त जटिलता पेश करता है।

यदि आप सामान्य मार्ग पर जाना नहीं चाहते हैं, तो मैं वास्तव में एक नई संपत्ति (एक अलग नाम के साथ) पेश करता हूं।

+0

मैं वापसी प्रकारों में contravariance के साथ सी # 4 के लिए इंतजार नहीं कर सकता। – erikkallen

+0

कॉन्वेंरिएंट रिटर्न प्रकार, contravariant पैरामीटर प्रकार :) हालांकि, यह केवल इंटरफेस के लिए है और केवल कुछ परिदृश्यों में जहां तक ​​मुझे पता है ... –

+0

@ जोन: मैंने इस सटीक मामले के लिए संपत्ति सेटर को संरक्षित किया है। आधार वर्ग और व्युत्पन्न वर्ग को इसे खींचने के लिए मिलकर काम करने की आवश्यकता है। –

1

क्या कोई कारण है कि आपको रिटर्न टाइप बदलने की आवश्यकता क्यों है? यदि नहीं है तो मैं सिर्फ प्रसव संपत्ति आभासी बनाने तो यह बजाय विरासत में मिला वर्गों द्वारा परिभाषित किया जाना है सुझाव है: क्यों तुम करोगी

public abstract class Order 
{ 
    protected Order(DeliveryStrategy delivery) 
    { 
     Delivery = delivery; 
    } 

    public virtual DeliveryStrategy Delivery { get; protected set; } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public DeliveryStrategy Delivery { get; protected set; } 
} 

आप वापसी प्रकार में परिवर्तन की आवश्यकता होती है, तो मुझे आश्चर्य है कि होगा वापसी प्रकार पर एक व्यवहार परिवर्तन की कठोर जरूरत है। भले ही, यदि ऐसा है, तो यह आपके लिए काम नहीं करेगा।

तो सीधे अपने प्रश्न का उत्तर देने के लिए, मैं केवल आपके द्वारा वर्णित पैटर्न का उपयोग करता हूं यदि यह आवश्यक है कि रिटर्न प्रकार बेस क्लास से अलग हो, और बहुत कम (मैं अपने ऑब्जेक्ट मॉडल का विश्लेषण करता हूं कि यह देखने के लिए कि क्या है कुछ और मैं पहले कर सकता था)। यदि ऐसा नहीं है, तो मैं ऊपर वर्णित पैटर्न का उपयोग करूंगा।

3

आधार वर्ग से लिखने योग्य गुणों को छिपाने के लिए 'नया' कीवर्ड का उपयोग करना मेरी राय में एक बुरा विचार है। नया कीवर्ड आपको ओवरराइड करने के बजाए व्युत्पन्न कक्षा में बेस क्लास के सदस्य को छिपाने की अनुमति देता है। इसका मतलब है कि बेस-क्लास संदर्भ का उपयोग करने वाले ऐसे सदस्यों को कॉल अभी भी बेस क्लास कोड तक पहुंचते हैं, न कि क्लास कोड व्युत्पन्न। सी # में 'वर्चुअल' कीवर्ड है, जो व्युत्पन्न कक्षाओं को वास्तव में override को कार्यान्वित करने की बजाय, इसे छिपाने की बजाय अनुमति देता है। एक उचित अच्छा लेख here है जो मतभेदों के बारे में बात करता है।

आपके मामले में, ऐसा लगता है कि आप property covariance को सी # में पेश करने के तरीके के रूप में छिपाने के तरीके का उपयोग करने का प्रयास कर रहे हैं। हालांकि, इस दृष्टिकोण के साथ समस्याएं हैं।

अक्सर, बेस क्लास रखने का मूल्य आपके कोड के उपयोगकर्ताओं को पॉलिमॉर्फिक रूप से टाइप करने की अनुमति देना है। यदि कोई बेस क्लास के संदर्भ का उपयोग करके डिलिवरी प्रॉपर्टी सेट करता है तो आपके कार्यान्वयन के साथ क्या होता है? यदि डिलीवरी संपत्ति पार्सल डिलीवरी का उदाहरण नहीं है तो व्युत्पन्न वर्ग तोड़ देगा? कार्यान्वयन की इस पसंद के बारे में खुद से पूछने के लिए ये प्रश्न हैं।

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

जैसा कि कुछ अन्य पदों का पहले से ही उल्लेख किया गया है, आप विभिन्न वितरण संपत्ति प्रकारों को प्राप्त करने के लिए जेनेरिक का उपयोग कर सकते हैं। जॉन का उदाहरण यह प्रदर्शित करने में बहुत अच्छा है। जेनेरिक के दृष्टिकोण के साथ एक समस्या है, यदि आपको ग्राहक ऑर्डर से प्राप्त करने की आवश्यकता है और डिलिवरी संपत्ति को नए प्रकार में बदलना है।

जेनिक्स के लिए एक विकल्प है। आपको यह विचार करने की आवश्यकता है कि क्या आप वास्तव में अपने मामले में एक सेटटेबल संपत्ति चाहते हैं। यदि आप डिलिवरी संपत्ति पर सेटर से छुटकारा पा लेते हैं, तो ऑर्डर क्लास का उपयोग करके पेश किए गए मुद्दे सीधे चले जाते हैं। चूंकि आप कन्स्ट्रक्टर पैरामीटर का उपयोग करके डिलीवरी प्रॉपर्टी सेट करते हैं, इसलिए आप गारंटी दे सकते हैं कि सभी ऑर्डर में सही प्रकार की रणनीति है।

1

इस दृष्टिकोण पर विचार करें:

public interface IOrder 
{ 
    public DeliveryStrategy Delivery 
    { 
     get; 
    } 
} 

// order (base) class 
public abstract class Order : IOrder 
{ 
    protected readonly DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
    } 
} 

तो क्यों आधार वर्ग वितरण रणनीति के बारे में पता करने की जरूरत है का उपयोग

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public ParcelDelivery Delivery 
    { 
     get { return (ParcelDelivery)base.Delivery; } 
    } 


    DeliveryStrategy IOrder.Delivery 
    { 
     get { return base.Delivery} 
    } 
} 

यह अभी भी सही से दूर है (अपने उदाहरण प्रदर्शित नहीं करता है बिलकुल भी और यह एक बाधा के साथ सामान्य होने के लिए और अधिक समझ में आता है लेकिन यह कम से कम आपको संपत्ति के लिए समान नाम का उपयोग करने और प्रकार की सुरक्षा प्राप्त करने की अनुमति देता है।

जैसा कि आपके उदाहरण में व्यर्थ था, अगर कुछ सही प्रकार है तो आपको इसे शून्य से मुखौटा नहीं करना चाहिए क्योंकि आपको अपने आविष्कार का उल्लंघन किया गया है।

केवल पढ़ने योग्य फ़ील्ड हमेशा बेहतर होते हैं जहां संभव हो। वे अपरिवर्तनीयता स्पष्ट करते हैं।

+0

मुझे विकल्प पसंद है जो इंटरफेस ऑफ़र करता है, लेकिन मेरा मानना ​​है कि यह पैटर्न मॉडल को अधिक जटिल बनाता है (उसी वर्ग पर दो डिलीवरी गुण होते हैं)। –

+0

सह/कॉन्ट्रैक्ट भिन्नता की अनुपस्थिति में आपको उपभोक्ता अंत (अलग-अलग नाम/कास्ट) या प्रदाता (दो गुण या तो मेरे जैसे पारस्परिक या छाया से) पर स्वयं को करना होगा। सभी प्रदाता परिदृश्यों में दो होंगे गुण (आप इसे 'नए' के ​​साथ नहीं देखते हैं) – ShuggyCoUk

1

आपका समाधान ऐसा नहीं करता है जो आपको लगता है कि यह करता है। ऐसा लगता है कि यह काम करता है लेकिन यह आपकी "नई" विधि को नहीं बुला रहा है।

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public DeliveryStrategy Delivery 
    { 
     get 
     { 
      Console.WriteLine("Order"); 
      return delivery; 
     } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    // 'new' Delivery property 
    public new ParcelDelivery Delivery 
    { 
     get 
     { 
      Console.WriteLine("CustomOrder"); 
      return base.Delivery as ParcelDelivery; 
     } 
     set { base.Delivery = value; } 
    } 
} 

तो कोड का यह स्निपेट वास्तव में अपने CustomOrder वर्ग का उपयोग करने के:

Order o = new CustomerOrder(); 
var d = o.Delivery; 

चाहेंगे उत्पादन "आदेश कुछ उत्पादन जोड़ने के लिए देखने के लिए कि कौन सी विधि कहा जाता है अपने कोड में निम्न परिवर्तन पर विचार करें "। नई विधि विशेष रूप से polymorphism तोड़ता है। यह कस्टमऑर्डर पर एक नई डिलिवरी संपत्ति बनाता है जो ऑर्डर बेस क्लास का हिस्सा नहीं है। तो जब आप अपने कस्टम ऑर्डर का उपयोग करते हैं जैसे कि यह एक ऑर्डर था, तो आप नई डिलिवरी प्रॉपर्टी नहीं कहते हैं क्योंकि यह केवल कस्टम ऑर्डर पर मौजूद है और ऑर्डर क्लास का हिस्सा नहीं है।

जो आप करने की कोशिश कर रहे हैं वह उस विधि को ओवरराइड कर रहा है जो अतिरंजित नहीं है। क्योंकि उप व्युत्पन्न प्रकार उन्हें ठीक से ओवरराइड करने के लिए सक्षम नहीं होगा,

// order (base) class 
public abstract class Order 
{ 
    private DeliveryStrategy delivery; 

    protected Order(DeliveryStrategy delivery) 
    { 
     this.delivery = delivery; 
    } 

    public abstract DeliveryStrategy Delivery 
    { 
     get { return delivery; } 
     protected set { delivery = value; } 
    } 
} 

public class CustomerOrder : Order 
{ 
    public CustomerOrder() 
     : base(new ParcelDelivery()) 
    { } 

    public override ParcelDelivery Delivery 
    { 
     get { return base.Delivery as ParcelDelivery; } 
     set { base.Delivery = value; } 
    } 
} 
+0

ऐसा लगता है कि मैं इसे करने की उम्मीद करता हूं। Console.WriteLine ("ऑर्डर") को कंसोल में बदलें। राइटलाइन (डिलीवरी। गेटटाइप()। GetName()) (या कुछ ऐसे) और आप देखेंगे ;-) –

+0

डिलीवरी। गेट टाइप() आपको पार्सल डिलीवरी देगा क्योंकि आप इसे आदेश के निर्माता को पास कर दिया, न कि क्योंकि आप सबक्लास की नई डिलिवरी संपत्ति को बुला रहे हैं। यदि आप यह देखने के लिए प्रतिबिंब का उपयोग करना चाहते हैं कि आप कहां हैं, आप जिस विधि को वास्तव में कॉल कर रहे हैं, तो लेखन रेखा को कंसोल। राइटलाइन (System.Reflection.MethodBase.GetCurrentMethod()। DeclaringType.FullName); – ZombieDev

0

एक आधार वर्ग की आभासी सदस्यों परछाई new का उपयोग करते हुए एक बुरा विचार है: आप संपत्ति मतलब तो overridable होने के लिए, यह सार हैं। यदि वर्ग के सदस्य मौजूद हैं जो कोई व्युत्पन्न कक्षाओं में छाया करना चाहते हैं, तो बेस क्लास सदस्यों को abstract या virtual के रूप में घोषित नहीं किया जाना चाहिए, बल्कि इसके बजाय protected abstract या protected virtual सदस्य को कॉल करना चाहिए। एक व्युत्पन्न प्रकार बेस-क्लास विधि को उस व्यक्ति के साथ छाया कर सकता है जो उचित protected सदस्य को कॉल करता है और परिणाम उचित रूप से रखता है।

संबंधित मुद्दे