स्थैतिक विधि वंशानुक्रम का सही विकल्प क्या है?


83

मैं समझता हूं कि स्थैतिक विधि विरासत C # में समर्थित नहीं है। मैंने कई चर्चाएँ भी पढ़ी हैं (यहाँ सहित) जिसमें डेवलपर्स इस कार्यक्षमता की आवश्यकता का दावा करते हैं, जिस पर विशिष्ट प्रतिक्रिया है "यदि आपको स्थैतिक सदस्य विरासत की आवश्यकता है, तो आपके डिज़ाइन में दोष है"।

ठीक है, यह देखते हुए कि ओओपी मुझे स्थिर विरासत के बारे में सोचना भी नहीं चाहता है, मुझे यह निष्कर्ष निकालना चाहिए कि इसके लिए मेरी स्पष्ट आवश्यकता मेरे डिजाइन में त्रुटि की ओर इशारा करती है। लेकिन, मैं फंस गया हूं। मैं वास्तव में इसे हल करने में कुछ मदद की सराहना करूंगा। यहां है चुनौती ...

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

फलों को अन्य ठोस वर्गों (ऐप्पल, ऑरेंज) द्वारा विरासत में प्राप्त किया जाएगा, जिनमें से प्रत्येक को एक मानक कारखाना विधि CreateInstance () को एक उदाहरण बनाने और आरंभ करने के लिए उजागर करना होगा।

यदि स्थिर सदस्य उत्तराधिकार संभव था, तो मैं कारखाने की विधि को आधार वर्ग में रखूंगा और उस प्रकार को प्राप्त करने के लिए व्युत्पन्न वर्ग के लिए एक आभासी विधि कॉल का उपयोग करूंगा जिसमें से एक ठोस उदाहरण को आरंभीकृत किया जाना चाहिए। क्लाइंट कोड Apple.CreateInstance () को पूरी तरह से प्रारंभिक Apple उदाहरण प्राप्त करने के लिए सरल करेगा।

लेकिन स्पष्ट रूप से यह संभव नहीं है, इसलिए कोई यह बता सकता है कि उसी कार्यक्षमता को समायोजित करने के लिए मेरे डिजाइन को कैसे बदलना होगा।


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

जवाबों:


62

एक विचार:

public abstract class Fruit<T>
    where T : Fruit<T>, new()
{
    public static T CreateInstance()
    {
        T newFruit = new T();
        newFruit.Initialize();  // Calls Apple.Initialize
        return newFruit;
    }

    protected abstract void Initialize();
}

public class Apple : Fruit<Apple>
{
    protected override void Initialize() { ... }
}

और ऐसे कॉल करें:

Apple myAppleVar = Fruit<Apple>.CreateInstance();

कोई अतिरिक्त कारखाना वर्गों की जरूरत नहीं है।


9
IMHO, क्लास स्तर पर डालने के बजाय जेनेरिक प्रकार को CreateInstance विधि में ले जाना बेहतर है। (जैसा मैंने अपने उत्तर में किया है)।
फ्रेडरिक घीसेल्स

1
आपकी प्राथमिकता नोट की गई है। आपकी पसंद का एक संक्षिप्त विवरण अधिक सराहना की जाएगी।
मैट हैस्मिथ

9
ऐसा करने से, आप शेष कक्षा को 'प्रदूषित' नहीं करते हैं; प्रकार पैरामीटर केवल बनाने की विधि में आवश्यक है (इस उदाहरण में कम से कम)। उसके आगे, मुझे लगता है कि: Fruit.Create <Apple> (); तब अधिक पठनीय है: फल <सेब> .क्रीट ();
फ्रेडरिक घीसेल्स

2
आप Appleकंस्ट्रक्टर को सार्वजनिक से कम नहीं कर सकते , क्योंकि where T : Fruit<T>, new()यह तय करता है कि Tसार्वजनिक कंस्ट्रक्टर होना चाहिए। @Matt Hamsmith - जब तक आप नहीं हटाते आपका कोड संकलित नहीं होता है protected Apple() { }। मैंने इसका परीक्षण पहले ही वी.एस.
Tohid

