Обчислена властивість лише для читання проти функції Swift


98

У вступі до сеансу 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" Є кілька підказок
Костянтин Коваль

4
зачекайте, ви можете зробити властивість лише для читання і пропустити get {}? Я не знав цього, дякую!
Dan Rosenstark

WWDC14 Session 204 можна знайти тут (відео та слайди), developer.apple.com/videos/play/wwdc2014/204
користувач3207158

Відповіді:


53

Мені здається, що це здебільшого питання стилю: я дуже віддаю перевагу використанню властивостей саме для цього: властивості; що означає прості значення, які ви можете отримати та / або встановити. Я використовую функції (або методи), коли виконується фактична робота. Можливо, щось потрібно обчислити або прочитати з диска або з бази даних: У цьому випадку я використовую функцію, навіть коли повертається лише просте значення. Таким чином я легко бачу, дешевий дзвінок (властивості) чи, можливо, дорогий (функції).

Можливо, ми отримаємо більше ясності, коли Apple опублікує деякі правила кодування Swift.


12

Ну, ви можете застосувати поради Котліна https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

У деяких випадках функції без аргументів можуть бути взаємозамінними із властивостями лише для читання. Хоча семантика схожа, існують деякі стилістичні домовленості щодо того, коли віддавати перевагу одній перед іншою.

Віддайте перевагу властивості над функцією, коли основний алгоритм:

  • не кидає
  • складність дешево обчислити (або погашено в перший цикл)
  • повертає той самий результат для викликів

1
Пропозиція "має O (1)" більше не включається до цієї поради.
Девід Петтігрю

Відредаговано так, щоб відобразити зміни Котліна.
Карстен Хагеманн

11

Хоча питання обчислених властивостей проти методів загалом є важким і суб'єктивним, на даний момент у випадку Свіфта є один важливий аргумент щодо переваги методів над властивостями. Ви можете використовувати методи у Swift як чисті функції, що не відповідає властивостям (станом на Swift 2.0 beta). Це робить методи набагато потужнішими та кориснішими, оскільки вони можуть брати участь у функціональному складі.

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

Оскільки тривалість виконання однакова, це питання стосується і Objective-C. Я б сказав, з отриманими вами властивостями

  • можливість додавання сеттера в підклас, роблячи властивість readwrite
  • можливість використовувати KVO / didSetдля сповіщень про зміни
  • загалом, ви можете передати властивість методам, які очікують ключових шляхів, наприклад, сортування запитів на отримання

Що стосується чогось конкретного для Swift, єдиний приклад, який я маю, - це те, що ви можете використовувати @lazyдля власності.


7

Існує різниця: якщо ви використовуєте властивість, ви можете врешті-решт замінити його та змусити читати / писати у підкласі.


9
Ви також можете змінити функції. Або додайте сетер, щоб забезпечити можливість письма.
Йоганнес Фаренкруг

Ви можете додати сеттер або визначити збережену властивість, коли базовий клас визначив ім'я як функцію? Безумовно, ви можете це зробити, якщо він визначив властивість (це саме моя думка), але я не думаю, що ви можете це зробити, якщо він визначив функцію.
Аналоговий файл

Після того, як Swift має приватні властивості (див. Тут stackoverflow.com/a/24012515/171933 ), ви можете просто додати функцію setter до свого підкласу, щоб встановити цю приватну власність. Коли ваша функція getter називається "name", ваш сеттер буде називатися "setName", тому немає конфлікту імен.
Йоханнес Фаренкруг,

Ви можете це зробити вже (різниця полягає в тому, що збережене майно, яке ви використовуєте для підтримки, буде загальнодоступним). Але ОП запитав, чи є різниця між оголошенням властивості лише для читання або функцією в базі. Якщо ви оголошуєте властивість лише для читання, ви можете змусити її читати-записувати у похідному класі. Розширення, що додає willSetі didSetдо базового класу, не знаючи нічого про майбутні похідні класи, може виявити зміни в перекритому властивості. Але я не можу зробити щось подібне з функціями, я думаю.
Аналоговий файл

Як ви можете замінити властивість лише для читання, щоб додати сетер? Дякую. Я бачу це в документах: "Ви можете представити успадковану властивість лише для читання як властивість читання-запису, надаючи як getter, так і сеттер у вашому властивості підкласу", але ... в яку змінну пише сетер?
Дан Розенстарк

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, і багато хто очікував би, що він продовжує те саме в Swift. Додавання паролів після цього лише додасть плутанини.

EDIT: Після несанкціонованого скорочення я повинен щось уточнити - якщо це доступ до синтаксису точок, це може вважатися властивістю. Неважливо, що знаходиться під капотом. Ви не можете отримати доступ до звичайних методів із синтаксисом крапок.

Крім того, для виклику цього властивості не потрібні додаткові парени, як у випадку із Swift, що може призвести до плутанини.


1
Насправді це неправильно - descriptionє необхідним методом у NSObjectпротоколі, і тому в object-C повертається за допомогою [myObject description]. У будь-якому випадку, властивість descriptionбула просто надуманим прикладом - я шукаю більш загальну відповідь, яка стосується будь-якої користувацької властивості / функції.
Стюарт

1
Дякую за пояснення. Я все ще не впевнений, що я повністю згоден з вашим твердженням, що будь-який параметричний метод obj-c, який повертає значення, може вважатися властивістю, хоча я розумію ваші міркування. Я зараз відмовусь від свого голосування, але я думаю, що ця відповідь описує причину «семантики», про яку вже говорилося в запитанні, і міжмовна послідовність насправді тут теж не проблема.
Стюарт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.