जावा: एक कदम बिल्डर को कैसे लागू किया जाए जिसके लिए बसने का क्रम मायने नहीं रखता है?


10

संपादित करें: मैं यह बताना चाहता हूं कि यह प्रश्न एक सैद्धांतिक समस्या का वर्णन करता है, और मुझे पता है कि मैं अनिवार्य पैरामीटर के लिए कंस्ट्रक्टर तर्क का उपयोग कर सकता हूं, या एपीआई गलत तरीके से उपयोग किए जाने पर रनटाइम अपवाद को फेंक सकता हूं। हालाँकि, मैं एक ऐसे समाधान की तलाश कर रहा हूँ जिसमें निर्माण तर्क या रनटाइम जाँच की आवश्यकता न हो

कल्पना कीजिए कि आपके पास Carइस तरह का एक इंटरफ़ेस है:

public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional
}

जैसा कि टिप्पणी से पता चलता है, एक Carहोना चाहिए Engineऔर Transmissionएक Stereoवैकल्पिक है। इसका मतलब है कि एक बिल्डर जो build()एक Carउदाहरण हो सकता है, उसके पास केवल एक build()विधि होनी चाहिए यदि ए Engineऔर Transmissionपहले से ही दोनों बिल्डर उदाहरण के लिए दिए गए हैं। जिस तरह से उस प्रकार चेकर किसी भी कोड है कि प्रयास एक बनाने के लिए संकलित करने के लिए मना कर देगा Carएक के बिना उदाहरण Engineया Transmission

यह एक कदम बिल्डर के लिए कहता है । आमतौर पर आप कुछ इस तरह से लागू करेंगे:

public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional

    public class Builder {
        public BuilderWithEngine engine(Engine engine) {
            return new BuilderWithEngine(engine);
        }
    }

    public class BuilderWithEngine {
        private Engine engine;
        private BuilderWithEngine(Engine engine) {
            this.engine = engine;
        }
        public BuilderWithEngine engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            return new CompleteBuilder(engine, transmission);
        }
    }

    public class CompleteBuilder {
        private Engine engine;
        private Transmission transmission;
        private Stereo stereo = null;
        private CompleteBuilder(Engine engine, Transmission transmission) {
            this.engine = engine;
            this.transmission = transmission;
        }
        public CompleteBuilder engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        public CompleteBuilder stereo(Stereo stereo) {
            this.stereo = stereo;
            return this;
        }
        public Car build() {
            return new Car() {
                @Override
                public Engine getEngine() {
                    return engine;
                }
                @Override
                public Transmission getTransmission() {
                    return transmission;
                }
                @Override
                public Stereo getStereo() {
                    return stereo;
                }
            };
        }
    }
}

(वहाँ अलग अलग बिल्डर वर्गों की एक श्रृंखला है Builder, BuilderWithEngine, CompleteBuilder), कि ऐड एक आवश्यक एक के बाद सेटर विधि, साथ ही सभी वैकल्पिक सेटर तरीकों से युक्त पिछले वर्ग के साथ।
इसका मतलब यह है कि इस कदम बिल्डर के उपयोगकर्ता उस क्रम तक ही सीमित हैं जिसमें लेखक ने अनिवार्य निपटान उपलब्ध कराया है । यहां संभावित उपयोगों का एक उदाहरण है (ध्यान दें कि वे सभी कड़ाई से आदेशित हैं: engine(e)पहले, उसके बाद transmission(t), और अंत में वैकल्पिक stereo(s))।

new Builder().engine(e).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).engine(e).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).engine(e).build();
new Builder().engine(e).transmission(t).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).stereo(s).build();

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

इसका एकमात्र उपाय जो मैं सोच सकता था, वह बहुत ही जटिल है: अनिवार्य गुणों के हर संयोजन के लिए सेट होने या अभी तक सेट नहीं होने के कारण, मैंने एक समर्पित बिल्डर वर्ग बनाया है जो जानता है कि संभावित अन्य अनिवार्य निवासियों को आने से पहले बुलाया जाना चाहिए राज्य जहां build()विधि उपलब्ध होनी चाहिए, और उन बसने वालों में से प्रत्येक अधिक पूर्ण प्रकार के बिल्डर को लौटाता है जो एक build()विधि से युक्त एक कदम है ।
मैंने नीचे कोड जोड़ा है, लेकिन आप कह सकते हैं कि मैं FSM बनाने के लिए टाइप सिस्टम का उपयोग कर रहा हूं जो आपको एक बनाने की अनुमति देता है Builder, जिसे BuilderWithEngineया तो एक में बदल दिया जा सकता है BuilderWithTransmission, जिसे फिर दोनों को एक में बदल दिया जा सकता है CompleteBuilder, जो लागू होता हैbuild()तरीका। इनमें से किसी भी बिल्डर इंस्टेंसेस पर वैकल्पिक सेटर को लागू किया जा सकता है। यहां छवि विवरण दर्ज करें