1
@Tohid - आप सही हैं। मैंने संपादित किया है। हालाँकि, यह Apple CreateTstance स्थिर फैक्ट्री पद्धति का उपयोग किए बिना निर्मित किया जाना है, लेकिन यह अपरिहार्य लगता है।
मैट हम्सिथ

18

कारखाने की विधि को प्रकार से बाहर ले जाएं, और इसे अपने कारखाने वर्ग में रखें।

public abstract class Fruit
{
    protected Fruit() {}

    public abstract string Define();

}

public class Apple : Fruit
{
    public Apple() {}

    public override string Define()
    {
         return "Apple";
    }
}

public class Orange : Fruit
{
    public Orange() {}

    public override string Define()
    {
         return "Orange";
    }
}

public static class FruitFactory<T> 
{
     public static T CreateFruit<T>() where T : Fruit, new()
     {
         return new T();
     }
}

लेकिन, जैसा कि मैं यह देख रहा हूं, बनाएँ विधि को अपने स्वयं के फ़ैक्टरी वर्ग में स्थानांतरित करने की कोई आवश्यकता नहीं है (हालांकि मुझे लगता है कि यह बेहतर है-चिंताओं का -अपराध-), आप इसे फ्रूट क्लास में रख सकते हैं:

public abstract class Fruit
{

   public abstract string Define();

   public static T CreateFruit<T>() where T : Fruit, new()
   {
        return new T();
   }

}

और, यह देखने के लिए कि क्या यह काम करता है:

    class Program
    {
        static void Main( string[] args )
        {
            Console.WriteLine (Fruit.CreateFruit<Apple> ().Define ());
            Console.WriteLine (Fruit.CreateFruit<Orange> ().Define ());

            Console.ReadLine ();
        }        
    }

1
आपका कोड संकलित नहीं होगा। आपको वह जगह चाहिए जहां T: नया () क्लॉज है। हालाँकि, यह मेरा एक सटीक धोखा है।
जॉन गीटजेन

1
यद्यपि कोई एक स्थिर फ्रूटफैक्टरी क्लास बना सकता है, मैं एक तत्काल फ्रूटफैक्टरी कक्षा के साथ एक IFruitFactory इंटरफ़ेस का पक्ष लेगा। आप केवल एक FruitFactory वर्ग के साथ समाप्त हो सकते हैं, जिसकी CreateFruit विधि अपनी वस्तु उदाहरण के लिए कोई संदर्भ नहीं देती है और इस प्रकार बस एक स्थिर विधि हो सकती है, लेकिन यदि फल बनाने के कई तरीकों की पेशकश करना आवश्यक हो जाता है, या यदि फल निर्माण होता है कभी भी राज्य को बनाए रखने की क्षमता की आवश्यकता होती है, उदाहरण के तरीकों का उपयोग करना उपयोगी साबित हो सकता है।
सुपरकैट


4

मैं ऐसा कुछ करूंगा

 public abstract class Fruit() {
      public abstract void Initialize();
 }

 public class Apple() : Fruit {
     public override void Initialize() {

     }
 }

 public class FruitFactory<T> where T : Fruit, new {
      public static <T> CreateInstance<T>() {
          T fruit = new T();
          fruit.Initialize();
          return fruit;  
      }
 } 


var fruit = FruitFactory<Apple>.CreateInstance()

3

WebRequestवर्ग और .NET बीसीएल में अपनी व्युत्पन्न प्रकार कैसे डिजाइन इस तरह की अपेक्षाकृत अच्छी तरह से लागू किया जा सकता का एक अच्छा उदाहरण प्रस्तुत करती हैं।

WebRequestवर्ग सहित कई उप-वर्गों, है HttpWebRequestऔर FtpWebReuest। अब, यह WebRequestआधार वर्ग भी एक कारखाना प्रकार है, और एक स्थिर Createविधि को उजागर करता है (उदाहरण के निर्माता छिपे हुए हैं, जैसा कि कारखाना पैटर्न द्वारा आवश्यक है)।

public static WebRequest Create(string requestUriString)
public static WebRequest Create(Uri requestUri)

