स्विफ्ट में कम्प्यूटेड रीड ओनली प्रॉपर्टी बनाम फंक्शन


98

डब्ल्यूडब्ल्यूडीसी सत्र के परिचय में, केवल पढ़ने के लिए संपत्ति descriptionका प्रदर्शन किया गया है:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description)

क्या इसके बजाय किसी विधि का उपयोग करने के लिए उपरोक्त दृष्टिकोण को चुनने के कोई निहितार्थ हैं:

class Vehicle {
    var numberOfWheels = 0
    func description() -> String {
        return "\(numberOfWheels) wheels"
    }
}

let vehicle = Vehicle()
println(vehicle.description())

यह मुझे लगता है कि सबसे स्पष्ट कारण आप केवल एक पढ़ने योग्य संपत्ति का चयन करेंगे:

  • शब्दार्थ - इस उदाहरण में यह descriptionवर्ग की संपत्ति होने के लिए समझ में आता है , बजाय इसके कि यह क्रिया करता है।
  • ब्रेविटी / स्पष्टता - मूल्य मिलने पर खाली कोष्ठक का उपयोग करने की आवश्यकता को रोकता है।

स्पष्ट रूप से उपरोक्त उदाहरण अत्यधिक सरल है, लेकिन क्या दूसरे को चुनने के लिए अन्य अच्छे कारण हैं? उदाहरण के लिए, क्या फ़ंक्शंस या गुणों की कुछ विशेषताएं हैं जो आपके निर्णय का मार्गदर्शन करेंगी कि किसका उपयोग करना है?


NB पहली नज़र में यह काफी सामान्य OOP प्रश्न लगता है, लेकिन मैं किसी भी स्विफ्ट-विशिष्ट विशेषताओं के बारे में जानने के लिए उत्सुक हूं जो इस भाषा का उपयोग करते समय सर्वोत्तम अभ्यास का मार्गदर्शन करेंगे।


1
देखो 204 सत्र - "जब @ प्रयोग न करने के लिए" यह कुछ सुझाव है
Kostiantyn Koval

4
प्रतीक्षा करें, आप केवल-पढ़ने के लिए संपत्ति कर सकते हैं और छोड़ सकते हैं get {}? मुझे नहीं पता था कि, धन्यवाद!
डैन रोसेन्स्टार्क

WWDC14 सत्र 204 यहां (वीडियो और स्लाइड), डेवलपर.
apple.com/videos/play/wwdc2014/204

जवाबों:


53

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

हम शायद अधिक स्पष्टता प्राप्त करेंगे जब एप्पल कुछ स्विफ्ट कोडिंग सम्मेलनों को प्रकाशित करेगा।


12

खैर, आप कोटलिन की सलाह https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties लागू कर सकते हैं ।

कुछ मामलों में बिना किसी तर्क के कार्य केवल पढ़ने योग्य गुणों के साथ विनिमेय हो सकते हैं। यद्यपि शब्दार्थ समान हैं, फिर भी एक से दूसरे को पसंद करने पर कुछ शैलीगत परंपराएँ हैं।

अंतर्निहित एल्गोरिथ्म पर एक फ़ंक्शन पर एक संपत्ति को प्राथमिकता दें:

  • फेंकता नहीं है
  • जटिलता की गणना करना सस्ता है (या पहले रन के लिए सतर्क)
  • इनवॉइस पर एक ही परिणाम देता है

1
"O (1)" का सुझाव अब उस सलाह में शामिल नहीं है।
डेविड पेटीग्रीव

कोटलिन के परिवर्तनों को प्रतिबिंबित करने के लिए संपादित।
कार्स्टन हेजमैन

11

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

func fflat<A, R>(f: (A) -> () -> (R)) -> (A) -> (R) {
    return { f($0)() }
}

func fnot<A>(f: (A) -> Bool) -> (A) -> (Bool) {
    return { !f($0) }
}

extension String {
    func isEmptyAsFunc() -> Bool {
        return isEmpty
    }
}

let strings = ["Hello", "", "world"]

