Thuộc tính chỉ đọc được tính toán so với hàm trong Swift


98

Trong phần Giới thiệu về Swift WWDC phiên, một thuộc tính chỉ đọc descriptionđược trình bày:

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

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

Có bất kỳ ý nghĩa nào đối với việc lựa chọn phương pháp trên thay vì sử dụng một phương pháp thay thế:

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

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

Đối với tôi, dường như lý do rõ ràng nhất mà bạn chọn thuộc tính được tính chỉ đọc là:

  • Ngữ nghĩa - trong ví dụ này, nó có ý nghĩa descriptionlà thuộc tính của lớp, hơn là một hành động mà nó thực hiện.
  • Brevity / Clarity - ngăn chặn sự cần thiết phải sử dụng dấu ngoặc đơn trống khi nhận giá trị.

Rõ ràng ví dụ trên quá đơn giản, nhưng có những lý do chính đáng nào khác để chọn cái này hơn cái kia không? Ví dụ: có một số tính năng của các chức năng hoặc thuộc tính sẽ hướng dẫn bạn quyết định sử dụng cái nào không?


NB Thoạt nhìn, đây có vẻ là một câu hỏi OOP khá phổ biến, nhưng tôi rất muốn biết về bất kỳ tính năng cụ thể nào của Swift sẽ hướng dẫn phương pháp thực hành tốt nhất khi sử dụng ngôn ngữ này.


1
Xem 204 phiên - "Khi không sử dụng @property" Nó có một số lời khuyên
Kostiantyn Koval

4
chờ đã, bạn có thể tạo thuộc tính chỉ đọc và bỏ qua get {}? Tôi không biết điều đó, cảm ơn!
Dan Rosenstark

WWDC14 Phiên 204 có thể được tìm thấy tại đây (video và trang trình bày), developer.apple.com/videos/play/wwdc2014/204
user3207158

Câu trả lời:


53

Đối với tôi, có vẻ như đó chủ yếu là vấn đề về phong cách: Tôi thực sự thích sử dụng các thuộc tính chỉ vì điều đó: thuộc tính; nghĩa là các giá trị đơn giản mà bạn có thể lấy và / hoặc đặt. Tôi sử dụng các chức năng (hoặc phương thức) khi công việc thực tế đang được thực hiện. Có thể một cái gì đó phải được tính toán hoặc đọc từ đĩa hoặc từ cơ sở dữ liệu: Trong trường hợp này, tôi sử dụng một hàm, ngay cả khi chỉ trả về một giá trị đơn giản. Bằng cách đó, tôi có thể dễ dàng xem một cuộc gọi là rẻ (thuộc tính) hay có thể đắt (chức năng).

Chúng ta có thể sẽ hiểu rõ hơn khi Apple công bố một số quy ước về mã hóa Swift.


12

Vâng, bạn có thể áp dụng lời khuyên của Kotlin https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

Trong một số trường hợp, các hàm không có đối số có thể hoán đổi cho nhau bằng các thuộc tính chỉ đọc. Mặc dù ngữ nghĩa tương tự nhau, nhưng có một số quy ước phong cách về thời điểm thích cái này hơn cái khác.

Ưu tiên một thuộc tính hơn một hàm khi thuật toán cơ bản:

  • không ném
  • độ phức tạp là rẻ để tính toán (hoặc được lưu trong lần chạy đầu tiên)
  • trả về cùng một kết quả so với lời gọi

1
Gợi ý "có một O (1)" không còn được bao gồm trong lời khuyên đó.
David Pettigrew

Đã chỉnh sửa để phản ánh những thay đổi của Kotlin.
Carsten Hagemann

11

Mặc dù câu hỏi về thuộc tính được tính toán so với phương thức nói chung là khó và chủ quan, nhưng hiện tại có một lập luận quan trọng trong trường hợp của Swift là thích phương thức hơn thuộc tính. Bạn có thể sử dụng các phương thức trong Swift dưới dạng các hàm thuần túy không đúng với các thuộc tính (kể từ phiên bản Swift 2.0 beta). Điều này làm cho các phương pháp trở nên mạnh mẽ và hữu ích hơn nhiều vì chúng có thể tham gia vào thành phần chức năng.

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
string.filter {! $ (0) .isEmpty} - trả về cùng một kết quả. Nó là mẫu được sửa đổi từ tài liệu apple trên Array.filter (). Và nó dễ hiểu hơn nhiều.
poGUIst

7