यह Createविधि WebRequestकक्षा का एक विशिष्ट कार्यान्वयन लौटाती है , और ऑब्जेक्ट के प्रकार को बनाने और वापस करने के लिए URI (या URI स्ट्रिंग) का उपयोग करती है।

यह निम्नलिखित उपयोग पैटर्न का अंतिम परिणाम है:

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/");
// or equivalently
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/");

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/");
// or equivalently
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/");

मुझे व्यक्तिगत रूप से लगता है कि यह मुद्दे पर पहुंचने का एक अच्छा तरीका है, और यह वास्तव में .NET फ्रेमवर्क रचनाकारों की पूर्वनिर्मित विधि प्रतीत होती है।


3

सबसे पहले, स्टैटिक इनिशियलाइज़र जो वर्चुअल हो सकते हैं उनका मतलब यह नहीं है कि आपके पास "मानक" सदस्य विधियाँ नहीं हो सकती हैं, जो ओवरलोड हो सकती हैं। सभी में से, आप अपने वर्चुअल तरीकों को कंस्ट्रक्टर्स से कॉल कर सकते हैं, और वे उम्मीद के मुताबिक काम करेंगे, इसलिए यहां कोई समस्या नहीं है। तीसरा, आप सामान्य-सुरक्षित कारखाने के लिए जेनरिक का उपयोग कर सकते हैं।
यहां कुछ कोड दिए गए हैं, जो निर्माता + सदस्य प्रारंभिक () विधि का उपयोग करता है जिसे निर्माता द्वारा कहा जाता है (और यह संरक्षित है, इसलिए आपको चिंता करने की ज़रूरत नहीं है, कि कोई व्यक्ति ऑब्जेक्ट बनाने के बाद इसे फिर से कॉल करेगा):


abstract class Fruit
{
    public Fruit()
    {
        Initialize();
    }

    protected virtual void Initialize()
    {
        Console.WriteLine("Fruit.Initialize");
    }
}

class Apple : Fruit
{
    public Apple()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Apple.Initialize");
    }

    public override string ToString()
    {
        return "Apple";
    }
}

class Orange : Fruit
{
    public Orange()
        : base()
    { }

    protected override void Initialize()
    {
        base.Initialize();
        Console.WriteLine("Orange.Initialize");
    }

    public override string ToString()
    {
        return "Orange";
    }
}

class FruitFactory
{
    public static T CreateFruit<T>() where T : Fruit, new()
    {
        return new T();
    }
}

public class Program
{

    static void Main()
    {
        Apple apple = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(apple.ToString());

        Orange orange = new Orange();
        Console.WriteLine(orange.ToString());

        Fruit appleFruit = FruitFactory.CreateFruit<Apple>();
        Console.WriteLine(appleFruit.ToString());
    }
}

1
आम तौर पर निर्माणकर्ताओं से आभासी तरीकों को कॉल करने से बचना सबसे अच्छा है। ब्लॉग
TrueWill

यह सच है, यह मुश्किल हो सकता है, हालांकि, यह संभव है और हमेशा उसी तरह काम करना चाहिए जैसा कि समस्या है, यह समझने के लिए कि 'इसका मतलब' क्या है। सामान्य तौर पर, मैं किसी भी अधिक जटिल तरीकों (यहां तक ​​कि गैर-आभासी, यानी, क्योंकि वे अक्सर एक तरह से निर्मित होते हैं, को कॉल करने से बचने के लिए जाते हैं, जो उन्हें एक अपवाद फेंकने की अनुमति देता है, और मुझे अपने कॉस्टर को कुछ भी फेंकना पसंद नहीं है) , लेकिन यह डेवलपर का निर्णय है।
Marcin Deptuła

0

मैं सबसे अच्छी बात यह कहूंगा कि फलों के वर्ग पर एक वर्चुअल / एब्सट्रैक्ट इनिशियलाइज़ विधि बनाना है जिसे कॉल किया जाना चाहिए और फिर इंस्टेंस बनाने के लिए एक बाहरी 'फ्रूट फैक्ट्री' क्लास बनाना होगा:


public class Fruit
{
    //other members...
    public abstract void Initialise();
}

public class FruitFactory()
{
    public Fruit CreateInstance()
    {
        Fruit f = //decide which fruit to create
        f.Initialise();

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