java बिल्डर पैटर्न और विरासत




design-patterns inheritance (4)

मेरे पास एक ऑब्जेक्ट पदानुक्रम है जो जटिलता में बढ़ता है क्योंकि विरासत वृक्ष गहरा होता है। इनमें से कोई भी सार नहीं है, इसलिए, उनके सभी उदाहरण एक, कम या ज्यादा परिष्कृत, उद्देश्य प्रदान करते हैं।

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

जब मैंने अपने डिजाइन के दौरान कुछ समस्याएं आईं तो मैंने यहां एक उत्तर के लिए ब्राउज किया है। सबसे पहले, मुझे समस्या का वर्णन करने के लिए आपको एक सरल, उथला उदाहरण दें।

public class Rabbit
{
    public String sex;
    public String name;

    public Rabbit(Builder builder)
    {
        sex = builder.sex;
        name = builder.name;
    }

    public static class Builder
    {
        protected String sex;
        protected String name;

        public Builder() { }

        public Builder sex(String sex)
        {
            this.sex = sex;
            return this;
        }

        public Builder name(String name)
        {
            this.name = name;
            return this;
        }

        public Rabbit build()
        {
            return new Rabbit(this);
        }
    }
}

public class Lop extends Rabbit
{
    public float earLength;
    public String furColour;

    public Lop(LopBuilder builder)
    {
        super(builder);
        this.earLength = builder.earLength;
        this.furColour = builder.furColour;
    }

    public static class LopBuilder extends Rabbit.Builder
    {
        protected float earLength;
        protected String furColour;

        public LopBuilder() { }

        public Builder earLength(float length)
        {
            this.earLength = length;
            return this;
        }

        public Builder furColour(String colour)
        {
            this.furColour = colour;
            return this;
        }

        public Lop build()
        {
            return new Lop(this);
        }
    }
}

अब हमारे पास कुछ कोड है, इमेजिंग मैं एक Lop बनाना चाहता हूं:

Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);

यह कॉल संकलित नहीं होगा क्योंकि आखिरी जंजीर कॉल को हल नहीं किया जा सकता है, Builder विधि earLength को परिभाषित नहीं कर earLength । इसलिए इस तरह की आवश्यकता है कि सभी कॉल एक विशिष्ट क्रम में जंजीर हो जाएं जो बहुत अव्यवहारिक है, खासकर एक गहरे पदानुक्रम के पेड़ के साथ।

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

एक दृष्टिकोण जो मैंने वर्तमान में तय किया है, पदानुक्रम में सुपरक्लास Builder सभी तरीकों को ओवरराइड करना है और बस निम्न कार्य करना है:

public ConcreteBuilder someOverridenMethod(Object someParameter)
{
    super(someParameter);
    return this;
}

इस दृष्टिकोण के साथ मैं आश्वस्त कर सकता हूं कि मुझे एक उदाहरण लौटाया जा रहा है, मैं चेन कॉल जारी कर सकता हूं। हालांकि यह टेलीस्कोपिंग एंटी-पैटर्न के जितना भी बुरा नहीं है, यह एक करीबी दूसरा है और मैं इसे थोड़ा "हैकी" मानता हूं।

क्या मेरी समस्या का कोई और समाधान है जिसके बारे में मुझे पता नहीं है? पसंदीदा रूप से डिजाइन पैटर्न के साथ एक समाधान संगत। धन्यवाद!


यह प्रपत्र लगभग काम करता प्रतीत होता है। यह बहुत साफ नहीं है लेकिन ऐसा लगता है कि यह आपके मुद्दों से बचाता है:

class Rabbit<B extends Rabbit.Builder<B>> {

    String name;

    public Rabbit(Builder<B> builder) {
        this.name = builder.colour;
    }

    public static class Builder<B extends Rabbit.Builder<B>> {

        protected String colour;

        public B colour(String colour) {
            this.colour = colour;
            return (B)this;
        }

        public Rabbit<B> build () {
            return new Rabbit<>(this);
        }
    }
}

class Lop<B extends Lop.Builder<B>> extends Rabbit<B> {

    float earLength;

    public Lop(Builder<B> builder) {
        super(builder);
        this.earLength = builder.earLength;
    }

    public static class Builder<B extends Lop.Builder<B>> extends Rabbit.Builder<B> {

        protected float earLength;

        public B earLength(float earLength) {
            this.earLength = earLength;
            return (B)this;
        }

        @Override
        public Lop<B> build () {
            return new Lop<>(this);
        }
    }
}

public class Test {

    public void test() {
        Rabbit rabbit = new Rabbit.Builder<>().colour("White").build();
        Lop lop1 = new Lop.Builder<>().earLength(1.4F).colour("Brown").build();
        Lop lop2 = new Lop.Builder<>().colour("Brown").earLength(1.4F).build();
        //Lop.Builder<Lop, Lop.Builder> builder = new Lop.Builder<>();
    }

    public static void main(String args[]) {
        try {
            new Test().test();
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }
}

यद्यपि मैंने सफलतापूर्वक Rabbit और Lop (दोनों रूपों में) बनाया है, मैं इस चरण में काम नहीं कर सकता कि वास्तव में Builder ऑब्जेक्ट्स में से एक को पूर्ण प्रकार के साथ कैसे चालू किया जाए।

इस विधि का सार Builder विधियों में (B) को कास्ट पर निर्भर करता है। यह आपको ऑब्जेक्ट के प्रकार और Builder के प्रकार को परिभाषित करने और ऑब्जेक्ट के भीतर बनाए जाने के दौरान बनाए रखने की अनुमति देता है।

यदि कोई इस के लिए सही वाक्यविन्यास (जो गलत है) का काम कर सकता है तो मैं इसकी सराहना करता हूं।

Lop.Builder<Lop.Builder> builder = new Lop.Builder<>();

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

स्वीकार्य उत्तर के साथ मुख्य अंतर यह है कि

