คุณสมบัติอ่านอย่างเดียวที่คำนวณเทียบกับฟังก์ชันใน Swift


98

ในเซสชัน Introduction to Swift WWDC คุณสมบัติอ่านอย่างเดียว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 ทั่วไป แต่ฉันอยากทราบคุณสมบัติเฉพาะของ Swift ที่จะแนะนำแนวทางปฏิบัติที่ดีที่สุดเมื่อใช้ภาษานี้


1
ชม 204 เซสชัน - "เมื่อใดที่ไม่ควรใช้ @property" มีเคล็ดลับ
Kostiantyn Koval

4
เดี๋ยวก่อนคุณสามารถสร้างคุณสมบัติแบบอ่านอย่างเดียวและข้ามget {}? ฉันไม่รู้ขอบคุณ!
Dan Rosenstark

WWDC14 เซสชัน 204 ได้ที่นี่ (วิดีโอและสไลด์), developer.apple.com/videos/play/wwdc2014/204
user3207158

คำตอบ:


54

สำหรับฉันแล้วดูเหมือนว่าส่วนใหญ่จะเป็นเรื่องของรูปแบบ: ฉันชอบใช้คุณสมบัติเพียงอย่างเดียว: คุณสมบัติ; หมายถึงค่าง่ายๆที่คุณจะได้รับและ / หรือตั้งค่า ฉันใช้ฟังก์ชัน (หรือวิธีการ) เมื่อต้องทำงานจริง อาจจะต้องคำนวณหรืออ่านบางอย่างจากดิสก์หรือจากฐานข้อมูล: ในกรณีนี้ฉันใช้ฟังก์ชันแม้ว่าจะส่งคืนค่าธรรมดาเท่านั้น ด้วยวิธีนี้ฉันสามารถดูได้อย่างง่ายดายว่าการโทรนั้นถูก (คุณสมบัติ) หรืออาจแพง (ฟังก์ชัน)

เราอาจจะได้รับความชัดเจนมากขึ้นเมื่อ Apple เผยแพร่อนุสัญญาการเข้ารหัส Swift บางส่วน


12

ดีที่คุณสามารถนำไปใช้ Kotlin 's คำแนะนำhttps://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties

ในบางกรณีฟังก์ชันที่ไม่มีอาร์กิวเมนต์อาจใช้แทนกันได้กับคุณสมบัติอ่านอย่างเดียว แม้ว่าความหมายจะคล้ายกัน แต่ก็มีการประชุมโวหารบางประการเกี่ยวกับเวลาที่จะชอบอีกแบบหนึ่ง

ชอบคุณสมบัติมากกว่าฟังก์ชันเมื่ออัลกอริทึมพื้นฐาน:

  • ไม่โยน
  • ความซับซ้อนมีราคาถูกในการคำนวณ (หรือคำนวณในการวิ่งครั้งแรก)
  • ส่งคืนผลลัพธ์เดียวกันในการเรียกใช้

1
คำแนะนำ "มี O (1)" จะไม่รวมอยู่ในคำแนะนำนั้นอีกต่อไป
David Pettigrew

แก้ไขเพื่อสะท้อนการเปลี่ยนแปลงของ Kotlin
Carsten Hagemann

11

ในขณะที่คำถามเกี่ยวกับคุณสมบัติที่คำนวณเทียบกับวิธีการโดยทั่วไปนั้นยากและเป็นอัตวิสัย แต่ปัจจุบันมีข้อโต้แย้งที่สำคัญอย่างหนึ่งในกรณีของ Swift สำหรับการเลือกวิธีการมากกว่าคุณสมบัติ คุณสามารถใช้วิธีการใน Swift เป็นฟังก์ชันบริสุทธิ์ซึ่งไม่เป็นความจริงสำหรับคุณสมบัติ (ณ Swift 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} - ส่งคืนผลลัพธ์เดียวกัน เป็นตัวอย่างที่แก้ไขจากเอกสารของ apple ใน Array.filter () และเข้าใจง่ายกว่ามาก
poGUIst

7

