Obliczona właściwość tylko do odczytu a funkcja w języku Swift

98

W sesji Wprowadzenie do Swift WWDC descriptionpokazano właściwość tylko do odczytu :

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

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

Czy istnieją jakiekolwiek implikacje dla wyboru powyższego podejścia zamiast użycia metody zamiast tego:

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

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

Wydaje mi się, że najbardziej oczywistymi powodami, dla których wybrałbyś obliczoną właściwość tylko do odczytu, są:

  • Semantyka - w tym przykładzie sensowne jest, descriptionaby być właściwością klasy, a nie działaniem, które wykonuje.
  • Zwięzłość / przejrzystość - zapobiega konieczności używania pustych nawiasów podczas pobierania wartości.

Oczywiście powyższy przykład jest zbyt prosty, ale czy istnieją inne dobre powody, aby wybrać jeden z nich? Na przykład, czy są jakieś cechy funkcji lub właściwości, które pomogłyby w podjęciu decyzji, której użyć?


NB Na pierwszy rzut oka wydaje się to dość powszechnym pytaniem OOP, ale chciałbym poznać wszelkie funkcje specyficzne dla Swift, które wskazywałyby na najlepsze praktyki podczas używania tego języka.

Stuart
źródło
1
Obejrzyj sesję 204 - „Kiedy nie używać @property” Zawiera kilka wskazówek
Kostiantyn Koval,
4
czekaj, możesz zrobić właściwość tylko do odczytu i pominąć get {}? Nie wiedziałem, dzięki!
Dan Rosenstark
WWDC14 Session 204 można znaleźć tutaj (wideo i slajdy), developer.apple.com/videos/play/wwdc2014/204
user3207158

Odpowiedzi:

53

Wydaje mi się, że to głównie kwestia stylu: zdecydowanie wolę używać właściwości właśnie do tego: właściwości; czyli proste wartości, które możesz uzyskać i / lub ustawić. Używam funkcji (lub metod), gdy wykonywana jest rzeczywista praca. Może trzeba coś obliczyć lub odczytać z dysku lub z bazy danych: w tym przypadku używam funkcji, nawet jeśli zwracana jest tylko prosta wartość. W ten sposób mogę łatwo sprawdzić, czy połączenie jest tanie (właściwości), czy prawdopodobnie drogie (funkcje).

Prawdopodobnie uzyskamy większą przejrzystość, gdy Apple opublikuje pewne konwencje kodowania Swift.

Johannes Fahrenkrug
źródło
12

Cóż, możesz zastosować porady Kotlina https://kotlinlang.org/docs/reference/coding-conventions.html#functions-vs-properties .

W niektórych przypadkach funkcje bez argumentów mogą być zamienne z właściwościami tylko do odczytu. Chociaż semantyka jest podobna, istnieją pewne konwencje stylistyczne dotyczące tego, kiedy należy preferować jedną od drugiej.

Preferuj właściwość zamiast funkcji, gdy podstawowy algorytm:

  • nie rzuca
  • złożoność jest tania do obliczenia (lub do pobrania za pierwszym razem)
  • zwraca ten sam wynik po wywołaniach
onmyway133
źródło
1
Sugestia „ma O (1)” nie jest już zawarta w tej radzie.
David Pettigrew
Edytowane, aby odzwierciedlić zmiany Kotlina.
Carsten Hagemann
11

Podczas gdy kwestia obliczonych właściwości w porównaniu z metodami jest ogólnie trudna i subiektywna, w przypadku Swifta istnieje jeden ważny argument przemawiający za preferowaniem metod nad właściwościami. Możesz używać metod w Swift jako czystych funkcji, co nie jest prawdą dla właściwości (od wersji Swift 2.0 beta). Dzięki temu metody są znacznie bardziej wydajne i użyteczne, ponieważ mogą uczestniczyć w składzie funkcjonalnym.

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)))
Max O
źródło
1
strings.filter {! $ (0) .isEmpty} - zwraca ten sam wynik. Jest to zmodyfikowana próbka z dokumentacji Apple w Array.filter (). I dużo łatwiej jest to zrozumieć.
poGUIst
7