public interface Car {
    public Engine getEngine(); // required
    public Transmission getTransmission(); // required
    public Stereo getStereo(); // optional

    public class Builder extends OptionalBuilder {
        public BuilderWithEngine engine(Engine engine) {
            return new BuilderWithEngine(engine, stereo);
        }
        public BuilderWithTransmission transmission(Transmission transmission) {
            return new BuilderWithTransmission(transmission, stereo);
        }
        @Override
        public Builder stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class OptionalBuilder {
        protected Stereo stereo = null;
        private OptionalBuilder() {}
        public OptionalBuilder stereo(Stereo stereo) {
            this.stereo = stereo;
            return this;
        }
    }

    public class BuilderWithEngine extends OptionalBuilder {
        private Engine engine;
        private BuilderWithEngine(Engine engine, Stereo stereo) {
            this.engine = engine;
            this.stereo = stereo;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            return new CompleteBuilder(engine, transmission, stereo);
        }
        public BuilderWithEngine engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        @Override
        public BuilderWithEngine stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class BuilderWithTransmission extends OptionalBuilder {
        private Transmission transmission;
        private BuilderWithTransmission(Transmission transmission, Stereo stereo) {
            this.transmission = transmission;
            this.stereo = stereo;
        }
        public CompleteBuilder engine(Engine engine) {
            return new CompleteBuilder(engine, transmission, stereo);
        }
        public BuilderWithTransmission transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        @Override
        public BuilderWithTransmission stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
    }

    public class CompleteBuilder extends OptionalBuilder {
        private Engine engine;
        private Transmission transmission;
        private CompleteBuilder(Engine engine, Transmission transmission, Stereo stereo) {
            this.engine = engine;
            this.transmission = transmission;
            this.stereo = stereo;
        }
        public CompleteBuilder engine(Engine engine) {
            this.engine = engine;
            return this;
        }
        public CompleteBuilder transmission(Transmission transmission) {
            this.transmission = transmission;
            return this;
        }
        @Override
        public CompleteBuilder stereo(Stereo stereo) {
            super.stereo(stereo);
            return this;
        }
        public Car build() {
            return new Car() {
                @Override
                public Engine getEngine() {
                    return engine;
                }
                @Override
                public Transmission getTransmission() {
                    return transmission;
                }
                @Override
                public Stereo getStereo() {
                    return stereo;
                }
            };
        }
    }
}

जैसा कि आप बता सकते हैं, यह अच्छी तरह से पैमाने पर नहीं है, क्योंकि आवश्यक विभिन्न बिल्डर वर्गों की संख्या ओ (2 ^ एन) होगी जहां एन अनिवार्य बसने वालों की संख्या है।

इसलिए मेरा प्रश्न: क्या यह अधिक शान से किया जा सकता है?

(मैं एक जवाब की तलाश कर रहा हूं जो जावा के साथ काम करता है, हालांकि स्काला स्वीकार्य होगा)


1
क्या आप इन निर्भरता के सभी को खड़ा करने के लिए एक IoC कंटेनर का उपयोग करने से रोकता है? इसके अलावा, मुझे यह स्पष्ट नहीं है कि, यदि आपके द्वारा बताए गए आदेश पर कोई फर्क नहीं पड़ता है, तो आप साधारण सेटर विधियों का उपयोग नहीं कर सकते हैं जो वापस आते हैं this?
रॉबर्ट हार्वे

.engine(e)एक बिल्डर के लिए दो बार आह्वान करने का क्या मतलब है ?
एरिक इद्दत

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

1
रॉबर्ट: लक्ष्य यह है कि टाइप चेकर इस तथ्य को लागू करे कि इंजन और ट्रांसमिशन दोनों अनिवार्य हैं; इस तरह से आप कॉल नहीं कर सकते हैं build()यदि आपने कॉल नहीं किया है engine(e)और transmission(t)इससे पहले।
व्युत्पन्न

एरिक: आप एक डिफ़ॉल्ट Engineकार्यान्वयन के साथ शुरू करना चाहते हैं , और बाद में इसे अधिक विशिष्ट कार्यान्वयन के साथ अधिलेखित कर सकते हैं। लेकिन सबसे अधिक संभावना है कि यह अधिक समझ में आता है अगर engine(e)एक सेटर नहीं था, लेकिन एक योजक addEngine(e):। यह एक Carबिल्डर के लिए उपयोगी होगा जो एक से अधिक इंजन / मोटर के साथ हाइब्रिड कारों का उत्पादन कर सकता है। चूंकि यह एक आकस्मिक उदाहरण है, इसलिए मैं इस बात पर नहीं गया कि आप ऐसा क्यों करना चाहते हैं - संक्षिप्तता के लिए।
derabbink

जवाबों:


3

आपके द्वारा प्रदत्त विधि कॉल के आधार पर आपको दो अलग-अलग आवश्यकताएं लगती हैं।