เนื่องจากรันไทม์เหมือนกันคำถามนี้จึงใช้กับ Objective-C เช่นกัน ฉันจะบอกว่าด้วยคุณสมบัติที่คุณจะได้รับ

  • ความเป็นไปได้ในการเพิ่มตัวเซ็ตในคลาสย่อยทำให้คุณสมบัติ readwrite
  • ความสามารถในการใช้ KVO /didSetสำหรับการแจ้งเตือนการเปลี่ยนแปลง
  • โดยทั่วไปคุณสามารถส่งคุณสมบัติไปยังเมธอดที่คาดว่าจะมีพา ธ หลักเช่นการจัดเรียงคำร้องขอดึงข้อมูล

สำหรับสิ่งที่เฉพาะเจาะจงสำหรับ Swift ตัวอย่างเดียวที่ฉันมีคือคุณสามารถใช้@lazyสำหรับคุณสมบัติ


7

มีความแตกต่าง: หากคุณใช้พร็อพเพอร์ตี้คุณสามารถแทนที่มันและทำให้มันอ่าน / เขียนในคลาสย่อยได้ในที่สุด


9
คุณยังสามารถแทนที่ฟังก์ชันได้เช่นกัน หรือเพิ่ม setter เพื่อให้สามารถเขียนได้
Johannes Fahrenkrug

คุณสามารถเพิ่ม setter หรือกำหนดคุณสมบัติที่เก็บไว้เมื่อคลาสฐานกำหนดชื่อเป็นฟังก์ชัน? แน่นอนคุณสามารถทำได้ถ้ามันกำหนดคุณสมบัติ (นั่นคือประเด็นของฉัน) แต่ฉันไม่คิดว่าคุณจะทำได้ถ้ามันกำหนดฟังก์ชัน
ไฟล์อนาล็อก

เมื่อ Swift มีคุณสมบัติส่วนตัว (ดูที่นี่stackoverflow.com/a/24012515/171933 ) คุณสามารถเพิ่มฟังก์ชัน setter ให้กับคลาสย่อยของคุณเพื่อตั้งค่าคุณสมบัติส่วนตัวนั้นได้ เมื่อฟังก์ชัน getter ของคุณเรียกว่า "name" ตัวตั้งค่าของคุณจะถูกเรียกว่า "setName" ดังนั้นจึงไม่มีข้อขัดแย้งในการตั้งชื่อ
Johannes Fahrenkrug

คุณสามารถทำได้แล้ว (ความแตกต่างคือคุณสมบัติที่จัดเก็บไว้ที่คุณใช้สำหรับการสนับสนุนจะเป็นแบบสาธารณะ) แต่ OP ถามว่ามีความแตกต่างระหว่างการประกาศคุณสมบัติแบบอ่านอย่างเดียวหรือฟังก์ชันในฐานหรือไม่ หากคุณประกาศคุณสมบัติแบบอ่านอย่างเดียวคุณสามารถทำให้เป็นแบบอ่าน - เขียนในคลาสที่ได้รับ ส่วนขยายที่เพิ่มwillSetและdidSetลงในคลาสพื้นฐานโดยไม่ทราบอะไรเกี่ยวกับคลาสที่ได้รับในอนาคตสามารถตรวจจับการเปลี่ยนแปลงในคุณสมบัติที่ถูกแทนที่ได้ แต่คุณไม่สามารถทำอะไรแบบนั้นด้วยฟังก์ชันได้ฉันคิดว่า
ไฟล์อนาล็อก

คุณจะแทนที่คุณสมบัติแบบอ่านอย่างเดียวเพื่อเพิ่มตัวตั้งค่าได้อย่างไร ขอบคุณ. ฉันเห็นสิ่งนี้ในเอกสาร "คุณสามารถนำเสนอคุณสมบัติอ่านอย่างเดียวที่สืบทอดมาเป็นคุณสมบัติอ่านเขียนได้โดยให้ทั้ง getter และ setter ในคุณสมบัติ subclass ของคุณแทนที่" แต่ ... ตัวแปรใดที่ setter เขียนถึง?
Dan Rosenstark

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
นี้เป็นที่น่าสนใจ แต่คุณสามารถให้ตัวอย่างของที่คำนวณคุณสมบัติสำหรับการอ่านเท่านั้นที่จะนำมาใช้เมื่อทำตามหลักการนี้? บางทีฉันอาจจะผิด แต่ข้อโต้แย้งของคุณดูเหมือนจะชี้ให้เห็นว่าไม่ควรใช้เนื่องจากตามความหมายคุณสมบัติอ่านอย่างเดียวที่คำนวณไม่เคยประกอบด้วยสถานะ
Stuart

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จะได้ผลลัพธ์ที่น่าเชื่อถือมากขึ้น
Cristik