Ponieważ środowisko wykonawcze jest takie samo, to pytanie dotyczy również celu-C. Powiedziałbym, z właściwościami, które dostajesz

  • możliwość dodania setera w podklasie, nadając właściwość readwrite
  • możliwość korzystania z KVO / didSetdo powiadomień o zmianach
  • bardziej ogólnie, możesz przekazać właściwość metodom, które oczekują ścieżek kluczy, np. sortowanie żądań pobierania

Jeśli chodzi o coś specyficznego dla języka Swift, jedynym przykładem, jaki mam, jest to, że można go użyć @lazydla właściwości.

ilya n.
źródło
7

Jest różnica: jeśli używasz właściwości, możesz ją ostatecznie przesłonić i sprawić, by była odczytywana / zapisywana w podklasie.

Plik analogowy
źródło
9
Możesz także przesłonić funkcje. Lub dodaj setera, aby zapewnić umiejętność pisania.
Johannes Fahrenkrug
Możesz dodać metodę ustawiającą lub zdefiniować przechowywaną właściwość, gdy klasa bazowa zdefiniowała nazwę jako funkcję? Z pewnością możesz to zrobić, jeśli zdefiniowałeś właściwość (dokładnie o to chodzi), ale nie sądzę, abyś mógł to zrobić, jeśli zdefiniował funkcję.
Plik analogowy
Gdy Swift ma prywatne właściwości (patrz tutaj stackoverflow.com/a/24012515/171933 ), możesz po prostu dodać funkcję ustawiającą do swojej podklasy, aby ustawić tę prywatną właściwość. Gdy funkcja pobierająca nazywa się „nazwa”, ustawiająca będzie nazywać się „setName”, więc nie ma konfliktu nazw.
Johannes Fahrenkrug
Możesz to już zrobić (różnica polega na tym, że przechowywana właściwość, której używasz do pomocy, będzie publiczna). Ale OP zapytał, czy istnieje różnica między zadeklarowaniem właściwości tylko do odczytu lub funkcji w bazie. Jeśli zadeklarujesz właściwość tylko do odczytu, możesz ustawić ją do odczytu i zapisu w klasie pochodnej. Rozszerzenie, które dodaje willSeti didSetdo klasy bazowej , bez znajomości przyszłych klas pochodnych, może wykrywać zmiany w przesłoniętej właściwości. Ale myślę, że nie można zrobić czegoś takiego z funkcjami.
Plik analogowy
Jak można zastąpić właściwość tylko do odczytu, aby dodać metodę ustawiającą? Dzięki. Widzę to w dokumentach: „Możesz przedstawić dziedziczoną właściwość tylko do odczytu jako właściwość do odczytu i zapisu, udostępniając zarówno metodę pobierającą, jak i ustawiającą w nadpisaniu właściwości podklasy”, ale… do jakiej zmiennej zapisuje ustawiający?
Dan Rosenstark
5

W przypadku tylko do odczytu, właściwość komputerowej powinno nie być uważane semantycznie równoważne metody, nawet wtedy, gdy zachowują się identycznie, ponieważ upuszczając funcdeklarację zaciera różnicę między ilościami, które składają się na stan z instancji i ilości, które są jedynie funkcje z stan. Oszczędzasz na wpisywaniu tekstu ()w witrynie wywoławczej, ale ryzykujesz utratę przejrzystości kodu.

Jako trywialny przykład rozważmy następujący typ wektora:

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

Deklarując długość jako metodę, staje się jasne, że jest to funkcja stanu, która zależy tylko od xi y.

Z drugiej strony, gdybyś miał wyrazić lengthjako obliczoną właściwość

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

wtedy, gdy kropka-tab-kompletne IDE na instancji VectorWithLengthAsProperty, to będzie wyglądać tak, jakby x, y, lengthbyły nieruchomości na równi, która jest konceptualnie błędne.