  1. केवल एक (आवश्यक) इंजन, केवल एक (आवश्यक) संचरण, और केवल एक (वैकल्पिक) स्टीरियो।
  2. एक या एक से अधिक (आवश्यक) इंजन, एक या अधिक (आवश्यक) प्रसारण, और एक या अधिक (वैकल्पिक) स्टीरियो।

मुझे लगता है कि यहां पहला मुद्दा यह है कि आप नहीं जानते कि आप क्या करना चाहते हैं। इसका एक हिस्सा यह है कि यह ज्ञात नहीं है कि आप निर्मित वस्तु को कैसा दिखना चाहते हैं।

एक कार में केवल एक इंजन और एक ट्रांसमिशन हो सकता है। यहां तक ​​कि हाइब्रिड कारों में केवल एक इंजन होता है (शायद GasAndElectricEngine)

मैं दोनों कार्यान्वयनों को संबोधित करूंगा:

public class CarBuilder {

    public CarBuilder(Engine engine, Transmission transmission) {
        // ...
    }

    public CarBuilder setStereo(Stereo stereo) {
        // ...
        return this;
    }
}

तथा

public class CarBuilder {

    public CarBuilder(List<Engine> engines, List<Transmission> transmission) {
        // ...
    }

    public CarBuilder addStereo(Stereo stereo) {
        // ...
        return this;
    }
}

यदि एक इंजन और ट्रांसमिशन की आवश्यकता होती है, तो उन्हें निर्माता में होना चाहिए।

यदि आपको नहीं पता कि इंजन या ट्रांसमिशन की आवश्यकता क्या है, तो अभी तक एक सेट न करें; यह एक संकेत है कि आप बिल्डर को ढेर तक बना रहे हैं।


2

नल ऑब्जेक्ट पैटर्न का उपयोग क्यों नहीं कर रहा है? इस बिल्डर से छुटकारा पाएं, सबसे सुंदर कोड जिसे आप लिख सकते हैं वह वह है जिसे आपको वास्तव में लिखना नहीं है।

public final class CarImpl implements Car {
    private final Engine engine;
    private final Transmission transmission;
    private final Stereo stereo;

    public CarImpl(Engine engine, Transmission transmission) {
        this(engine, transmission, new DefaultStereo());
    }

    public CarImpl(Engine engine, Transmission transmission, Stereo stereo) {
        this.engine = engine;
        this.transmission = transmission;
        this.stereo = stereo;
    }

