एक वस्तु मॉडल को बदलने के लिए पैटर्न ..?


22

यहां एक सामान्य परिदृश्य है जो मेरे लिए हमेशा निराशाजनक है।

मेरे पास पैरेंट ऑब्जेक्ट के साथ ऑब्जेक्ट मॉडल है। जनक में कुछ बाल वस्तुएं होती हैं। कुछ इस तरह।

public class Zoo
{
    public List<Animal> Animals { get; set; }
    public bool IsDirty { get; set; }
}

प्रत्येक बच्चे के ऑब्जेक्ट में विभिन्न डेटा और विधियां होती हैं

public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void MakeMess()
    {
        ...
    }
}

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

इस परिदृश्य को संभालने के कुछ तरीके हैं (जो मुझे पता है)।

1) प्रत्येक पशु में परिवर्तनों को संप्रेषित करने के लिए माता-पिता चिड़ियाघर का संदर्भ हो सकता है।

public class Animal
{
    public Zoo Parent { get; set; }
    ...

    public void MakeMess()
    {
        Parent.OnAnimalMadeMess();
    }
}

यह सबसे खराब विकल्प की तरह लगता है क्योंकि यह पशु को अपनी मूल वस्तु से जोड़े रखता है। अगर मुझे एक जानवर चाहिए जो घर में रहता है तो क्या होगा?

2) एक अन्य विकल्प, यदि आप ऐसी भाषा का उपयोग कर रहे हैं जो घटनाओं का समर्थन करती है (जैसे C #) तो घटनाओं को बदलने के लिए अभिभावक की सदस्यता होनी चाहिए।

public class Animal
{
    public event OnMakeMessDelegate OnMakeMess;

    public void MakeMess()
    {
        OnMakeMess();
    }
}

public class Zoo
{
    ...

    public void SubscribeToChanges()
    {
        foreach (var animal in Animals)
        {
            animal.OnMakeMess += new OnMakeMessDelegate(OnMakeMessHandler);
        }
    }

    public void OnMakeMessHandler(object sender, EventArgs e)
    {
        ...
    }
}

यह काम करने लगता है लेकिन अनुभव से इसे बनाए रखना मुश्किल हो जाता है। यदि पशु कभी चिड़ियाघर बदलते हैं तो आपको पुराने चिड़ियाघर में घटनाओं को अनसब्सक्राइब करना होगा और नए चिड़ियाघर में फिर से भेजना होगा। यह केवल तब तक ख़राब हो जाता है जब रचना का पेड़ गहरा हो जाता है।

3) दूसरा विकल्प तर्क को अभिभावक तक ले जाना है।

public class Zoo
{
    public void AnimalMakesMess(Animal animal)
    {
        ...
    }
}

यह बहुत ही अस्वाभाविक लगता है और तर्क के दोहराव का कारण बनता है। उदाहरण के लिए, अगर मेरे पास एक हाउस ऑब्जेक्ट था जो चिड़ियाघर के साथ किसी भी सामान्य विरासत माता-पिता को साझा नहीं करता है।

public class House
{
    // Now I have to duplicate this logic
    public void AnimalMakesMess(Animal animal)
    {
        ...
    }
}

मुझे अभी तक इन स्थितियों से निपटने के लिए एक अच्छी रणनीति नहीं मिली है। और क्या उपलब्ध है? इसे सरल कैसे बनाया जा सकता है?


आप # 1 के बुरे होने के बारे में सही हैं, और मैं # 2 पर उत्सुक नहीं हूं; आम तौर पर आप दुष्प्रभावों से बचना चाहते हैं, और इसके बजाय आप उन्हें बढ़ा रहे हैं। विकल्प # 3 के बारे में, क्यों आप AnimalMakeMess को एक स्थिर पद्धति में शामिल नहीं कर सकते, जिसे सभी वर्ग कॉल कर सकते हैं?
डोभाल

