लंबे पैरामीटर सूचियों वाले कंस्ट्रक्टरों का उपयोग किए बिना बड़ी, अपरिवर्तनीय वस्तुओं का निर्माण


96

मेरे पास कुछ बड़ी (3 से अधिक फ़ील्ड) ऑब्जेक्ट हैं जो अपरिवर्तनीय हो सकते हैं और होने चाहिए। हर बार जब मैं उस मामले में भाग लेता हूं तो मैं लंबे पैरामीटर सूचियों के साथ कंस्ट्रक्टर एबोमिनेशन बनाता हूं।

यह सही नहीं लगता है, यह उपयोग करने के लिए कठिन है, और पठनीयता ग्रस्त है।

यदि फ़ील्ड कुछ प्रकार के संग्रह प्रकार जैसे सूचियाँ हैं, तो यह और भी खराब है। एक साधारण addSibling(S s)वस्तु के निर्माण में बहुत आसानी होती है, लेकिन यह वस्तु को बदलने योग्य बनाता है।

आप लोग ऐसे मामलों में क्या इस्तेमाल करते हैं?

मैं स्काला और जावा पर हूं, लेकिन मुझे लगता है कि समस्या भाषा अज्ञेय है जब तक भाषा वस्तु उन्मुख है।

समाधान मैं सोच सकता हूं:

  1. "लंबे पैरामीटर सूचियों के साथ कंस्ट्रक्टर एबोमिनेशन"
  2. बिल्डर पैटर्न

5
मुझे लगता है कि बिल्डर पैटर्न इसका सबसे अच्छा और सबसे मानक समाधान है।
ज़ाचरी राइट

@Zachary: आप जो वकालत कर रहे हैं वह केवल बिल्डर पैटर्न के एक विशेष रूप के लिए काम करता है, जैसा कि जोशुआ बलोच ने यहां बताया है: drdobbs.com/java/208403883?pgno=2 मैं संबंधित "धाराप्रवाह इंटरफ़ेस" के साथ कॉल करना पसंद करता हूं यह "बिल्डर पैटर्न का विशेष रूप" (मेरा उत्तर देखें)।
SyntaxT3rr0r

जवाबों:


76

ठीक है, आप चाहते हैं कि एक बार पढ़ने में आसान और अपरिवर्तनीय वस्तु दोनों एक बार बनाई जाए?

मुझे लगता है कि एक धाराप्रवाह इंटरफ़ेस सही तरीके से आपकी मदद करेगा।

यह इस तरह दिखेगा (विशुद्ध रूप से बना उदाहरण):

final Foo immutable = FooFactory.create()
    .whereRangeConstraintsAre(100,300)
    .withColor(Color.BLUE)
    .withArea(234)
    .withInterspacing(12)
    .build();

मैंने बोल्ड में "CORRECTLY DONE" लिखा क्योंकि अधिकांश जावा प्रोग्रामर धाराप्रवाह इंटरफेस गलत पाते हैं और ऑब्जेक्ट को बनाने के लिए आवश्यक विधि के साथ अपनी वस्तु को प्रदूषित करते हैं, जो कि पूरी तरह से गलत है।

चाल यह है कि केवल बिल्ड () विधि वास्तव में एक फू बनाता है (इसलिए आप फू को अपरिवर्तनीय कर सकते हैं)।

FooFactory.create () , जहांXXX (..) और withXXX (..) सभी "कुछ और" बनाते हैं।

यह कुछ और FooFactory हो सकता है, यहाँ यह करने का एक तरीका है…।

आप FooFactory इस तरह दिखेगा:

// Notice the private FooFactory constructor
private FooFactory() {
}

public static FooFactory create() {
    return new FooFactory();
}

public FooFactory withColor( final Color col ) {
    this.color = color;
    return this;
}

public Foo build() {
    return new FooImpl( color, and, all, the, other, parameters, go, here );
}

11
@ सभी: कृपया "FooImpl" में "Impl" पोस्टफ़िक्स के बारे में कोई शिकायत नहीं है: यह वर्ग एक कारखाने के अंदर छिपा हुआ है और कोई भी व्यक्ति इसे धाराप्रवाह इंटरफ़ेस लिखने के अलावा कभी नहीं देखेगा। सभी उपयोगकर्ता परवाह करते हैं कि उन्हें "फू" मिलता है। मैं "FooImpl" को "FooPointlessNitpick" भी कह सकता था;)
SyntaxT3rr0r

