Leaders d’opinion

Mise en œuvre des principes SOLID dans le développement Android

mm
Ajouter Unite.AI à vos sources préférées sur Google

Écrire des logiciels est un acte de création, et le développement Android ne fait pas exception. Il s’agit de plus que de simplement faire fonctionner quelque chose. Il s’agit de concevoir des applications qui peuvent grandir, s’adapter et rester gérables dans le temps.

En tant que développeur Android qui a rencontré de nombreux défis architecturaux, j’ai découvert que l’adhésion aux principes SOLID peut transformer même les codes les plus emmêlés en systèmes propres. Ces principes ne sont pas abstraits, mais des moyens résultat-orientés et reproductibles pour écrire du code robuste, évolutif et maintenable.

Cet article fournira des informations sur la façon dont les principes SOLID peuvent être appliqués au développement Android à travers des exemples du monde réel, des techniques pratiques et des expériences de l’équipe Meta WhatsApp.

Compréhension des principes SOLID

Les principes SOLID, proposés par Robert C. Martin, sont cinq principes de conception pour la programmation orientée objet qui garantissent une architecture logicielle propre et efficace.

  • Principe de responsabilité unique (SRP) : Une classe doit avoir une et une seule raison de changer.
  • Principe ouvert/fermé (OCP) : Les entités logicielles doivent être ouvertes pour l’extension mais fermées pour la modification.
  • Principe de substitution de Liskov (LSP) : Les sous-types doivent être substituables pour leurs types de base.
  • Principe de ségrégation des interfaces (ISP) : Les interfaces doivent être spécifiques au client et ne pas forcer la mise en œuvre de méthodes inutilisées.
  • Principe d’inversion des dépendances (DIP) : Les modules de niveau élevé doivent dépendre d’abstractions, et non de modules de niveau inférieur.

En intégrant ces principes dans le développement Android, nous pouvons créer des applications qui sont plus faciles à mettre à l’échelle, à tester et à maintenir.

Principe de responsabilité unique (SRP) : Rationalisation des responsabilités

Le principe de responsabilité unique est la base de l’écriture de code maintenable. Il stipule que chaque classe doit avoir une seule préoccupation pour laquelle elle est responsable. Un anti-modèle courant est de considérer les activités ou les fragments comme des « classes Dieu » qui gèrent des responsabilités allant de la gestion de l’interface utilisateur à la récupération de données, en passant par la gestion des erreurs, etc. Cette approche rend les tests et la maintenance un cauchemar.

Avec le SRP, séparez les différentes préoccupations en différents composants : par exemple, dans une application de nouvelles, créez ou lisez des nouvelles.


class NewsRepository {
fun fetchNews(): List {
// Gère la logique de récupération de données
}
}