4
यदि आप उस विशिष्ट अभिभावक वर्ग के बजाय एक इंटरफ़ेस (IAnimalObserver) के माध्यम से संवाद करते हैं तो # आवश्यक नहीं है।
coredump

जवाबों:


11

मुझे इससे एक-दो बार निपटना पड़ा। पहली बार मैंने विकल्प 2 (घटनाओं) का उपयोग किया और जैसा कि आपने कहा कि यह वास्तव में जटिल हो गया है। यदि आप उस मार्ग पर जाते हैं, तो मेरा सुझाव है कि घटनाओं को सही ढंग से पूरा करने के लिए आपको बहुत गहन इकाई परीक्षणों की आवश्यकता है और आप संदर्भों को ख़त्म नहीं कर रहे हैं, अन्यथा यह डीबग करने के लिए एक बहुत बड़ा दर्द है।

दूसरी बार, मैंने सिर्फ बच्चों के एक समारोह के रूप में मूल संपत्ति को लागू किया, इसलिए Dirtyप्रत्येक जानवर पर एक संपत्ति रखें , और Animal.IsDirtyवापस आने दें this.Animals.Any(x => x.IsDirty)। जो कि मॉडल में था। मॉडल के ऊपर एक नियंत्रक था, और नियंत्रक का काम यह जानना था कि मैंने मॉडल को बदलने के बाद मॉडल पर सभी कार्य नियंत्रक के माध्यम से पारित किए थे, इसलिए यह पता था कि कुछ बदल गया था), तो यह जानता था कि उसे कुछ निश्चित कॉल करना होगा -विभाजन कार्य, जैसे कि फिर से गंदे ZooMaintenanceहोने पर जांच करने के लिए विभाग को ट्रिगर Zooकरना। वैकल्पिक रूप से मैं बस ZooMaintenanceकुछ निर्धारित समय (प्रत्येक 100 एमएस, 1 सेकंड, 2 मिनट, 24 घंटे, जो भी आवश्यक था) तक चेक को धक्का दे सकता था।

मैंने पाया कि उत्तरार्द्ध बनाए रखना बहुत सरल है, और प्रदर्शन की समस्याओं के बारे में मेरी आशंका कभी भी भौतिक नहीं थी।

संपादित करें

इससे निपटने का एक अन्य तरीका एक संदेश बस पैटर्न है। Controllerमेरे उदाहरण में एक का उपयोग करने के बजाय , आप प्रत्येक वस्तु को एक IMessageBusसेवा के साथ इंजेक्ट करते हैं । तब Animalवर्ग एक संदेश प्रकाशित कर सकता है, जैसे "मेस मेड" और आपका Zooवर्ग "मेस मेड" संदेश की सदस्यता ले सकता है। संदेश बस सेवा Zooतब सूचित करने का ध्यान रखेगी जब कोई भी जानवर उन संदेशों में से किसी एक को प्रकाशित करता है, और वह अपनी IsDirtyसंपत्ति का पुनर्मूल्यांकन कर सकता है।

इसका यह लाभ है कि Animalsअब किसी Zooसंदर्भ की आवश्यकता नहीं है , और Zooहर एक से घटनाओं की सदस्यता लेने और सदस्यता रद्द करने के बारे में चिंता करने की आवश्यकता नहीं है Animal। जुर्माना यह है कि Zooउस संदेश की सदस्यता लेने वाले सभी वर्गों को अपने गुणों का पुनर्मूल्यांकन करना होगा, भले ही वह उसके जानवरों में से एक न हो। यह एक बड़ी बात हो सकती है या नहीं। यदि केवल एक या दो Zooउदाहरण हैं, तो यह शायद ठीक है।

संपादित करें 2