5
पूर्व-खाली लग रहा है? ;) आप इस बारे में अतीत में निपजे गए हैं। :)
ग्रेग डी

3
मेरा मानना है कि सामान्य त्रुटि वह की चर्चा करते हुए है कि लोगों को "withXXX" (आदि) विधियों को जोड़ने है करने के लिएFoo , वस्तु नहीं बल्कि एक अलग होने से FooFactory
डीन हार्डिंग

3
आपके पास अभी भी FooImpl8 मापदंडों वाला कंस्ट्रक्टर है। सुधार क्या है?
मोट

4
मैं इस कोड को नहीं कहूंगा immutable, और मुझे लगता है कि लोग कारखाने की वस्तुओं का पुन: उपयोग करने से डरेंगे क्योंकि उन्हें लगता है कि यह है। मेरा मतलब है: FooFactory people = FooFactory.create().withType("person"); Foo women = people.withGender("female").build(); Foo talls = people.tallerThan("180m").build();जहां tallsअब केवल महिलाएं होंगी। यह एक अपरिवर्तनीय एपीआई के साथ नहीं होना चाहिए।
थॉमस अहले

60

स्केल 2.8 में, आप नाम और डिफ़ॉल्ट मापदंडों के साथ-साथ copyकेस क्लास पर विधि का उपयोग कर सकते हैं । यहाँ कुछ उदाहरण कोड है:

case class Person(name: String, age: Int, children: List[Person] = List()) {
  def addChild(p: Person) = copy(children = p :: this.children)
}

val parent = Person(name = "Bob", age = 55)
  .addChild(Person("Lisa", 23))
  .addChild(Person("Peter", 16))

31
स्काला भाषा का आविष्कार करने के लिए +1। हाँ, यह प्रतिष्ठा प्रणाली का दुरुपयोग है लेकिन ... aww ... मुझे स्काला से बहुत प्यार है, मुझे यह करना था। :)
मालाक्स

1
बेनाम: ओह, आदमी ... मैं बस लगभग समान कुछ उत्तर दिया है! वैसे, मैं अच्छी कंपनी में हूं। :-) मुझे आश्चर्य है कि मैंने आपका उत्तर पहले नहीं देखा, हालांकि ... <shrug>
डैनियल सी। सोबराल

20

खैर, इस पर विचार करें 2.8:

case class Person(name: String, 
                  married: Boolean = false, 
                  espouse: Option[String] = None, 
                  children: Set[String] = Set.empty) {
  def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
  def addChild(whom: String) = this.copy(children = children + whom)
}

scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))

यह निश्चित रूप से अपनी समस्याओं का हिस्सा है। उदाहरण के लिए, बनाने का प्रयास करें espouseऔर Option[Person], और उसके बाद दो लोगों को एक-दूसरे से शादी हो रही है। मैं एक private varऔर / या एक privateनिर्माता प्लस एक कारखाने का सहारा लिए बिना हल करने का एक तरीका नहीं सोच सकता ।


11

यहाँ कुछ और विकल्प दिए गए हैं:

विकल्प 1

कार्यान्वयन को स्वयं परिवर्तनशील बनाएं, लेकिन उन अंतरों को अलग करें जिन्हें यह परिवर्तनशील और अपरिवर्तनीय बनाता है। यह स्विंग लाइब्रेरी डिज़ाइन से लिया गया है।

public interface Foo {
  X getX();
  Y getY();
}

public interface MutableFoo extends Foo {
  void setX(X x);
  void setY(Y y);
}

public class FooImpl implements MutableFoo {...}

public SomeClassThatUsesFoo {
  public Foo makeFoo(...) {
    MutableFoo ret = new MutableFoo...
    ret.setX(...);
    ret.setY(...);
    return ret; // As Foo, not MutableFoo
  }
}

विकल्प 2

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


3
विकल्प 1 चतुर है (लेकिन बहुत चालाक नहीं है ), इसलिए मुझे यह पसंद है।
तीमुथियुस

