web-dev-qa-db-fra.com

Comment utiliser correctement les extensions de classe dans Swift?

Dans Swift, j'ai toujours utilisé des extensions pour étendre des types fermés et fournir des fonctionnalités pratiques et sans logique, comme des animations, des extensions mathématiques, etc. Cependant, comme les extensions sont des dépendances dures dispersées sur votre base de code, je réfléchis toujours trois fois avant de mettre en œuvre quelque chose comme une extension.

Dernièrement, cependant, j'ai vu que Apple suggère d'utiliser des extensions dans une mesure encore plus grande, par exemple en mettant en œuvre des protocoles en tant qu'extensions distinctes.

Autrement dit, si vous avez une classe A qui implémente le protocole B, vous vous retrouvez avec cette conception:

class A {
    // Initializers, stored properties etc.
}

extension A: B {
    // Protocol implementation
}

Lorsque vous entrez dans ce lapin, j'ai commencé à voir plus de code basé sur l'extension, comme:

fileprivate extension A {
    // Private, calculated properties
}

fileprivate extension A {
    // Private functions
}

Une partie de moi aime les éléments constitutifs que vous obtenez lorsque vous implémentez des protocoles dans des extensions distinctes. Cela rend les parties distinctes de la classe vraiment distinctes. Cependant, dès que vous héritez de cette classe, vous devrez modifier cette conception, car les fonctions d'extension ne peuvent pas être remplacées.

Je pense que la deuxième approche est ... intéressante. Une fois, une grande chose est que vous n'avez pas à annoter chaque propriété privée et à fonctionner comme privé, car vous pouvez le spécifier pour l'extension.

Cependant, cette conception divise également les propriétés stockées et non stockées, les fonctions publiques et privées, ce qui rend la "logique" de la classe plus difficile à suivre (écrire des classes plus petites, je sais). Cela, avec les problèmes de sous-classement, me fait m'arrêter un peu sur le porche de l'extension au pays des merveilles.

J'adorerais savoir comment la communauté Swift du monde regarde les extensions. Que pensez-vous? Y a-t-il une balle d'argent?

17
Daniel Saidi

Ce n'est que mon opinion, bien sûr, alors prenez ce que j'écrirai facilement.

J'utilise actuellement le extension-approach dans mes projets pour plusieurs raisons:

  • Le code est beaucoup plus propre : mes classes ne dépassent jamais 150 lignes et la séparation par les extensions rend mon code plus lisible et séparé par les responsabilités

Voici généralement à quoi ressemble une classe:

final class A {
    // Here the public and private stored properties
}

extension A {
    // Here the public methods and public non-stored properties
}

fileprivate extension A {
    // here my private methods
}

Les extensions peuvent être plus d'une, bien sûr, cela dépend de ce que fait votre classe. C'est tout simplement utile pour organiser votre code et le lire à partir de la barre supérieure Xcode

extension description

  • Cela me rappelle que Swift est un langage de programmation orientée protocole , pas un OOP langage. Il n'y a rien que vous ne puissiez pas faire avec le protocole et les extensions de protocole. Et je préfère utiliser des protocoles pour ajouter une couche de sécurité à mes classes/struct. Par exemple, j'écris habituellement mes modèles de cette façon:

    protocol User {
        var uid: String { get }
        var name: String { get }
    }
    
    final class UserModel: User {
        var uid: String
        var name: String
    
        init(uid: String, name: String) {
            self.uid = uid
            self.name = name
        }
    }
    

De cette façon, vous pouvez toujours modifier vos valeurs uid et name à l'intérieur de la classe UserModel, mais vous ne pouvez pas l'extérieur car vous ne gérerez que le User type de protocole.

10
Luca D'Alberti

J'utilise une approche similaire, qui peut être décrite en une phrase:

Trier les responsabilités d'un type en extensions

Voici des exemples d'aspects que je mets dans des extensions individuelles:

  • Interface principale d'un type, vue depuis un client.
  • Conformités au protocole (c'est-à-dire un protocole délégué, souvent privé).
  • Sérialisation (par exemple tout ce qui concerne NSCoding).
  • Parties d'un type qui vivent sur un thread d'arrière-plan, comme les rappels réseau.

Parfois, lorsque la complexité d'un seul aspect augmente, je divise même l'implémentation d'un type sur plusieurs fichiers.

Voici quelques détails qui décrivent comment je trie le code lié à l'implémentation:

  • L'accent est mis sur l'appartenance fonctionnelle.
  • Gardez les implémentations publiques et privées proches, mais séparées.
  • Ne divisez pas entre var et func.
  • Gardez tous les aspects de l'implémentation d'une fonctionnalité ensemble: types imbriqués, initialiseurs, conformités de protocole, etc.

Avantage

La principale raison de séparer les aspects d'un type est de le rendre plus facile à lire et à comprendre.

Lors de la lecture de code étranger (ou de mon ancien code), comprendre la vue d'ensemble est souvent la partie la plus difficile de plonger. Donner au développeur une idée du contexte d'une méthode aide beaucoup.

Il y a un autre avantage: le contrôle d'accès permet de ne pas appeler quelque chose par inadvertance. Une méthode qui n'est censée être appelée qu'à partir d'un thread d'arrière-plan peut être déclarée private dans l'extension "background". Maintenant, il ne peut tout simplement pas être appelé ailleurs.

Restrictions actuelles

Swift 3 impose certaines restrictions à ce style. Il y a quelques choses qui ne peuvent vivre que dans l'implémentation du type principal:

  • propriétés stockées
  • surcharge fonc/var
  • func/var overidable
  • initialiseurs requis (désignés)

Ces restrictions (au moins les trois premières) proviennent de la nécessité de connaître à l'avance la disposition des données de l'objet (et la table témoin pour Swift pur). Les extensions peuvent potentiellement être chargées tardivement pendant l'exécution (via des frameworks, des plugins, dlopen, ...) et changer la disposition du type après la création des instances freinerait leur ABI.

Une proposition modeste pour l'équipe Swift :)

Tous les codes d'un module sont garantis disponibles en même temps. Les restrictions qui empêchent la séparation complète des aspects fonctionnels pourraient être contournées si le compilateur Swift permettait de "composer" les types dans un seul module. Avec la composition de types, je veux dire que le Le compilateur collecterait toutes les déclarations qui définissent la disposition d'un type à partir de tous les fichiers d'un module. Comme pour les autres aspects du langage, il trouverait automatiquement les dépendances intra-fichier.

Cela permettrait d'écrire vraiment des extensions "orientées aspect". Ne pas avoir à déclarer des propriétés stockées ou des remplacements dans la déclaration principale permettrait un meilleur contrôle d'accès et une séparation des préoccupations.

5
Nikolai Ruhe

Je déteste ça. Cela ajoute une complexité supplémentaire et rend l'utilisation des extensions plus floue, ce qui ne permet pas de savoir à quoi s'attendre que les utilisateurs utilisent les extensions.

Si vous utilisez une extension pour la conformité du protocole, OK, je peux le voir, mais pourquoi ne pas simplement commenter votre code? Comment est-ce mieux? Je ne vois pas ça.

0
Alex Zavatone