  1. मैं एक पैरामीटर पास करता हूं जो रिटर्न प्रकार का संकेत देता है
  2. एक सार ... और एक अंतिम निर्माता के लिए कोई ज़रूरत नहीं है।
  3. मैं एक 'न्यूबिल्डर' सुविधा विधि बनाता हूं।

कोड:

public class MySuper {
    private int superProperty;

    public MySuper() { }

    public void setSuperProperty(int superProperty) {
        this.superProperty = superProperty;
    }

    public static SuperBuilder<? extends MySuper, ? extends SuperBuilder> newBuilder() {
        return new SuperBuilder<>(new MySuper());
    }

    public static class SuperBuilder<R extends MySuper, B extends SuperBuilder<R, B>> {
        private final R mySuper;

        public SuperBuilder(R mySuper) {
            this.mySuper = mySuper;
        }

        public B withSuper(int value) {
            mySuper.setSuperProperty(value);
            return (B) this;
        }

        public R build() {
            return mySuper;
        }
    }
}

और फिर एक उपclass इस तरह दिखता है:

public class MySub extends MySuper {
    int subProperty;

    public MySub() {
    }

    public void setSubProperty(int subProperty) {
        this.subProperty = subProperty;
    }

    public static SubBuilder<? extends MySub, ? extends SubBuilder> newBuilder() {
        return new SubBuilder(new MySub());
    }

    public static class SubBuilder<R extends MySub, B extends SubBuilder<R, B>>
        extends SuperBuilder<R, B> {

        private final R mySub;

        public SubBuilder(R mySub) {
            super(mySub);
            this.mySub = mySub;
        }

        public B withSub(int value) {
            mySub.setSubProperty(value);
            return (B) this;
        }
    }
}

और एक सबब वर्ग

public class MySubSub extends MySub {
    private int subSubProperty;

    public MySubSub() {
    }

    public void setSubSubProperty(int subProperty) {
        this.subSubProperty = subProperty;
    }

    public static SubSubBuilder<? extends MySubSub, ? extends SubSubBuilder> newBuilder() {
        return new SubSubBuilder<>(new MySubSub());
    }

    public static class SubSubBuilder<R extends MySubSub, B extends SubSubBuilder<R, B>>
        extends SubBuilder<R, B> {

        private final R mySubSub;

        public SubSubBuilder(R mySub) {
            super(mySub);
            this.mySubSub = mySub;
        }

        public B withSubSub(int value) {
            mySubSub.setSubSubProperty(value);
            return (B)this;
        }
    }

}

इसे पूरी तरह से सत्यापित करने के लिए मैंने इस परीक्षण का उपयोग किया:

MySubSub subSub = MySubSub
        .newBuilder()
        .withSuper (1)
        .withSub   (2)
        .withSubSub(3)
        .withSub   (2)
        .withSuper (1)
        .withSubSub(3)
        .withSuper (1)
        .withSub   (2)
        .build();

यदि कोई भी अभी भी एक ही समस्या में फंस गया है, तो मैं निम्नलिखित समाधान का सुझाव देता हूं, जो "विरासत पर संरचना को पसंद करते हैं" डिजाइन पैटर्न का पालन करता है।

अभिभावक वर्ग

इसका मुख्य तत्व इंटरफेस है कि अभिभावक वर्ग बिल्डर को कार्यान्वित करना होगा:

public interface RabbitBuilder<T> {
    public T sex(String sex);
    public T name(String name);
}

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

public class Rabbit {
    public String sex;
    public String name;

    public Rabbit(Builder builder) {
        sex = builder.sex;
        name = builder.name;
    }

    public static class Builder implements RabbitBuilder<Builder> {
        protected String sex;
        protected String name;

        public Builder() {}

        public Rabbit build() {
            return new Rabbit(this);
        }

        @Override
        public Builder sex(String sex) {
            this.sex = sex;
            return this;
        }

        @Override
        public Builder name(String name) {
            this.name = name;
            return this;
        }
    }
}

बाल वर्ग

बाल वर्ग Builder को एक ही इंटरफेस को लागू करना होगा (विभिन्न सामान्य प्रकार के साथ):

public static class LopBuilder implements RabbitBuilder<LopBuilder>

बाल वर्ग Builder के अंदर फ़ील्ड संदर्भित फ़ील्ड Builder :

private Rabbit.Builder baseBuilder;

यह सुनिश्चित करता है कि बच्चे में माता-पिता Builder विधियों को बुलाया जाता है, हालांकि, उनका कार्यान्वयन अलग है:

@Override
public LopBuilder sex(String sex) {
    baseBuilder.sex(sex);
    return this;
}

@Override
public LopBuilder name(String name) {
    baseBuilder.name(name);
    return this;
}

public Rabbit build() {
    return new Lop(this);
}

बिल्डर के निर्माता:

public LopBuilder() {
    baseBuilder = new Rabbit.Builder();
}

निर्मित बाल वर्ग का निर्माता:

public Lop(LopBuilder builder) {
    super(builder.baseBuilder);
}

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

संग्रहित डेटा को संसाधित करते समय नकारात्मक पक्ष अतिरिक्त प्रकार कास्टिंग होगा।

यदि यह मामला बहुत ढीला है, तो आप मौजूदा गुणों को रख सकते हैं, लेकिन एक सामान्य सेट विधि है, जो 'कुंजी' नाम के आधार पर प्रतिबिंब विधि के लिए प्रतिबिंब और खोजों का उपयोग करती है। हालांकि मुझे लगता है कि प्रतिबिंब एक ओवरकिल होगा।





builder