4
मैंने ऐसा पहले किया था, लेकिन मेरी राय में यह एक अच्छा समाधान होने से बहुत दूर है क्योंकि वस्तु अभी भी परिवर्तनशील है, केवल उत्परिवर्तन विधियां "छिपी" हैं। हो सकता है कि मैं भी इस विषय को लेकर बहुत अछूता रहा हो ...
मैलाक्स

विकल्प 1 पर एक प्रकार के लिए अपरिवर्तनीय और म्यूटेबल वेरिएंट के लिए अलग-अलग कक्षाएं हैं। यह इंटरफ़ेस दृष्टिकोण की तुलना में बेहतर सुरक्षा प्रदान करता है। यकीनन आप एपीआई के उपभोक्ता से झूठ बोल रहे हैं जब भी आप उन्हें अपरिवर्तनीय नाम की एक परिवर्तनशील वस्तु देते हैं, जिसे बस इसे उत्परिवर्ती इंटरफ़ेस में डालने की आवश्यकता होती है। अलग वर्ग के दृष्टिकोण को आगे और पीछे परिवर्तित करने के तरीकों की आवश्यकता होती है। JodaTime API इस पैटर्न का उपयोग करता है। DateTime और MutableDateTime देखें।
टूलबार

6

यह याद रखने में मदद करता है कि विभिन्न प्रकार की अपरिवर्तनीयता हैं । आपके मामले के लिए, मुझे लगता है कि "popsicle" अपरिवर्तनीयता वास्तव में अच्छी तरह से काम करेगी:

पोप्सिकल इम्युटेबिलिटी: जिसे मैं लिखने-लिखने में एक बार कमजोर पड़ने की हल्की कमजोरी कहता हूं। कोई एक ऐसी वस्तु या क्षेत्र की कल्पना कर सकता है जो इसके आरंभ के दौरान थोड़ी देर के लिए परस्पर स्थिर रहे, और फिर हमेशा के लिए "स्थिर" हो गया। इस तरह की अपरिवर्तनीयता अपरिवर्तनीय वस्तुओं के लिए विशेष रूप से उपयोगी है जो एक दूसरे का संदर्भ देते हैं, या अपरिवर्तनीय वस्तुओं को डिस्क पर अनुक्रमित किया जाता है और deserialization को "द्रव" होने की आवश्यकता होती है जब तक कि पूरी deserialization प्रक्रिया नहीं की जाती है, जिस बिंदु पर सभी ऑब्जेक्ट हो सकते हैं जमे हुए।

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


1
Downvotes? कोई भी एक टिप्पणी छोड़ना चाहता है कि यह एक अच्छा समाधान क्यों नहीं है?
जूलियट १

+1। हो सकता है कि किसी ने इसे ठुकरा दिया हो क्योंकि आप clone()नए उदाहरण प्राप्त करने के लिए उपयोग कर रहे हैं ।
फाइनव

या शायद यह इसलिए था क्योंकि यह जावा में थ्रेड सुरक्षा से समझौता कर सकता है: java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5
फिन

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

5

आप अपरिवर्तनीय वस्तुओं को उत्परिवर्तित करने वाले तरीकों (जैसे AddSibling) की तरह प्रकट कर सकते हैं, लेकिन उन्हें एक नया उदाहरण वापस करने दें। यही अपरिवर्तनीय स्काला संग्रह करता है।

नकारात्मक पक्ष यह है कि आप आवश्यकता से अधिक उदाहरण बना सकते हैं। यह तब भी लागू होता है जब मध्यवर्ती वैध कॉन्फ़िगरेशन मौजूद होते हैं (जैसे भाई-बहनों के बिना कुछ नोड जो ज्यादातर मामलों में ठीक है) जब तक कि आप आंशिक रूप से निर्मित वस्तुओं से निपटना नहीं चाहते हैं।

उदाहरण के लिए एक ग्राफ एज जिसके पास कोई गंतव्य नहीं है अभी तक एक वैध ग्राफ बढ़त नहीं है।


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