Vì thời gian chạy là như nhau, câu hỏi này cũng áp dụng cho Objective-C. Tôi muốn nói, với tài sản bạn nhận được

  • khả năng thêm một setter trong một lớp con, làm cho thuộc tính readwrite
  • khả năng sử dụng KVO / didSetcho các thông báo thay đổi
  • tổng quát hơn, bạn có thể chuyển thuộc tính cho các phương thức mong đợi đường dẫn chính, ví dụ: sắp xếp yêu cầu tìm nạp

Đối với một cái gì đó cụ thể cho Swift, ví dụ duy nhất mà tôi có là bạn có thể sử dụng @lazycho một thuộc tính.


7

Có một sự khác biệt: Nếu bạn sử dụng một thuộc tính thì cuối cùng bạn có thể ghi đè nó và làm cho nó đọc / ghi trong một lớp con.


9
Bạn cũng có thể ghi đè các chức năng. Hoặc thêm một bộ định vị để cung cấp khả năng viết.
Johannes Fahrenkrug

Bạn có thể thêm một setter hoặc xác định một thuộc tính được lưu trữ khi lớp cơ sở xác định tên dưới dạng một hàm không? Chắc chắn bạn có thể làm điều đó nếu nó xác định một thuộc tính (đó chính xác là quan điểm của tôi), nhưng tôi không nghĩ bạn có thể làm điều đó nếu nó xác định một hàm.
Tệp tương tự

Khi Swift có các thuộc tính private (xem tại đây stackoverflow.com/a/24012515/171933 ), bạn có thể chỉ cần thêm một hàm setter vào lớp con của mình để đặt thuộc tính riêng đó. Khi hàm getter của bạn được gọi là "name", setter của bạn sẽ được gọi là "setName", vì vậy không có xung đột đặt tên.
Johannes Fahrenkrug

Bạn có thể làm điều đó rồi (sự khác biệt là tài sản được lưu trữ mà bạn sử dụng để hỗ trợ sẽ được công khai). Nhưng OP đã hỏi liệu có sự khác biệt giữa việc khai báo thuộc tính chỉ đọc hoặc một hàm trong cơ sở hay không. Nếu bạn khai báo một thuộc tính chỉ đọc, thì bạn có thể làm cho nó đọc-ghi trong một lớp dẫn xuất. Một phần mở rộng bổ sung willSetdidSetvào lớp cơ sở , mà không cần biết bất kỳ điều gì về các lớp dẫn xuất trong tương lai, có thể phát hiện những thay đổi trong thuộc tính bị ghi đè. Nhưng bạn không thể làm bất cứ điều gì như vậy với các chức năng, tôi nghĩ.
Tệp tương tự

Làm cách nào bạn có thể ghi đè thuộc tính chỉ đọc để thêm một bộ định tuyến? Cảm ơn. Tôi thấy điều này trong tài liệu, "Bạn có thể trình bày thuộc tính chỉ đọc được kế thừa dưới dạng thuộc tính đọc-ghi bằng cách cung cấp cả getter và setter trong ghi đè thuộc tính lớp con của bạn" nhưng ... biến nào mà setter ghi vào?
Dan Rosenstark

5

Trong trường hợp chỉ đọc, một thuộc tính được tính toán không nên được coi là tương đương về mặt ngữ nghĩa với một phương thức, ngay cả khi chúng hoạt động giống nhau, bởi vì việc bỏ funckhai báo sẽ làm mờ sự phân biệt giữa các đại lượng bao gồm trạng thái của một thể hiện và các đại lượng chỉ đơn thuần là hàm của tiểu bang. Bạn lưu đánh máy() tại trang web cuộc gọi, nhưng có nguy cơ mất đi sự rõ ràng trong mã của bạn.

Như một ví dụ đơn giản, hãy xem xét loại vectơ sau:

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

Bằng cách khai báo độ dài dưới dạng một phương thức, rõ ràng đó là một hàm của trạng thái, chỉ phụ thuộc vào xy.

Mặt khác, nếu bạn thể hiện lengthdưới dạng một thuộc tính được tính toán

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

sau đó khi bạn dot-tab-đầy đủ trong IDE của bạn trên một thể hiện của VectorWithLengthAsProperty, nó sẽ trông như thể x, y, lengthlà tài sản trên cơ sở bình đẳng, đó là khái niệm không chính xác.