विकल्प की सादगी पर ध्यान न दें। 1. किसी को भी कोड को समझने में ज्यादा समस्या नहीं होगी। यह Animalकक्षा को देखने वाले किसी व्यक्ति के लिए स्पष्ट होगा कि जब MakeMessयह कहा जाता है कि यह संदेश को प्रचारित करता है Zooऔर यह उस Zooकक्षा के लिए स्पष्ट होगा जहां इसे इसके संदेश मिलते हैं। याद रखें कि ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग में, एक विधि कॉल को "संदेश" कहा जाता था। वास्तव में, विकल्प 1 से टूटने के लिए केवल एक बार यह समझ में आता है कि Zooक्या Animalगड़बड़ी होने पर सिर्फ नोटिफ़िकेशन से अधिक होना चाहिए । यदि ऐसी और वस्तुएं थीं जिन्हें अधिसूचित करने की आवश्यकता थी, तो मैं शायद एक संदेश बस या नियंत्रक पर जाऊंगा।


5

मैंने एक साधारण वर्ग आरेख बनाया है जो आपके डोमेन का वर्णन करता है: यहां छवि विवरण दर्ज करें

प्रत्येक Animal में Habitat गड़बड़ है।

इस Habitatबात की परवाह नहीं करता है कि उसके पास कितने या कितने जानवर हैं (जब तक कि यह आपके डिजाइन का मूल रूप से हिस्सा नहीं है, इस मामले में आपने जो वर्णन किया है वह नहीं है)।

लेकिन Animalदेखभाल करता है, क्योंकि यह हर में अलग तरह से व्यवहार करेगा Habitat

यह आरेख रणनीति डिजाइन पैटर्न के यूएमएल आरेख के समान है , लेकिन हम इसे अलग तरीके से उपयोग करेंगे।