2
@gustafc: हां। क्लिफ क्लिक ने एक बार अपने बड़े बक्से (864 कोर, 768 जीबी रैम) में से एक पर रिच हिक्की के क्लोजर एंट कॉलोनी सिमुलेशन को बेंचमार्क करने की कहानी बताई: 700 समानांतर धागे चल रहे हैं 700 कोर, प्रत्येक 100% पर, 20 जीबी से अधिक उत्पन्न प्रति सेकंड कचरा । GC ने एक पसीना भी नहीं छोड़ा।
जोर्ज डब्ल्यू मित्तग

5

चार संभावनाओं पर विचार करें:

new Immutable(one, fish, two, fish, red, fish, blue, fish); /*1 */

params = new ImmutableParameters(); /*2 */
params.setType("fowl");
new Immutable(params);

factory = new ImmutableFactory(); /*3 */
factory.setType("fish");
factory.getInstance();

Immutable boringImmutable = new Immutable(); /* 4 */
Immutable lessBoring = boringImmutable.setType("vegetable");

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

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

अंत में, किसी और के उत्तर का उल्लेख विकल्प (4) है, जहां आपके पास बहुत कम अपरिवर्तनीय वस्तुएं हैं और पुराने लोगों से समाचार प्राप्त करने के लिए बेहतर पैटर्न है।

ध्यान दें कि मैं 'पैटर्न फैन क्लब' का सदस्य नहीं हूं - निश्चित रूप से, कुछ चीजें अनुकरण के लायक हैं, लेकिन यह मुझे लगता है कि वे अपने स्वयं के अनजाने जीवन को लेते हैं जब लोग उन्हें नाम और मजाकिया टोपी देते हैं।


6
यह बिल्डर पैटर्न (विकल्प 2) है
साइमन निकर्सन

क्या वह कारखाना ('बिल्डर') वस्तु नहीं होगी जो उसकी अपरिवर्तनीय वस्तुओं का उत्सर्जन करती है?
bmargulies

यह अंतर बहुत अर्थपूर्ण लगता है। बीन कैसे बनती है? कैसे एक बिल्डर से अलग है?
कार्ल

आप एक बिल्डर के लिए जावा बीन सम्मेलनों (या बहुत कुछ के लिए) का पैकेज नहीं चाहते हैं।
टॉम हॉल्टिन -

4

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


1
नोट: इस उत्तर का माइलेज समस्या, कोड-बेस और डेवलपर कौशल के साथ भिन्न हो सकता है।
कार्ल

2

मैं C # का उपयोग करता हूं, और ये मेरे दृष्टिकोण हैं। विचार करें:

class Foo
{
    // private fields only to be written inside a constructor
    private readonly int i;
    private readonly string s;
    private readonly Bar b;

    // public getter properties
    public int I { get { return i; } }
    // etc.
}

विकल्प 1. वैकल्पिक मापदंडों के साथ कंस्ट्रक्टर

public Foo(int i = 0, string s = "bla", Bar b = null)
{
    this.i = i;
    this.s = s;
    this.b = b;
}

उदाहरण के लिए प्रयुक्त new Foo(5, b: new Bar(whatever))। 4.0 से पहले जावा या सी # संस्करणों के लिए नहीं। लेकिन फिर भी दिखाने लायक है, क्योंकि यह एक उदाहरण है कि कैसे सभी समाधान भाषा अज्ञेय नहीं हैं।

विकल्प 2. एकल पैरामीटर ऑब्जेक्ट लेने वाला कंस्ट्रक्टर

public Foo(FooParameters parameters)
{
    this.i = parameters.I;
    // etc.
}

class FooParameters
{
    // public properties with automatically generated private backing fields
    public int I { get; set; }
    public string S { get; set; }
    public Bar B { get; set; }

    // All properties are public, so we don't need a full constructor.
    // For convenience, you could include some commonly used initialization
    // patterns as additional constructors.
    public FooParameters() { }
}

उपयोग उदाहरण:

FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`

3.0 पर C # से यह वस्तु इनिशियलाइज़र सिंटैक्स के साथ और अधिक सुरुचिपूर्ण बनाता है (शब्दार्थ पिछले उदाहरण के बराबर):

FooParameters fp = new FooParameters { I = 5, S = "bla", B = new Bar() };
Foo f = new Foo(fp);

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

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