5
Điều này thật thú vị, nhưng bạn có thể đưa ra một ví dụ về nơi thuộc tính chỉ đọc được tính toán sẽ được sử dụng khi tuân theo nguyên tắc này không? Có thể tôi sai, nhưng lập luận của bạn dường như gợi ý rằng chúng không bao giờ nên được sử dụng, vì theo định nghĩa, thuộc tính chỉ đọc được tính toán không bao giờ bao gồm trạng thái.
Stuart

2

Có những tình huống mà bạn muốn thuộc tính được tính toán hơn các hàm bình thường. Chẳng hạn như: trả lại họ và tên của một người. Bạn đã biết tên và họ. Vì vậy, thực sự fullNametài sản là một tài sản không phải là một chức năng. Trong trường hợp này, nó là thuộc tính tính toán (vì bạn không thể đặt tên đầy đủ, bạn chỉ có thể trích xuất nó bằng cách sử dụng tên và họ)

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

Từ quan điểm hiệu suất, dường như không có sự khác biệt. Như bạn có thể thấy trong kết quả điểm chuẩn.

ý chính

main.swift đoạn mã:

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)

Đầu ra:

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

Trên biểu đồ:

điểm chuẩn


2
Date()không phù hợp với điểm chuẩn vì nó sử dụng đồng hồ máy tính, được cập nhật tự động bởi hệ điều hành. mach_absolute_timesẽ nhận được kết quả đáng tin cậy hơn.
Cristik,

1

Nói theo ngữ nghĩa, các thuộc tính được tính toán phải được kết hợp chặt chẽ với trạng thái nội tại của đối tượng - nếu các thuộc tính khác không thay đổi, thì việc truy vấn thuộc tính được tính toán tại các thời điểm khác nhau sẽ cho cùng một kết quả (có thể so sánh thông qua == hoặc ===) - tương tự để gọi một hàm thuần túy trên đối tượng đó.

Mặt khác, các phương thức xuất hiện với giả định rằng chúng ta có thể không phải lúc nào cũng nhận được kết quả giống nhau, bởi vì Swift không có cách nào để đánh dấu các hàm là thuần túy. Ngoài ra, các phương thức trong OOP được coi là các hành động, có nghĩa là việc thực thi chúng có thể dẫn đến các tác dụng phụ. Nếu phương pháp không có tác dụng phụ, thì nó có thể được chuyển đổi một cách an toàn thành thuộc tính tính toán.

Lưu ý rằng cả hai câu lệnh trên hoàn toàn là từ góc độ ngữ nghĩa, vì nó có thể xảy ra đối với các thuộc tính được tính toán có các tác dụng phụ mà chúng ta không mong đợi và các phương thức là thuần túy.


0

Mô tả lịch sử là một thuộc tính trên NSObject và nhiều người mong đợi rằng nó sẽ tiếp tục như vậy trong Swift. Thêm parens sau nó sẽ chỉ thêm sự nhầm lẫn.

CHỈNH SỬA: Sau khi phản đối dữ dội, tôi phải làm rõ điều gì đó - nếu nó được truy cập thông qua cú pháp dấu chấm, nó có thể được coi là một thuộc tính. Nó không quan trọng những gì dưới mui xe. Bạn không thể truy cập các phương pháp thông thường với cú pháp dấu chấm.

Ngoài ra, việc gọi thuộc tính này không yêu cầu thêm parens, giống như trường hợp của Swift, điều này có thể dẫn đến nhầm lẫn.


1
Trên thực tế, điều này là không chính xác - descriptionlà một phương thức bắt buộc trên NSObjectgiao thức, và do đó, trong mục tiêu-C được trả về bằng cách sử dụng [myObject description]. Dù sao, thuộc tính descriptionchỉ đơn giản là một ví dụ có sẵn - tôi đang tìm kiếm một câu trả lời chung chung hơn áp dụng cho bất kỳ thuộc tính / chức năng tùy chỉnh nào.
Stuart

1
Cảm ơn vì đã làm rõ. Tôi vẫn không chắc mình hoàn toàn đồng ý với tuyên bố của bạn rằng bất kỳ phương thức obj-c không tham số nào trả về giá trị đều có thể được coi là thuộc tính, mặc dù tôi hiểu lý do của bạn. Tôi sẽ rút lại phiếu bầu của mình ngay bây giờ, nhưng tôi nghĩ câu trả lời này đang mô tả lý do 'ngữ nghĩa' đã được đề cập trong câu hỏi và tính nhất quán giữa các ngôn ngữ cũng không thực sự là vấn đề ở đây.
Stuart
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.