यहाँ जावा में कुछ कोड उदाहरण हैं (मैं कोई C # विशिष्ट गलतियाँ नहीं करना चाहता)।

बेशक आप इस डिजाइन, भाषा और आवश्यकताओं के लिए अपना खुद का ट्वीक बना सकते हैं।

यह रणनीति इंटरफ़ेस है:

public interface Habitat {
    public void messUp(float magnitude);

    public float getCleanliness();
}

एक ठोस का एक उदाहरण Habitat। बेशक प्रत्येक Habitatउपवर्ग इन तरीकों को अलग तरीके से लागू कर सकता है।

public class Zoo implements Habitat {
    public float cleanliness = 1;

    public float getCleanliness() {
        return cleanliness;
    }

    public void messUp(float magnitude) {
        cleanliness -= magnitude;
    }
}

बेशक, आपके पास कई पशु उप-वर्ग हो सकते हैं, जहां हर एक इसे अलग तरीके से गड़बड़ करता है:

public class Animel {
    private Habitat habitat;

    public void makeMess() {
        habitat.messUp(.05f);
    }

    public Animel addTo(Habitat habitat) {
        this.habitat = habitat;
        return this;
    }
}

यह ग्राहक वर्ग है, यह मूल रूप से बताता है कि आप इस डिज़ाइन का उपयोग कैसे कर सकते हैं।

public class ZooKeeper {
    public Habitat zoo = new Zoo();

    public ZooKeeper() {
        new Animal()
            .addTo( zoo )
            .makeMess();

        if (zoo.getCleanliness() < 0.5f) {
            System.out.println("The zoo is really messy");
        } else {
            System.out.println("The zoo looks clean");
        }
    }
}

अपने असली एप्लिकेशन में बेशक आप Habitatजान सकते हैं और Animalजरूरत पड़ने पर प्रबंधन कर सकते हैं ।


3

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

interface MessablePlace
{
  void OnMess(object sender, MessEvent e);
}

class MessEvent
{
  String DetailsOrWhatever;
}

इंटरफ़ेस विकल्प में आपके विकल्प 1 के रूप में लगभग सरल होने का लाभ है, लेकिन यह आपको घर के जानवरों को बहुत आसानी से Houseया एक में रखने की अनुमति देता है FairlyLand


3
  • विकल्प 1 वास्तव में बहुत सीधा है। यह सिर्फ एक बैक-रेफरेंस है। लेकिन इसे इंटरफेस के साथ सामान्यीकृत करें Dwellingऔर MakeMessउस पर एक विधि प्रदान करें । जो वृत्ताकार निर्भरता को तोड़ता है। फिर जब जानवर कोई गड़बड़ करता है, तो वह dwelling.MakeMess()भी कॉल करता है।

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

ध्यान दें कि यदि आप संबंधपरक डेटाबेस तालिकाओं को मॉडलिंग कर रहे थे, तो संबंध दूसरे तरीके से चलेगा: पशु का चिड़ियाघर के लिए एक संदर्भ होगा, और चिड़ियाघर के लिए पशु का संग्रह एक क्वेरी परिणाम होगा।

  • उस विचार को आगे बढ़ाते हुए, आप एक जंजीर वास्तुकला का उपयोग कर सकते हैं। यही है, एक इंटरफ़ेस बनाएं Messable, और प्रत्येक मेसेज करने योग्य आइटम में एक संदर्भ शामिल करें next। एक गड़बड़ बनाने के बाद, MakeMessअगले आइटम पर कॉल करें ।

तो यहाँ चिड़ियाघर एक गड़बड़ बनाने में शामिल है, क्योंकि यह भी गड़बड़ हो जाता है। है:

Zoo implements Messable
House implements Messable
Animal implements Messable
   Messable next

   MakeMess()
       messy = true
       next.MakeMess

तो अब आपके पास उन चीजों की एक श्रृंखला है जो संदेश प्राप्त करती हैं कि गड़बड़ हो गई है।

  • विकल्प 2, एक प्रकाशित / सदस्यता मॉडल यहां काम कर सकता है, लेकिन वास्तव में भारी लगता है। ऑब्जेक्ट और कंटेनर का एक ज्ञात संबंध है, इसलिए यह कुछ अधिक सामान्य उपयोग करने के लिए थोड़ा भारी हाथ लगता है।

  • विकल्प 3: इस विशेष मामले में, कॉलिंग Zoo.MakeMess(animal)या House.MakeMess(animal)वास्तव में एक बुरा विकल्प नहीं है, क्योंकि एक घर में एक चिड़ियाघर की तुलना में गड़बड़ होने के लिए अलग-अलग शब्दार्थ हो सकते हैं।

यहां तक ​​कि अगर आप चेन मार्ग से नीचे नहीं जाते हैं, तो ऐसा लगता है कि यहां दो मुद्दे हैं: 1) मुद्दा एक वस्तु से उसके कंटेनर में परिवर्तन को फैलाने के बारे में है, 2) ऐसा लगता है कि आप एक इंटरफ़ेस बंद करना चाहते हैं कंटेनर अमूर्त जहां जानवर रह सकते हैं।

...

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

public Animal
    Function afterMess

    public MakeMess()
        messy = true
        afterMess()

जब जानवर चलता है, तो बस एक नया प्रतिनिधि सेट करें।

  • चरम पर ले जाया गया, आप मेकमाईस पर "सलाह के बाद" के साथ पहलू ओरिएंटेड प्रोग्रामिंग (एओपी) का उपयोग कर सकते हैं।

2

मैं 1 के साथ जाऊंगा, लेकिन मैं अभिभावक-बच्चे के संबंध को अधिसूचना तर्क के साथ अलग आवरण में बनाऊंगा। यह चिड़ियाघर पर पशु की निर्भरता को हटाता है और माता-पिता-बच्चे के संबंधों के स्वत: प्रबंधन की अनुमति देता है। लेकिन इससे आपको पदानुक्रम में वस्तुओं को पहले इंटरफेस / अमूर्त कक्षाओं में रीमेक करने और प्रत्येक इंटरफ़ेस के लिए एक विशिष्ट आवरण लिखने की आवश्यकता होती है। लेकिन कोड पीढ़ी का उपयोग करके इसे हटाया जा सकता है।

कुछ इस तरह :

public interface IAnimal
{
    string Name { get; set; }
    int Age { get; set; }

    void MakeMess();
}

public class Animal : IAnimal
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void MakeMess()
    {
        // makes mess
    }
}