egnha
źródło
5
Jest to interesujące, ale czy możesz podać przykład, w którym obliczona właściwość tylko do odczytu byłaby używana podczas przestrzegania tej zasady? Może się mylę, ale twój argument wydaje się sugerować, że nigdy nie powinny być używane, ponieważ z definicji obliczona właściwość tylko do odczytu nigdy nie obejmuje stanu.
Stuart,
2

Są sytuacje, w których wolisz obliczoną właściwość od normalnych funkcji. Takich jak: zwrot pełnego imienia i nazwiska osoby. Znasz już imię i nazwisko. Tak więc fullNamewłaściwość jest własnością, a nie funkcją. W tym przypadku jest to właściwość computed (ponieważ nie możesz ustawić pełnej nazwy, możesz ją po prostu wyodrębnić, używając imienia i nazwiska)

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
William Kinaan
źródło
1

Z punktu widzenia wydajności wydaje się, że nie ma różnicy. Jak widać w wyniku benchmarku.

sens

main.swift fragment kodu:

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)

Wynik:

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

Na wykresie:

reper

Benjamin Wen
źródło
2
Date()nie nadaje się do testów porównawczych, ponieważ wykorzystuje zegar komputera, który podlega automatycznym aktualizacjom przez system operacyjny. mach_absolute_timeuzyska bardziej wiarygodne wyniki.
Cristik,
1

Mówiąc semantycznie, obliczone właściwości powinny być ściśle sprzężone z wewnętrznym stanem obiektu - jeśli inne właściwości się nie zmieniają, zapytanie o obliczoną właściwość w różnych momentach powinno dać te same dane wyjściowe (porównywalne przez == lub ===) - podobne do wywołania czystej funkcji na tym obiekcie.

Z drugiej strony metody wychodzą z pudełka przy założeniu, że nie zawsze możemy uzyskać takie same wyniki, ponieważ Swift nie ma sposobu na oznaczanie funkcji jako czystych. Również metody w OOP są uważane za akcje, co oznacza, że ​​ich wykonanie może spowodować skutki uboczne. Jeśli metoda nie ma skutków ubocznych, można ją bezpiecznie przekonwertować na obliczoną właściwość.

Zauważ, że oba powyższe stwierdzenia są czysto semantyczne, ponieważ może się zdarzyć, że obliczone właściwości będą miały skutki uboczne, których się nie spodziewamy, a metody będą czyste.

Cristik
źródło
0

Z historycznego punktu widzenia opis jest właściwością NSObject i wielu spodziewałoby się, że będzie on kontynuowany w Swift. Dodanie parens po nim tylko wprowadzi zamieszanie.

EDYCJA: Po wściekłym przegłosowaniu muszę coś wyjaśnić - jeśli dostęp do niego uzyskuje się za pomocą składni kropkowej, można to uznać za właściwość. Nie ma znaczenia, co jest pod maską. Nie możesz uzyskać dostępu do zwykłych metod ze składnią kropkową.

Poza tym wywołanie tej właściwości nie wymagało dodatkowych parenów, jak w przypadku Swifta, co może prowadzić do nieporozumień.

Dvole
źródło
1
Właściwie to jest niepoprawne - descriptionjest to metoda wymagana w NSObjectprotokole, więc w celu-C jest zwracane za pomocą [myObject description]. W każdym razie właściwość descriptionbyła po prostu wymyślonym przykładem - szukam bardziej ogólnej odpowiedzi, która dotyczy dowolnej niestandardowej właściwości / funkcji.
Stuart,
1
Dzięki za wyjaśnienie. Nadal nie jestem pewien, czy całkowicie zgadzam się z twoim stwierdzeniem, że każda metoda bez parametrów obj-c, która zwraca wartość, może być uznana za właściwość, chociaż rozumiem twoje rozumowanie. Na razie wycofam swój głos negatywny, ale myślę, że ta odpowiedź opisuje powód `` semantyki '', o którym już mowa w pytaniu, a spójność między językami również nie jest tutaj problemem.
Stuart