strings.filter(fnot(fflat(String.isEmptyAsFunc)))

1
strings.filter {! $ (0) .isEmpty} - समान परिणाम देता है। यह Array.filter () पर सेब के प्रलेखन से संशोधित नमूना है। और इसे समझना बहुत आसान है।
PoGUIst

7

चूंकि रनटाइम समान है, इसलिए यह प्रश्न ऑब्जेक्टिव-सी पर भी लागू होता है। मैं कहता हूँ, आपको मिलने वाले गुणों के साथ

  • उपवर्ग में एक सेटर को जोड़ने, संपत्ति बनाने की संभावना readwrite
  • KVO का उपयोग करने की क्षमता / didSetसूचनाओं को बदलने के लिए
  • अधिक आम तौर पर, आप संपत्ति को उन तरीकों से पारित कर सकते हैं जो प्रमुख पथों की अपेक्षा करते हैं, उदाहरण के लिए सॉर्टिंग अनुरोध

स्विफ्ट के लिए कुछ विशिष्ट के रूप में, मेरे पास एकमात्र उदाहरण यह है कि आप @lazyएक संपत्ति के लिए उपयोग कर सकते हैं।


7

एक अंतर है: यदि आप एक संपत्ति का उपयोग करते हैं तो आप अंततः इसे ओवरराइड कर सकते हैं और इसे उपवर्ग में पढ़ / लिख सकते हैं।


9
आप कार्यों को ओवरराइड भी कर सकते हैं। या लेखन क्षमता प्रदान करने के लिए एक सेटर जोड़ें।
जोहान्स फारेनक्रग

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

एक बार स्विफ्ट के निजी गुण हैं (देखें यहां stackoverflow.com/a/24012515/171933 ), आप बस उस निजी संपत्ति को सेट करने के लिए अपने उपवर्ग में एक सेटर फ़ंक्शन जोड़ सकते हैं। जब आपके गेटर फ़ंक्शन को "नाम" कहा जाता है, तो आपके सेटर को "सेटनाम" कहा जाएगा, इसलिए कोई नामकरण संघर्ष नहीं होगा।
जोहान्स फारेनक्रग

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

आप एक सेटर जोड़ने के लिए केवल-पढ़ने के लिए संपत्ति को कैसे ओवरराइड कर सकते हैं? धन्यवाद। मैं इसे डॉक्स में देखता हूं, "आप विरासत में मिली संपत्ति को पठन-पाठन संपत्ति के रूप में प्रस्तुत कर सकते हैं, जो आपके उपवर्ग संपत्ति ओवरराइड में एक गेट्टर और सेटर दोनों प्रदान करके" लेकिन ... सेटर किस चर को लिखता है?
डैन रोसेनस्टार्क

5

केवल पढ़ने के मामले में, एक गणना की गई संपत्ति को एक विधि के समकक्ष नहीं माना जाना चाहिए , यहां तक ​​कि जब वे पहचान के साथ व्यवहार करते हैं, क्योंकि funcघोषणा को छोड़ने से मात्राओं के बीच का अंतर हो जाता है जिसमें एक उदाहरण और मात्राओं की स्थिति शामिल होती है जो केवल कार्यों का कार्य है राज्य। आप ()कॉल साइट पर टाइपिंग को बचाते हैं , लेकिन अपने कोड में स्पष्टता खोने का जोखिम उठाते हैं।

एक तुच्छ उदाहरण के रूप में, निम्नलिखित वेक्टर प्रकार पर विचार करें:

struct Vector {
    let x, y: Double
    func length() -> Double {
        return sqrt(x*x + y*y)
    }
}

लंबाई को एक विधि के रूप में घोषित करके, यह स्पष्ट है कि यह राज्य का एक कार्य है, जो केवल xऔर पर निर्भर करता है y

दूसरी ओर, यदि आप lengthएक गणना की गई संपत्ति के रूप में व्यक्त करना चाहते थे

struct VectorWithLengthAsProperty {
    let x, y: Double
    var length: Double {
        return sqrt(x*x + y*y)
    }
}