1

คุณสมบัติที่คำนวณตามความหมายควรควบคู่ไปกับสถานะที่แท้จริงของวัตถุ - หากคุณสมบัติอื่น ๆ ไม่เปลี่ยนแปลงดังนั้นการสืบค้นคุณสมบัติที่คำนวณในเวลาที่ต่างกันควรให้ผลลัพธ์ที่เหมือนกัน (เทียบเคียงผ่าน == หรือ ===) - คล้ายกัน เพื่อเรียกใช้ฟังก์ชันบริสุทธิ์บนวัตถุนั้น

ในทางกลับกันวิธีการนั้นออกมานอกกรอบโดยมีข้อสันนิษฐานว่าเราอาจไม่ได้ผลลัพธ์เหมือนกันเสมอไปเพราะ Swift ไม่มีวิธีทำเครื่องหมายฟังก์ชันว่าบริสุทธิ์ นอกจากนี้วิธีการใน OOP ถือเป็นการกระทำซึ่งหมายความว่าการดำเนินการดังกล่าวอาจส่งผลข้างเคียง หากวิธีการนี้ไม่มีผลข้างเคียงก็สามารถแปลงเป็นคุณสมบัติที่คำนวณได้อย่างปลอดภัย

โปรดทราบว่าข้อความทั้งสองข้อข้างต้นล้วนมาจากมุมมองเชิงความหมายเนื่องจากคุณสมบัติที่คำนวณแล้วอาจมีผลข้างเคียงที่เราไม่คาดคิดและวิธีการจะบริสุทธิ์


0

คำอธิบายในอดีตเป็นคุณสมบัติของ NSObject และหลายคนคาดหวังว่าจะยังคงเหมือนเดิมใน Swift การเพิ่ม parens หลังจากนั้นจะเพิ่มความสับสนเท่านั้น

แก้ไข: หลังจากการลงคะแนนอย่างโกรธเกรี้ยวฉันต้องชี้แจงบางสิ่งบางอย่าง - หากมีการเข้าถึงผ่าน dot syntax ก็ถือได้ว่าเป็นคุณสมบัติ ไม่สำคัญว่ามีอะไรอยู่ใต้ฝากระโปรง คุณไม่สามารถเข้าถึงวิธีการปกติด้วยไวยากรณ์จุด

นอกจากนี้การเรียกคุณสมบัตินี้ไม่จำเป็นต้องมีค่าเบี้ยประกันพิเศษเช่นในกรณีของ Swift ซึ่งอาจทำให้เกิดความสับสน


1
ที่จริงนี้ไม่ถูกต้อง - descriptionเป็นจำเป็นต้องใช้วิธีการบนNSObjectโปรโตคอลและเพื่อวัตถุประสงค์ใน-C [myObject description]จะถูกส่งกลับโดยใช้ อย่างไรก็ตามคุณสมบัติdescriptionนั้นเป็นเพียงตัวอย่างที่สร้างขึ้น - ฉันกำลังมองหาคำตอบทั่วไปที่ใช้กับคุณสมบัติ / ฟังก์ชันที่กำหนดเอง
Stuart

1
ขอบคุณสำหรับคำชี้แจง ฉันยังไม่แน่ใจว่าฉันเห็นด้วยกับคำแถลงของคุณอย่างสมบูรณ์ว่าวิธี obj-c ที่ไม่มีพารามิเตอร์ใด ๆ ที่ส่งคืนค่าถือเป็นคุณสมบัติแม้ว่าฉันจะเข้าใจเหตุผลของคุณก็ตาม ตอนนี้ฉันจะถอนการโหวตลงของฉัน แต่ฉันคิดว่าคำตอบนี้กำลังอธิบายถึงเหตุผลของ 'ความหมาย' ที่กล่าวถึงแล้วในคำถามและความสอดคล้องระหว่างภาษาก็ไม่ใช่ปัญหาที่นี่เช่นกัน
Stuart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.