विधि जंजीर का उपयोग करते समय, क्या मैं ऑब्जेक्ट का पुन: उपयोग करता हूं या एक बनाता हूं?


37

जब विधि का उपयोग करते हुए जैसे:

var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();

दो दृष्टिकोण हो सकते हैं:

  • समान वस्तु का पुन: उपयोग करें, जैसे:

    public Car PaintedIn(Color color)
    {
        this.Color = color;
        return this;
    }
    
  • Carहर कदम पर एक नई प्रकार की वस्तु बनाएं , जैसे:

    public Car PaintedIn(Color color)
    {
        var car = new Car(this); // Clone the current object.
        car.Color = color; // Assign the values to the clone, not the original object.
        return car;
    }
    

क्या पहला गलत है या यह डेवलपर की व्यक्तिगत पसंद है?


मेरा मानना ​​है कि वह पहले दृष्टिकोण सहज / भ्रामक कोड का कारण बन सकता है। उदाहरण:

// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);

// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();

// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?

कोई विचार?


1
इसमें गलत क्या है var car = new Car(Brand.Ford, 12345, Color.Silver);?
जेम्स

12
@ जेम्स टेलीस्कोपिक कंस्ट्रक्टर, धाराप्रवाह पैटर्न वैकल्पिक और आवश्यक मापदंडों के बीच अंतर करने में मदद कर सकता है (यदि वे निर्माणकर्ता आवश्यक हैं, तो वैकल्पिक नहीं)। और धाराप्रवाह पढ़ने के लिए अच्छा है।
निमचम्पस्की