फिर जब आप का एक उदाहरण पर डॉट-टैब-पूरा अपने IDE में VectorWithLengthAsProperty, यह लगेगा मानो x, y, lengthएक समान स्तर है, जो धारणात्मक गलत है पर गुण थे।


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

2

ऐसी परिस्थितियां हैं जहां आप सामान्य कार्यों से अधिक संपत्ति की गणना करना पसंद करेंगे। जैसे: किसी व्यक्ति का पूरा नाम वापस करना। आप पहले से ही अंतिम नाम और अंतिम नाम जानते हैं। तो वास्तव में fullNameसंपत्ति एक संपत्ति है एक फ़ंक्शन नहीं है। इस मामले में, यह गणना की गई संपत्ति है (क्योंकि आप पूरा नाम निर्धारित नहीं कर सकते हैं, आप इसे पहले नाम और अंतिम नाम का उपयोग करके निकाल सकते हैं)

class Person{
    let firstName: String
    let lastName: String
    init(firstName: String, lastName: String){
        self.firstName = firstName
        self.lastName = lastName
    }
    var fullName :String{
        return firstName+" "+lastName
    }
}
let william = Person(firstName: "William", lastName: "Kinaan")
william.fullName //William Kinaan

1

प्रदर्शन के दृष्टिकोण से, कोई अंतर नहीं लगता है। जैसा कि आप बेंचमार्क परिणाम में देख सकते हैं।

सार

main.swift सांकेतिक टुकड़ा:

import Foundation

class MyClass {
    var prop: Int {
        return 88
    }

    func foo() -> Int {
        return 88
    }
}

func test(times: u_long) {
    func testProp(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }


    func testFunc(times: u_long) -> TimeInterval {
        let myClass = MyClass()
        let starting = Date()
        for _ in 0...times {
            _ = myClass.prop
        }
        let ending = Date()
        return ending.timeIntervalSince(starting)
    }

    print("prop: \(testProp(times: times))")
    print("func: \(testFunc(times: times))")
}

test(times: 100000)
test(times: 1000000)
test(times: 10000000)
test(times: 100000000)

आउटपुट:

prop: 0.0380070209503174 func: 0.0350250005722046 prop: 0.371925950050354 func: 0.363085985183716 prop: 3.4023300409317 func: 3.38373708724976 prop: 33.5842199325562 func: 34.8433820009232 Program ended with exit code: 0

चार्ट में:

बेंचमार्क


2
Date()बेंचमार्क के लिए उपयुक्त नहीं है क्योंकि यह कंप्यूटर घड़ी का उपयोग करता है, जो ऑपरेटिंग सिस्टम द्वारा स्वचालित अपडेट के अधीन है। mach_absolute_timeअधिक विश्वसनीय परिणाम प्राप्त होगा।
बजे क्रिस्टी जूल

1

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

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

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


0

ऐतिहासिक रूप से वर्णन NSObject पर एक संपत्ति है और कई लोग उम्मीद करेंगे कि यह स्विफ्ट में भी जारी रहे। इसके बाद parens जोड़ना केवल भ्रम पैदा करेगा।

EDIT: उग्र अधोगति के बाद मुझे कुछ स्पष्ट करना होगा - अगर इसे डॉट सिंटैक्स के माध्यम से एक्सेस किया जाता है, तो इसे एक संपत्ति माना जा सकता है। इससे कोई फर्क नहीं पड़ता कि हुड के नीचे क्या है। आप डॉट सिंटैक्स के साथ सामान्य तरीकों तक नहीं पहुँच सकते।

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


1
वास्तव में यह गलत है - प्रोटोकॉल पर descriptionएक आवश्यक विधिNSObject है, और इसलिए उद्देश्य-सी का उपयोग करके लौटा दिया जाता है [myObject description]। वैसे भी, संपत्ति descriptionकेवल एक आकस्मिक उदाहरण था - मैं एक अधिक सामान्य उत्तर की तलाश कर रहा हूं जो किसी भी कस्टम संपत्ति / फ़ंक्शन पर लागू होता है।
स्टुअर्ट

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