public class ZooAnimals
{
    class AnimalInZoo : IAnimal
    {
        public IAnimal _animal;
        public ZooAnimals _zoo;

        public AnimalInZoo(IAnimal animal, ZooAnimals zoo)
        {
            _animal = animal;
            _zoo = zoo;
        }

        public string Name { get { return _animal.Name; } set { _animal.Name = value; } }
        public int Age { get { return _animal.Age; } set { _animal.Age = value; } }

        public void MakeMess()
        {
            _animal.MakeMess();
            _zoo.IsDirty = true;
        }
    }

    private Collection<AnimalInZoo> animals = new Collection<AnimalInZoo>();

    public IAnimal Add(IAnimal animal)
    {
        if (animal is AnimalInZoo)
        {
            var inZoo = (AnimalInZoo)animal;
            if (inZoo._zoo != this)
            {
                // animal is in a different zoo, what to do ?
                // either move animal to this zoo
                // or throw an exception so caller is forced to remove the animal from previous zoo first
            }
        }

        var anim = new AnimalInZoo(animal, this);
        animals.Add(anim);
        return anim;
    }

    public IAnimal Remove(IAnimal animal)
    {
        if (!(animal is AnimalInZoo))
        {
            // animal is not in zoo, throw an exception?
        }
        var inZoo = (AnimalInZoo)animal;
        if (inZoo._zoo != this)
        {
            // animal is in a different zoo, throw an exception?
        }

        animals.Remove(inZoo);
        return inZoo._animal;
    }

    public bool IsDirty { get; set; }
}

यह वास्तव में है कि कैसे कुछ ओआरएम अपना परिवर्तन ट्रैकिंग संस्थाओं पर करते हैं। वे संस्थाओं के आसपास रैपर बनाते हैं और आपको उन लोगों के साथ काम करते हैं। उन आवरणों को आम तौर पर प्रतिबिंब और गतिशील कोड पीढ़ी का उपयोग करके बनाया जाता है।


1

दो विकल्प जो मैं अक्सर उपयोग करता हूं। आप दूसरे दृष्टिकोण का उपयोग कर सकते हैं और संग्रह में घटना को वायरिंग के लिए तर्क को मूल पर डाल सकते हैं।

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


0

आप एक बुनियादी विफलता के साथ शुरू करते हैं: बाल वस्तुओं को अपने माता-पिता के बारे में नहीं जानना चाहिए।

क्या तार पता है कि वे एक सूची में हैं? नहीं, क्या वे जानते हैं कि वे एक कैलेंडर में मौजूद हैं? नहीं।

सबसे अच्छा विकल्प अपने डिजाइन को बदलना है ताकि इस प्रकार का परिदृश्य मौजूद न हो।

के बाद, नियंत्रण के उलट पर विचार करें। के बजाय MakeMessपर Animalएक पक्ष प्रभाव या घटना के साथ, पारित Zooविधि में। विकल्प 1 ठीक है यदि आपको उस आक्रमणकारी की रक्षा करने की आवश्यकता है जिसे Animalहमेशा कहीं न कहीं रहना पड़ता है। यह माता-पिता नहीं है, लेकिन एक सहकर्मी संघ है।

कभी-कभी 2 और 3 ठीक हो जाएंगे, लेकिन पालन करने के लिए महत्वपूर्ण वास्तु सिद्धांत यह है कि बच्चे अपने माता-पिता के बारे में नहीं जानते हैं।


मुझे संदेह है कि यह एक सूची में स्ट्रिंग की तुलना में फ़ॉर्म में सबमिट बटन की तरह अधिक है।
svidgen

1
@svidgen - फिर कॉलबैक में पास करें। एक घटना की तुलना में अधिक मूर्खतापूर्ण, इसके बारे में तर्क करना आसान है, और उन चीजों के लिए कोई शरारती संदर्भ नहीं है जिन्हें इसकी आवश्यकता नहीं है।
तेलस्तीन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.