class NewsViewModel(private val newsRepository: NewsRepository) { fun loadNews(): LiveData<List> { // Gère l’état de l’interface utilisateur et le flux de données } }

class NewsActivity : AppCompatActivity() { // Gère uniquement le rendu de l’interface utilisateur }

 

Chaque classe n’a qu’une seule responsabilité ; elle est donc facile à tester et à modifier sans avoir d’effets secondaires.

Dans le développement Android moderne, le SRP est principalement mis en œuvre avec l’architecture recommandée à l’aide de Jetpack. Par exemple, la logique liée à la manipulation des données peut résider à l’intérieur de ViewModel, tandis que les activités ou les fragments ne doivent s’occuper que de l’interface utilisateur et des interactions. La récupération de données peut être déléguée à un référentiel distinct, qu’il s’agisse d’une base de données locale comme Room ou d’une couche réseau comme Retrofit. Cela réduit le risque de gonflement des classes d’interface utilisateur, puisque chaque composant n’a qu’une seule responsabilité. Simultanément, votre code sera beaucoup plus facile à tester et à prendre en charge.

Principe ouvert/fermé (OCP) : Conception pour l’extension

Le principe ouvert/fermé déclare qu’une classe doit être ouverte pour l’extension mais non pour la modification. Il est plus raisonnable pour les applications Android, car elles sont constamment mises à jour et de nouvelles fonctionnalités sont ajoutées.

Le meilleur exemple de la façon d’utiliser le principe OCP dans les applications Android est les interfaces et les classes abstraites. Par exemple :


interface PaymentMethod {
fun processPayment(amount: Double)
}

class CreditCardPayment : PaymentMethod { override fun processPayment(amount: Double) { // Implémentation pour les paiements par carte de crédit } }

class PayPalPayment : PaymentMethod { override fun processPayment(amount: Double) { // Implémentation pour les paiements PayPal } }

 

L’ajout de nouvelles méthodes de paiement n’exige pas de modifications des classes existantes ; il nécessite la création de nouvelles classes. C’est là que le système devient flexible et peut être mis à l’échelle.

Dans les applications créées pour les appareils Android, le principe ouvert/fermé est très utile lorsqu’il s’agit de basculement de fonctionnalités et de configurations prises dynamiquement. Par exemple, si votre application a une interface AnalyticsTracker de base qui signale des événements à différents services d’analyse, Firebase et Mixpanel et des traceurs internes personnalisés, chaque nouveau service peut être ajouté sous forme de classe distincte sans modification du code existant. Cela maintient votre module d’analyse ouvert pour l’extension – vous pouvez ajouter de nouveaux traceurs – mais fermé pour la modification : vous n’écrivez pas les classes existantes chaque fois que vous ajoutez un nouveau service.

Principe de substitution de Liskov (LSP) : Assurance de l’interchangeabilité

Le principe de substitution de Liskov stipule que les sous-classes doivent être substituables pour leurs classes de base, et que le comportement de l’application ne doit pas changer. Dans Android, ce principe est fondamental pour la conception de composants réutilisables et prévisibles.

Par exemple, une application de dessin :


abstract class Shape {
abstract fun calculateArea(): Double
}

class Rectangle(private val width: Double, private val height: Double) : Shape() { override fun calculateArea() = width * height }

class Circle(private val radius: Double) : Shape() { override fun calculateArea() = Math.PI * radius * radius }

 

Les Rectangle et Circle peuvent être remplacés par n’importe lequel d’entre eux de manière interchangeable sans que le système ne défaille, ce qui signifie que le système est flexible et suit le LSP.

Considérez les sous-classes RecyclerView.Adapter d’Android. Chaque sous-classe de l’adaptateur étend RecyclerView.Adapter<VH> et remplace les fonctions principales comme onCreateViewHolder, onBindViewHolder et getItemCount. Le RecyclerView peut utiliser n’importe quelle sous-classe de manière interchangeable tant que ces méthodes sont implémentées correctement et ne cassent pas la fonctionnalité de votre application. Ici, le LSP est maintenu, et votre RecyclerView peut être flexible pour substituer n’importe quelle sous-classe d’adaptateur à volonté.

Principe de ségrégation des interfaces (ISP) : Interfaces lean et ciblées

Dans les applications plus grandes, il est courant de définir des interfaces avec trop de responsabilités, en particulier autour du réseau ou du stockage de données. Au lieu de cela, divisez-les en interfaces plus petites et plus ciblées. Par exemple, une interface ApiAuth responsable des points de terminaison d’authentification des utilisateurs doit être différente d’une interface ApiPosts responsable des points de terminaison des publications de blog ou des flux sociaux. Cette séparation empêchera les clients qui n’ont besoin que des méthodes liées aux publications d’être forcés de dépendre de et de mettre en œuvre des appels d’authentification, ce qui rendra votre code, ainsi que la couverture des tests, plus mince.

Le principe de ségrégation des interfaces signifie que, au lieu d’avoir de grandes interfaces, plusieurs interfaces plus petites et plus ciblées doivent être utilisées. Le principe empêche les situations dans lesquelles les classes implémentent des méthodes inutiles.

Par exemple, plutôt que d’avoir une grande interface représentant les actions des utilisateurs, envisagez le code kotlin :


interface Authentication {
fun login()
fun logout()
}

interface ProfileManagement { fun updateProfile() fun deleteAccount() }

 

Les classes qui implémentent ces interfaces peuvent se concentrer uniquement sur la fonctionnalité dont elles ont besoin, ce qui nettoie le code et le rend plus maintenable.

Principe d’inversion des dépendances (DIP) : Abstraction des dépendances

Le principe d’inversion des dépendances favorise la découpe en garantissant que les modules de niveau élevé dépendent d’abstractions plutôt que de mises en œuvre concrètes. Ce principe s’aligne parfaitement sur les pratiques de développement modernes d’Android, en particulier avec les frameworks d’injection de dépendances comme Dagger et Hilt.

Par exemple :


class UserRepository @Inject constructor(private val apiService: ApiService) {
fun fetchUserData() {
// Récupère les données utilisateur à partir d’une abstraction
}
}

 

Ici, UserRepository dépend de l’abstraction ApiService, ce qui le rend flexible et testable. Cette approche nous permet de remplacer la mise en œuvre, comme l’utilisation d’un service de simulation pendant les tests.

Les frameworks tels que Hilt, Dagger et Koin facilitent l’injection de dépendances en fournissant un moyen de fournir des dépendances aux composants Android, éliminant ainsi le besoin de les instancier directement. Dans un référentiel, par exemple, au lieu d’instancier une implémentation Retrofit, vous injectez une abstraction – par exemple, une interface ApiService. Vous pouvez ainsi facilement basculer entre les implémentation réseau – par exemple, un service de simulation en mémoire pour les tests locaux – et n’avez pas besoin de modifier quoi que ce soit dans votre code de référentiel. Dans les applications réelles, vous pouvez trouver que les classes sont annotées avec @Inject ou @Provides pour fournir ces abstractions, ce qui rend votre application modulaire et conviviale pour les tests.

Avantages pratiques des principes SOLID

L’adoption des principes SOLID dans le développement Android produit des avantages tangibles :