8
@NimChimpsky अच्छे पुराने जमाने के लिए (सी # के लिए) गुणों का क्या हुआ, और एक निर्माणकर्ता जिसके पास आवश्यक फ़ील्ड हैं - ऐसा नहीं है कि मैं धाराप्रवाह एपीआई को नष्ट कर रहा हूं, मैं एक बड़ा प्रशंसक हूं लेकिन वे अक्सर उपयोग में नहीं आते हैं
क्रिस

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

1
@NimChimpsky यह देख सकते हैं कि धाराप्रवाह जावा के लिए कितनी बड़ी छलांग है
क्रिस एस

जवाबों:


41

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

  • Car जो डोमेन ऑब्जेक्ट है
  • CarBuilder जो धाराप्रवाह एपीआई रखता है

उपयोग इस तरह होगा:

var car = CarBuilder.BuildCar()
    .OfBrand(Brand.Ford)
    .OfModel(12345)
    .PaintedIn(Color.Silver)
    .Build();

CarBuilderवर्ग (मैं सी # नामकरण परंपरा यहाँ उपयोग कर रहा हूँ) इस प्रकार दिखाई देगा:

public class CarBuilder {

    private Car _car;

    /// Constructor
    public CarBuilder() {
        _car = new Car();
        SetDefaults();
    }

    private void SetDefaults() {
        this.OfBrand(Brand.Ford);
          // you can continue the chaining for 
          // other default values
    }

    /// Starts an instance of the car builder to 
    /// build a new car with default values.
    public static CarBuilder BuildCar() {
        return new CarBuilder();
    }

    /// Sets the brand
    public CarBuilder OfBrand(Brand brand) {
        _car.SetBrand(brand);
        return this;
    }

    // continue with OfModel(...), PaintedIn(...), and so on...
    // that returns "this" to allow method chaining

    /// Returns the built car
    public Car Build() {
        return _car;
    }

}

ध्यान दें कि यह वर्ग थ्रेड सुरक्षित नहीं होगा (प्रत्येक थ्रेड के लिए इसे स्वयं CarBuilder उदाहरण की आवश्यकता होगी)। यह भी ध्यान दें कि, भले ही धाराप्रवाह एक बहुत अच्छी अवधारणा है, यह संभवतः साधारण डोमेन ऑब्जेक्ट बनाने के उद्देश्य से ओवरकिल है।

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


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

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

नीचे एक उदाहरण दिया गया है कि दोनों स्थिर आंतरिक वर्ग कैसे करें और इसे बनाने वाली अपरिवर्तनीय वस्तु निर्माण को कैसे संभालें:

// the class that represents the immutable object
public class ImmutableWriter {

    // immutable variables
    private int _times; private string _write;

    // the "complex" constructor
    public ImmutableWriter(int times, string write) {
        _times = times;
        _write = write;
    }

    public void Perform() {
        for (int i = 0; i < _times; i++) Console.Write(_write + " ");
    }

    // static inner builder of the immutable object
    protected static class ImmutableWriterBuilder {

        // the variables needed to construct the immutable object
        private int _ii = 0; private string _is = String.Empty;

        public void Times(int i) { _ii = i; }

        public void Write(string s) { _is = s; }

        // The stuff is all built here
        public ImmutableWriter Build() {
            return new ImmutableWriter(_ii, _is);
        }

    }

    // factory method to get the builder
    public static ImmutableWriterBuilder GetBuilder() {
        return new ImmutableWriterBuilder();
    }
}

उपयोग निम्नलिखित होगा:

var writer = ImmutableWriter
                .GetBuilder()
                .Write("peanut butter jelly time")
                .Times(2)
                .Build();

writer.Perform();
// console writes: peanut butter jelly time peanut butter jelly time 

संपादित करें 2: टिप्पणियों में पीट ने जटिल डोमेन ऑब्जेक्ट्स के साथ यूनिट परीक्षण लिखने के संदर्भ में लंबर फ़ंक्शन के साथ बिल्डरों का उपयोग करने के बारे में एक ब्लॉग पोस्ट किया । यह बिल्डर को थोड़ा अधिक अभिव्यंजक बनाने के लिए एक दिलचस्प विकल्प है।

इसके CarBuilderबजाय आपको इस विधि की आवश्यकता है:

public static Car Build(Action<CarBuilder> buildAction = null) {
    var carBuilder = new CarBuilder();
    if (buildAction != null) buildAction(carBuilder);
    return carBuilder._car;
}

जिसका उपयोग इस प्रकार किया जा सकता है:

Car c = CarBuilder
    .Build(car => 
        car.OfBrand(Brand.Ford)
           .OfModel(12345)
           .PaintedIn(Color.Silver);

3
@ बकायदा यह उल्लिखित है जोश बलोच का प्रभावी जावा
निमचम्पस्की

6
@ बक्वेता को जावा देव, इहो के लिए पढ़ना आवश्यक है।
निमचम्पस्की

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

1
हम्म ... मैंने हमेशा बिल्डर पैटर्न build()(या Build()) की अंतिम विधि को बुलाया है , न कि उस प्रकार का नाम जिसे वह बनाता है ( Car()आपके उदाहरण में)। इसके अलावा, अगर Carवास्तव में एक अपरिवर्तनीय वस्तु है (उदाहरण के लिए, इसके सभी क्षेत्र हैं readonly), तब भी बिल्डर इसे म्यूट नहीं कर पाएगा, इसलिए Build()विधि नए उदाहरण के निर्माण के लिए जिम्मेदार हो जाती है। ऐसा करने का एक तरीका Carकेवल एक निर्माणकर्ता है, जो एक बिल्डर को अपने तर्क के रूप में लेता है; तो Build()विधि बस कर सकते हैं return new Car(this);
डैनियल प्राइडेन

1
मैंने लैम्बदास पर आधारित बिल्डरों को बनाने के लिए एक अलग दृष्टिकोण के बारे में ब्लॉग किया। पोस्ट को शायद संपादन की थोड़ी जरूरत है। मेरा संदर्भ ज्यादातर एक इकाई परीक्षण के दायरे के अंदर था, लेकिन इसे अन्य क्षेत्रों में भी लागू किया जा सकता है। यह यहाँ पाया जा सकता है: petesdotnet.blogspot.com/2012/05/…
पीट

9

वह निर्भर करता है।

क्या आपकी कार एक इकाई या मूल्य वस्तु है ? यदि कार एक इकाई है, तो वस्तु पहचान महत्व की है, इसलिए आपको उसी संदर्भ को वापस करना चाहिए। यदि ऑब्जेक्ट एक मान ऑब्जेक्ट है, तो यह अपरिवर्तनीय होना चाहिए, जिसका अर्थ है कि हर बार एक नया उदाहरण वापस करना है।

बाद का एक उदाहरण .NET में डेटाइम क्लास होगा जो एक वैल्यू ऑब्जेक्ट है।

var date1 = new DateTime(2012,1,1);
var date2 = date1.AddDays(1);
// date2 now refers to Jan 2., while date1 remains unchanged at Jan 1.

हालाँकि, यदि मॉडल एक इकाई है, तो आपको ऑब्जेक्ट बनाने के लिए बिल्डर क्लास का उपयोग करने पर स्पोइक का जवाब पसंद है। दूसरे शब्दों में, आपने जो उदाहरण दिया है वह केवल आईएमएचओ को समझ में आता है यदि कार एक मूल्य वस्तु है।


1
'इकाई' बनाम 'मूल्य' प्रश्न के लिए +1। यह एक सवाल है कि क्या आपकी कक्षा एक उत्परिवर्ती या अपरिवर्तनीय प्रकार है (क्या इस वस्तु को बदलना चाहिए?), और पूरी तरह से आपके ऊपर है, हालांकि यह आपके डिजाइन को प्रभावित करेगा। जब तक विधि एक नई वस्तु नहीं लौटाती, तब तक मैं आमतौर पर एक म्यूट प्रकार पर काम करने की विधि की उम्मीद नहीं करूंगा।
केसी कुबाल

6

एक अलग स्थिर आंतरिक बिल्डर बनाएँ।

आवश्यक मापदंडों के लिए सामान्य निर्माता तर्क का उपयोग करें। और वैकल्पिक के लिए धाराप्रवाह एपीआई।

रंग सेट करते समय एक नया ऑब्जेक्ट न बनाएं, जब तक कि आप विधि का नाम बदलकर NewCarInColour या कुछ समान न करें।

मैं इस तरह से कुछ करना चाहूंगा जैसे वह ब्रांड और बाकी वैकल्पिक (यह जावा है, लेकिन तुम्हारा जावास्क्रिप्ट जैसा दिखता है, लेकिन बहुत यकीन है कि वे थोड़ा सा नाइट पिकिंग के साथ विनिमेय हैं):

Car yellowMercedes = new Car.Builder(Brand.MercedesBenz).PaintedIn(Color.Yellow).create();

Car specificYellowModel =new Car.Builder(Brand.MercedesBenz).WithModel(99).PaintedIn(Color.Yellow).create();

4

सबसे महत्वपूर्ण बात यह है कि आप जो भी निर्णय लेते हैं, वह स्पष्ट रूप से विधि के नाम और / या टिप्पणी में बताया गया है।

कोई मानक नहीं है, कभी-कभी विधि एक नई वस्तु लौटाएगी (अधिकांश स्ट्रिंग विधियां ऐसा करती हैं) या इस उद्देश्य के लिए उद्देश्य या स्मृति दक्षता के लिए वापस आ जाएगी)।

मैंने एक बार एक 3 डी वेक्टर ऑब्जेक्ट डिज़ाइन किया था और प्रत्येक गणित ऑपरेशन के लिए मेरे पास दोनों तरीके लागू थे। पैमाने की विधि के लिए तुरंत:

Vector3D scaleLocal(float factor){
    this.x *= factor; 
    this.y *= factor; 
    this.z *= factor; 
    return this;
}

Vector3D scale(float factor){
    Vector3D that = new Vector3D(this); // clone this vector
    return that.scaleLocal(factor);
}

3
+1। बहुत अच्छी बात है। मैं वास्तव में यह नहीं देखता कि यह क्यों घट गया। हालाँकि, मैं ध्यान दूंगा कि आपके द्वारा चुने गए नाम बहुत स्पष्ट नहीं हैं। मैं उन्हें scale(उत्परिवर्ती) और scaledBy(जनरेटर) कहूंगा ।
back2dos

अच्छा बिंदु, नाम स्पष्ट हो सकता है। नामकरण अन्य गणितीय वर्गों के एक सम्मेलन का पालन करता था जो मैंने एक पुस्तकालय से उपयोग किया था। प्रभाव भ्रम की स्थिति से बचने के लिए विधि के javadoc टिप्पणियों में भी कहा गया था।
XGouchet

3

मुझे यहाँ कुछ समस्याएं दिखती हैं जो मुझे लगता है कि भ्रमित हो सकती हैं ... प्रश्न में आपकी पहली पंक्ति:

var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();

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

// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);

// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();

फिर से बनाने के साथ, बस एक नए निर्माता के साथ नहीं। बात यह है, मुझे लगता है कि आप इसके बजाय एक प्रति () विधि की तलाश कर रहे हैं। तो अगर ऐसा है, और यह सिर्फ एक गरीब नाम है, तो एक बात पर गौर करें ... आप मर्सिडीज़ कहते हैं। पेंटेडिन (Color.Yellow) .Copy () - यह देखना आसान होना चाहिए और यह बताना चाहिए कि इसे 'चित्रित किया जा रहा है' 'नकल करने से पहले - तर्क का एक सामान्य प्रवाह, मेरे लिए। इसलिए पहले कॉपी लगाएं।

var yellowCar = mercedes.Copy().PaintedIn(Color.Yellow)

मेरे लिए, यह देखना आसान है कि आप कॉपी को पेंट कर रहे हैं, अपनी पीली कार बना रहे हैं।


+1 नए और क्रिएट () के बीच असंगति को इंगित करने के लिए;
यहोशू ड्रेक

1

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

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

आप जो भी करते हैं, एक ही वर्ग में नहीं मिलाते और करते हैं!


1

मैं बल्कि "एक्सटेंशन मेथड्स" तंत्र की तरह सोचता हूं।

public Car PaintedIn(this Car car, Color color)
{
    car.Color = color;
    return car;
}

0

यह उपरोक्त विधियों पर भिन्नता है। अंतर यह है कि कार क्लास पर स्थिर तरीके हैं जो बिल्डर पर विधि के नाम से मेल खाते हैं, इसलिए आपको स्पष्ट रूप से एक बिल्डर बनाने की आवश्यकता नहीं है:

Car car = Car.builder().ofBrand(Brand.Ford).ofColor("Green")...

आप उसी विधि के नाम का उपयोग कर सकते हैं जिसका उपयोग आप जंजीर बिल्डर कॉल पर करते हैं:

Car car = Car.ofBrand(Brand.Ford).ofColor("Green")...

इसके अलावा, वर्ग पर एक .copy () विधि है जो एक बिल्डर को वर्तमान उदाहरण से सभी मूल्यों के साथ आबादी देता है, इसलिए आप किसी विषय पर भिन्नता बना सकते हैं:

Car red = car.copy().paintedIn("Red").build();

अंत में, बिल्डर का .build () तरीका यह जांचता है कि सभी आवश्यक मान प्रदान किए गए हैं और यदि कोई लापता है तो फेंकता है। बिल्डर के निर्माता पर कुछ मूल्यों की आवश्यकता करना बेहतर हो सकता है और बाकी को वैकल्पिक होने की अनुमति दे सकता है; उस स्थिति में, आप अन्य उत्तरों में से एक पैटर्न चाहते हैं।

public enum Brand {
    Ford, Chrysler, GM, Honda, Toyota, Mercedes, BMW, Lexis, Tesla;
}

public class Car {
    private final Brand brand;
    private final int model;
    private final String color;

    public Car(Brand brand, int model, String color) {
        this.brand = brand;
        this.model = model;
        this.color = color;
    }

    public Brand getBrand() {
        return brand;
    }

    public int getModel() {
        return model;
    }

    public String getColor() {
        return color;
    }

    @Override public String toString() {
        return brand + " " + model + " " + color;
    }

    public Builder copy() {
        Builder builder = new Builder();
        builder.brand = brand;
        builder.model = model;
        builder.color = color;
        return builder;
    }

    public static Builder ofBrand(Brand brand) {
        Builder builder = new Builder();
        builder.brand = brand;
        return builder;
    }

    public static Builder ofModel(int model) {
        Builder builder = new Builder();
        builder.model = model;
        return builder;
    }

    public static Builder paintedIn(String color) {
        Builder builder = new Builder();
        builder.color = color;
        return builder;
    }

    public static class Builder {
        private Brand brand = null;
        private Integer model = null;
        private String color = null;

        public Builder ofBrand(Brand brand) {
            this.brand = brand;
            return this;
        }

        public Builder ofModel(int model) {
            this.model = model;
            return this;
        }

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

        public Car build() {
            if (brand == null) throw new IllegalArgumentException("no brand");
            if (model == null) throw new IllegalArgumentException("no model");
            if (color == null) throw new IllegalArgumentException("no color");
            return new Car(brand, model, color);
        }
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.