    //...

}

यह वही है जो मैंने सीधे सोचा था। हालाँकि मेरे पास तीन पैरामीटर कंस्ट्रक्टर नहीं होगा, बस दो पैरामीटर कंस्ट्रक्टर जिसमें अनिवार्य तत्व हैं और फिर स्टीरियो के लिए एक सेटर है क्योंकि यह वैकल्पिक है।
एनकेटर

1
जैसे एक सरल (वंचित) उदाहरण में Car, यह समझ में आता है क्योंकि c'tor तर्कों की संख्या बहुत कम है। हालाँकि, जैसे ही आप कुछ भी जटिल (> = 4 अनिवार्य तर्क) के साथ काम कर रहे हैं, पूरी बात को कम / कम पठनीय ("इंजन या ट्रांसमिशन पहले आया था?") को संभालने के लिए और अधिक कठिन हो जाता है। इसलिए आप एक बिल्डर का उपयोग करेंगे: एपीआई आपको जो निर्माण कर रहे हैं उसके बारे में अधिक स्पष्ट होने के लिए मजबूर करता है।
derabbink

1
@derabbink इस मामले में अपने से छोटे लोगों की क्लास क्यों नहीं तोड़ रहा? एक बिल्डर का उपयोग सिर्फ इस तथ्य को छिपाएगा कि वर्ग बहुत अधिक कर रहा है और अप्राप्य हो गया है।
देखा गया

1
पैटर्न पागलपन को समाप्त करने के लिए यश।
रॉबर्ट हार्वे

@ कुछ कक्षाओं में केवल बहुत सारा डेटा होता है। उदाहरण के लिए, यदि आप एक एक्सेस लॉग क्लास का उत्पादन करना चाहते हैं जो HTTP रिक्वेस्ट के बारे में सभी संबंधित जानकारी रखता है और डेटा को CSV या GSON प्रारूप में आउटपुट करता है। बहुत अधिक डेटा होगा और यदि आप संकलन समय के दौरान मौजूद रहने के लिए कुछ क्षेत्रों को लागू करना चाहते हैं, तो आपको एक बहुत लंबे अरग सूची निर्माता के साथ एक बिल्डर पैटर्न की आवश्यकता होगी, जो अच्छा नहीं लगता है।
ssgao

1

सबसे पहले, जब तक आपके पास मेरे द्वारा काम की गई किसी भी दुकान की तुलना में बहुत अधिक समय नहीं है, तब तक शायद यह किसी भी संचालन के आदेश की अनुमति देने या केवल इस तथ्य के साथ रहने के लायक नहीं है कि आप एक से अधिक रेडियो निर्दिष्ट कर सकते हैं। ध्यान दें कि आप कोड के बारे में बात कर रहे हैं, उपयोगकर्ता इनपुट के बारे में नहीं, इसलिए आपके पास ऐसे संकलन हो सकते हैं जो संकलन समय पर एक सेकंड से पहले आपकी इकाई परीक्षण के दौरान विफल हो जाएंगे।

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

new Builder(e, t).build();                      // ok
new Builder(e, t).stereo(s).build();            // ok
new Builder(e, t).stereo(s).stereo(s).build();  // exception on second call to stereo as stereo is already set 

यदि यह केवल स्टीरियो है जो वैकल्पिक है, तो बिल्डरों के उप-वर्गों का उपयोग करके अंतिम चरण करना संभव है, लेकिन इससे परे परीक्षण के बजाय संकलन समय पर त्रुटि प्राप्त करने से लाभ शायद प्रयास के लायक नहीं है।


0

आवश्यक बिल्डर वर्गों की संख्या O (2 ^ n) होगी जहां n अनिवार्य निपटानकर्ताओं की संख्या है।

आपने इस प्रश्न के लिए पहले ही सही दिशा का अनुमान लगा लिया है।

यदि आप संकलन-समय जाँच प्राप्त करना चाहते हैं, तो आपको (2^n)प्रकारों की आवश्यकता होगी । यदि आप रन-टाइम चेकिंग प्राप्त करना चाहते हैं, तो आपको एक चर की आवश्यकता होगी जो (2^n)राज्यों को स्टोर कर सकता है; एक n-बिट पूर्णांक करेगा।


क्योंकि सी ++ का समर्थन करता है गैर प्रकार टेम्पलेट पैरामीटर (मान पूर्णांक जैसे) , यह संभव है एक सी ++ वर्ग टेम्पलेट के लिए में instantiated जा करने के लिए O(2^n)विभिन्न प्रकार के एक योजना के समान का उपयोग कर इस

हालाँकि, ऐसी भाषाओं में जो गैर-प्रकार के टेम्पलेट मापदंडों का समर्थन नहीं करती हैं, आप O(2^n)विभिन्न प्रकारों को तुरंत टाइप करने के लिए प्रकार प्रणाली पर भरोसा नहीं कर सकते हैं ।


अगला अवसर जावा एनोटेशन (और C # विशेषताएँ) के साथ है। इन अतिरिक्त मेटाडेटा का उपयोग उपयोगकर्ता-परिभाषित व्यवहार को संकलन-समय पर करने के लिए किया जा सकता है, जब एनोटेशन प्रोसेसर का उपयोग किया जाता है। हालाँकि, इन पर अमल करना आपके लिए बहुत काम का होगा। यदि आप ऐसे ढांचे का उपयोग कर रहे हैं जो आपके लिए यह कार्यक्षमता प्रदान करते हैं, तो इसका उपयोग करें। अन्यथा, अगले अवसर की जांच करें।


अंत में, ध्यान दें कि O(2^n)रन-टाइम पर एक चर के रूप में विभिन्न राज्यों को संग्रहीत करना (शाब्दिक रूप से, पूर्णांक के रूप में कम से कम nबिट्स चौड़ा है) बहुत आसान है। यही कारण है कि सबसे अधिक उत्कीर्ण जवाब सभी आपको रन-टाइम पर इस चेक को करने की सलाह देते हैं, क्योंकि संभावित लाभ की तुलना में, संकलन-समय की जांच को लागू करने के लिए आवश्यक प्रयास बहुत अधिक है।

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