  1. Amélioration de la testabilité : Des classes et des interfaces ciblées rendent plus facile l’écriture de tests unitaires.
  2. Amélioration de la maintenabilité : Une séparation claire des préoccupations simplifie le débogage et les mises à jour.
  3. Évolutivité : Les conceptions modulaires permettent l’ajout de fonctionnalités de manière transparente.
  4. Collaboration : Un code bien structuré facilite la collaboration et réduit le temps d’intégration pour les nouveaux développeurs.
  5. Optimisation des performances : Des architectures légères et efficaces minimisent le traitement et l’utilisation de la mémoire inutiles.

Applications dans le monde réel

Dans les applications riches en fonctionnalités, telles que les applications de commerce électronique ou de réseautage social, l’application des principes SOLID peut grandement réduire le risque de régressions chaque fois qu’une nouvelle fonctionnalité ou un nouveau service est ajouté. Par exemple, si une nouvelle exigence nécessite un flux d’achat dans l’application, vous pouvez introduire un module distinct qui implémente les interfaces requises (Paiement, Analyse) sans toucher les modules existants. Cette approche modulaire, guidée par SOLID, permet à votre application Android de s’adapter rapidement aux demandes du marché et empêche la base de code de se transformer en spaghetti avec le temps.

Lorsque vous travaillez sur un grand projet qui nécessite la collaboration de nombreux développeurs, il est fortement recommandé de maintenir une base de code complexe avec les principes SOLID. Par exemple, la séparation de la récupération de données, de la logique métier et de la gestion de l’interface utilisateur dans le module de chat a aidé à réduire les chances de régressions pendant la mise à l’échelle du code avec de nouvelles fonctionnalités. De même, l’application du DIP a été cruciale pour abstraire les opérations réseau, permettant ainsi de changer presque sans perturbation entre les clients réseau.

Conclusion

Plus qu’un guide théorique, les principes de SOLID sont en fait la philosophie pratique pour créer des logiciels résilients, adaptables et maintenables. Dans le monde en constante évolution du développement Android, avec des exigences changeant presque aussi souvent que les technologies, l’adhésion à ces principes fournit un sol ferme sur lequel le succès peut être fondé.

Un bon code ne consiste pas seulement à faire fonctionner quelque chose – il s’agit de créer un système qui peut continuer à fonctionner et à grandir avec les besoins évolutifs. En embrassant les principes SOLID, vous écrirez non seulement un meilleur code, mais vous construirez également des applications qui sont un plaisir à développer, à mettre à l’échelle et à maintenir.

Farhana est une développeuse d'applications mobiles experte qui a livré avec succès de nombreuses applications mobiles à partir de zéro. Elle a dispensé une formation sur le développement Android pour les agents gouvernementaux des TIC au Bhoutan, a encadré de nombreux nouveaux arrivants et a dirigé des équipes pour atteindre le succès ensemble.