Geekflare est soutenu par notre public. Nous pouvons gagner des commissions d'affiliation en achetant des liens sur ce site.
Partager sur:

Rédaction de code maintenable: Principes SOLID expliqués en PHP (Laravel)

Scanner de sécurité des applications Web Invicti – la seule solution qui offre une vérification automatique des vulnérabilités avec Proof-Based Scanning™.

Ecrire des programmes informatiques est très amusant. À moins que vous n'ayez à travailler avec le code des autres.

Si vous avez travaillé en tant que développeur professionnel pendant plus de trois jours, vous savez que nos emplois sont tout sauf créatifs et passionnants. Une partie de la raison est le leadership de l'entreprise (lire: les gens qui ne comprennent jamais), tandis que l'autre partie est la complexité du code avec lequel nous devons travailler. Maintenant, même si nous ne pouvons absolument rien faire contre le premier, nous pouvons faire beaucoup pour le second.

Alors, pourquoi les bases de code sont-elles si complexes que nous avons envie de nous éviscérer? Simplement parce que les gens qui ont écrit la première version étaient pressés, et que ceux qui sont venus plus tard n'ont cessé d'ajouter au désordre. Le résultat final: un désordre moelleux que très peu de gens veulent toucher et que personne ne comprend.

Bienvenue au premier jour du travail!

«Personne n'a dit que ce serait si difficile. . . »

Mais cela ne doit pas être comme ça.

Écrire un bon code, un code modulaire et facile à maintenir, n'est pas si difficile. Seuls cinq principes simples - établis de longue date et bien connus - s'ils sont suivis avec discipline, garantiront que votre code est lisible, pour les autres et pour vous lorsque vous l'examinerez six mois plus tard. 😂

Ces principes directeurs sont représentés par l'acronyme SOLIDE. Peut-être avez-vous déjà entendu parler du terme «principes SOLIDES», peut-être pas. Au cas où vous auriez reporté cet apprentissage à «un jour», eh bien, assurons-nous que c'est ce jour-là!

Alors, sans plus tarder, regardons en quoi consiste ce contenu SOLIDE et comment cela peut nous aider à écrire un code très soigné.

“S” is for Single Responsibility

Si vous regardez différentes sources décrivant le principe de responsabilité unique, vous obtiendrez une certaine variation dans sa définition. Cependant, en termes plus simples, cela se résume à ceci: chaque classe de votre base de code devrait avoir un rôle très spécifique; c'est-à-dire que ça devrait être responsables pour rien de plus qu'un unique objectif. Et chaque fois qu'un changement est nécessaire dans cette classe, il s'ensuit que nous devrons le changer uniquement parce que cette responsabilité spécifique a changé.

Quand je suis tombé sur cette question la première fois, la définition qui m'a été présentée était: «Il devrait y en avoir une et une seule raison pour qu'une classe change». J'étais comme, "Quoi ??! Changement? Quel changement? Pourquoi changer? », C'est pourquoi j'ai dit plus tôt que si vous lisez ce sujet à différents endroits, vous obtiendrez des définitions reliées mais quelque peu différentes et potentiellement déroutantes.

Bref, j'en ai assez. Il est temps pour quelque chose de sérieux: si vous êtes comme moi, vous vous demandez probablement: «D'accord, tout va bien. Mais pourquoi diable devrais-je m'en soucier? Je ne vais pas commencer à écrire du code dans un style totalement différent de celui de demain simplement parce qu'un fou qui a écrit un livre (et qui est maintenant mort) le dit.

Excellent!

Et c'est l'esprit que nous devons maintenir si nous voulons vraiment apprendre des choses. Alors, pourquoi toute cette chanson et cette danse sur «Single Responsibility» sont-elles importantes? Différentes personnes expliquent cela différemment, mais pour moi, ce principe consiste à apporter de la discipline et de la concentration dans votre code.

Concentre-toi, mon garçon. Concentrer!

Voyons un exemple avant d'expliquer mon interprétation. Contrairement à d'autres ressources trouvées sur le Web qui fournissent des exemples que vous comprenez mais vous laissent ensuite vous demander comment elles vous aideraient dans des cas réels, plongeons-nous dans quelque chose de spécifique, un style de codage que nous voyons encore et encore, et peut-être même écrivons dans nos applications Laravel.

Lorsqu'une application Laravel reçoit une requête Web, l'URL est comparée aux routes que vous avez définies dans web.php et api.php, et s'il y a une correspondance, les données de la demande atteignent le contrôleur. Voici à quoi ressemble une méthode de contrôleur typique dans les applications réelles de niveau production:

class UserController extends Controller {
    public function store(Request $request)
    {        
        $validator = Validator::make($request->all(), [
           'first_name' => 'required',
           'last_name' => 'required',
           'email' => 'required|email|unique:users',
           'phone' => 'nullable'
       ]);
        
       if ($validator->fails()) {
            Session::flash('error', $validator->messages()->first());
            return redirect()->back()->withInput();
       }
       
       // create new user
       $user = User::create([
           'first_name' => $request->first_name,
           'last_name' => $request->last_name,
           'email' => $request->email,
           'phone' => $request->phone,
       ]);
        
       return redirect()->route('login');
    }
}

Nous avons tous écrit du code comme celui-ci. Et il est facile de voir ce qu'il fait: enregistrer de nouveaux utilisateurs. Cela a l'air bien et fonctionne bien, mais il y a un problème - ce n'est pas à l'épreuve du temps. Et par pérenne, je veux dire qu'il n'est pas prêt à gérer le changement sans créer un désordre.

Pourquoi

Vous pouvez dire que la fonction est destinée aux itinéraires définis dans le web.php fichier; c'est-à-dire des pages traditionnelles rendues par le serveur. Quelques jours passent, et maintenant votre client / employeur est en train de développer une application mobile, ce qui signifie que cet itinéraire ne sera d'aucune utilité pour les utilisateurs qui s'inscrivent à partir d'appareils mobiles. Que faire? Créez un itinéraire similaire dans le api.php fichier et écrire une fonction de contrôleur pilotée par JSON pour cela? Très bien, et puis quoi? Copiez tout le code de cette fonction, apportez quelques modifications et appelez-le un jour? C'est en effet ce que font de nombreux développeurs, mais ils se préparent à l'échec.

Le problème est que HTML et JSON ne sont pas les seuls formats d'API au monde (considérons simplement les pages HTML comme une API par souci d'argumentation). Qu'en est-il d'un client qui a un système hérité fonctionnant au format XML? Et puis il y en a un autre pour SOAP. Et gRPC. Et Dieu sait ce qui arrivera le lendemain.

Vous pouvez toujours envisager de créer un fichier distinct pour chacun de ces types d'API et de copier le code existant en le modifiant légèrement. Bien sûr, il y a dix fichiers, direz-vous, mais tout fonctionne bien, alors pourquoi se plaindre? Mais vient ensuite le coup de fouet, l'ennemi du développement logiciel - le changement. Supposons maintenant que les besoins de votre client / employeur aient changé. Ils veulent maintenant que, au moment de l'enregistrement de l'utilisateur, nous enregistrions l'adresse IP et ajoutions une option pour un champ indiquant qu'ils ont lu et compris les termes et conditions.

Oh, oh! Nous n'avons pas dix fichiers à éditer, et nous devons nous assurer que la logique est gérée exactement de la même manière dans chacun d'eux. Même un seul erreur peut entraîner des pertes commerciales importantes. Et maintenant, imaginez l'horreur dans les applications SaaS à grande échelle, car la complexité du code est déjà assez élevée.

Zut . . .

Comment avons-nous atteint cet enfer?

La réponse est que la méthode du contrôleur qui semble si inoffensive fait en fait un certain nombre de choses différentes: elle valide la demande entrante, gère les redirections et crée de nouveaux utilisateurs.

Ça fait trop de choses! Et oui, comme vous l'avez peut-être remarqué, savoir comment créer de nouveaux utilisateurs dans le système ne devrait pas être un travail de méthodes de contrôleur. Si nous devions retirer cette logique de la fonction et la placer dans une classe séparée, nous aurions maintenant deux classes, chacune avec une seule responsabilité à gérer. Bien que ces classes puissent s'entraider en appelant leurs méthodes, elles ne sont pas autorisées à savoir ce qui se passe dans l'autre.

class UserController extends Controller {
    public function store(Request $request)
    {        
        $validator = Validator::make($request->all(), [
           'first_name' => 'required',
           'last_name' => 'required',
           'email' => 'required|email|unique:users',
           'phone' => 'nullable'
       ]);
        
       if ($validator->fails()) {
            Session::flash('error', $validator->messages()->first());
            return redirect()->back()->withInput();
       }
       
       UserService::createNewUser($request->all());
       return redirect()->route('login');
    }
}

Regardez le code maintenant: beaucoup plus compact, facile à comprendre. . . et surtout, adaptable au changement. Poursuivant notre discussion précédente où nous avions dix types d'API différents, chacun d'entre eux appelant désormais une seule fonction UserService::createNewUser($request->all()); et en finir. Si des modifications sont nécessaires dans la logique d'enregistrement des utilisateurs, le UserService la classe veillera à ce que les méthodes du contrôleur ne soient pas du tout modifiées. Si la confirmation par SMS doit être définie après l'enregistrement de l'utilisateur, le UserService s'en chargera (en appelant une autre classe qui sait envoyer des SMS), et encore une fois, les contrôleurs ne seront pas touchés.

C'est ce que j'entendais par concentration et discipline: concentration sur le code (une chose ne faisant qu'une seule chose) et discipline par le développeur (ne pas tomber pour des solutions à court terme).

Eh bien, c'était toute une tournée! Et nous n'avons couvert qu'un seul des cinq principes. Allons-nous en!

“O” is for Open-Closed

Je dois dire que quiconque a proposé les définitions de ces principes ne pensait certainement pas à des développeurs moins expérimentés. Il en va de même pour le principe ouvert-fermé, et ceux à venir ont une longueur d'avance dans l'étrangeté. 😂😂

Quoi qu'il en soit, regardons la définition trouvée à tout le monde pour ce principe: les classes doivent être ouvertes pour extension mais fermées pour modification. Hein ?? Oui, je n'étais pas non plus amusé quand je l'ai rencontré pour la première fois, mais avec le temps, j'ai compris - et admiré - ce que cette règle essaie de dire: le code une fois écrit ne devrait pas avoir besoin d'être changé.

Dans un sens philosophique, cette règle est excellente - si le code ne change pas, il restera prévisible et de nouveaux bogues ne seront pas introduits. Mais comment est-il même possible de rêver d'un code qui ne change pas alors que tout ce que nous faisons, en tant que développeurs, c'est toujours à la poursuite du changement?

Eh bien, tout d'abord, le principe ne signifie pas que même pas une ligne de code existant n'est autorisée à changer; ce serait tout droit sorti d'un pays des fées. Le monde change, les affaires changent et, par conséquent, les changements de code - pas de solution. Mais ce que ce principe signifie, c'est que nous restreignons autant que possible la possibilité de modifier le code existant. Et cela vous indique aussi comment faire cela: les classes doivent être ouvertes pour extension et fermées pour modification.

«Extension» signifie ici la réutilisation, que la réutilisation se présente sous la forme de classes enfants héritant des fonctionnalités d'une classe parent, ou que d'autres classes stockent des instances d'une classe et appellent ses méthodes.

Alors, revenons à la question à un million de dollars: comment écrire du code qui survit au changement? Et ici, j'ai peur, personne n'a de réponse claire. Dans la programmation orientée objet, plusieurs techniques ont été découvertes et affinées pour atteindre cet objectif, depuis ces principes SOLID que nous étudions jusqu'aux modèles de conception courants, modèles d'entreprise, modèles architecturaux, etc. Il n'y a pas de réponse parfaite, et donc un développeur doit continuer à aller de plus en plus haut, rassembler autant d'outils que possible et essayer de faire de son mieux.

Dans cet esprit, examinons une de ces techniques. Supposons que nous devions ajouter la fonctionnalité pour convertir un contenu HTML donné (peut-être une facture?) En un fichier PDF et également forcer un téléchargement immédiat dans le navigateur. Supposons également que nous ayons l'abonnement payant d'un service hypothétique appelé MilkyWay, qui effectuera la génération réelle de PDF. Nous pourrions finir par écrire une méthode de contrôleur comme celle-ci:

class InvoiceController extends Controller {
    public function generatePDFDownload(Request $request) {
        $pdfGenerator = new MilkyWay();
        $pdfGenerator->apiKey = env('MILKY_WAY_API_KEY');
        $pdfGenerator->setContent($request->content); // HTML format
        $pdfFile = $pdfGenerator->generateFile('invoice.pdf');

        return response()->download($pdfFile, [
            'Content-Type' => 'application/pdf',
        ]);
    }
}

J'ai omis la validation des demandes, etc., afin de me concentrer sur le problème central. Vous remarquerez que cette méthode fait un bon travail en suivant le principe de responsabilité unique: elle n'essaie pas de parcourir le contenu HTML qui lui est transmis et de créer un PDF (en fait, elle ne sait même pas qu'on lui a donné du HTML ); au lieu de cela, il transfère cette responsabilité aux spécialistes MilkyWay class, et présente tout ce qu'il obtient, sous forme de téléchargement.

Mais il y a un léger problème.

Notre méthode de contrôleur dépend trop de la classe MilkyWay. Si la prochaine version de l'API MilkyWay change l'interface, notre méthode cessera de fonctionner. Et si nous souhaitons utiliser un autre service un jour, nous devrons littéralement faire une recherche globale dans notre éditeur de code et changer tous les extraits de code qui mentionnent MilkyWay. Et pourquoi est-ce mauvais? Parce que cela augmente considérablement les chances de faire une erreur et représente un fardeau pour l'entreprise (le temps des développeurs consacré à régler le problème).

Tout ce gaspillage parce que nous avons créé une méthode qui n'était pas fermée au changement.

Pouvons-nous faire mieux?

Oui nous pouvons!

Dans ce cas, nous pouvons profiter d'une pratique qui va quelque chose comme ceci - programme aux interfaces, pas aux implémentations.

Ouais, je sais, c'est un autre de ces OOPSismes qui n'ont aucun sens la première fois. Mais ce qu'il dit, c'est que notre code devrait dépendre de types des choses, et non des choses particulières elles-mêmes. Dans notre cas, nous devons nous libérer de la dépendance MilkyWay classe, et dépendent plutôt d'un générique, un type de la classe PDF (tout deviendra clair dans une seconde).

Maintenant, quels outils avons-nous en PHP pour créer de nouveaux types? De manière générale, nous avons l'héritage et les interfaces. Dans notre cas, créer une classe de base pour toutes les classes PDF ne sera pas une bonne idée car il est difficile d'imaginer différents types de moteurs / services PDF partageant le même comportement. Peut-être qu'ils peuvent partager le setContent() méthode, mais même là, le processus d'acquisition de contenu peut être différent pour chaque classe de service PDF, donc tout taper dans une hiérarchie d'héritage aggravera les choses.

Cela dit, créons une interface qui spécifie les méthodes que nous voulons que toutes nos classes de moteur PDF contiennent:

interface IPDFGenerator {
    public function setup(); // API keys, etc.
    public function setContent($content);
    public function generatePDF($fileName = null);
}

Alors, qu'avons-nous ici?

Grâce à cette interface, nous disons que nous nous attendons à ce que toutes nos classes PDF aient au moins ces trois méthodes. Maintenant, si le service que nous voulons utiliser (MilkyWay, dans notre cas) ne suit pas cette interface, c'est notre travail d'écrire une classe qui fait cela. Un aperçu de la façon dont nous pourrions écrire une classe wrapper pour notre MilkyWay le service est le suivant:

class MilkyWayPDFGenerator implements IPDFGenerator {
    public function __construct() {
        $this->setup();
    }

    public function setup() {
        $this->generator = new MilkyWay();
        $this->generator->api_key = env('MILKY_WAY_API_KEY');
    }

    public function setContent($content) {
        $this->generator->setContent($content);
    }

    public function generatePDF($fileName) {
        return $this->generator->generateFile($fileName);
    }
}

Et juste comme ça, chaque fois que nous avons un nouveau service PDF, nous écrirons une classe wrapper pour celui-ci. En conséquence, toutes ces classes seront considérées comme de type IPDFGenerator.

Alors, comment tout cela est-il lié au principe ouvert-fermé et à Laravel?

Pour atteindre ce point, nous devons connaître deux autres concepts clés: les liaisons de conteneurs de Laravel et une technique très courante appelée injection de dépendances. Encore une fois, de gros mots, mais l'injection de dépendances signifie simplement qu'au lieu de créer vous-même des objets de classes, vous les mentionnez dans les arguments de fonction et quelque chose les créera automatiquement pour vous. Cela vous évite d'avoir à écrire du code tel que $account = new Account(); tout le temps et rend le code plus testable (un sujet pour un autre jour). Ce «quelque chose» dont j'ai parlé prend la forme du Conteneur de service dans le monde Laravel.

Pour l'instant, pensez-y simplement comme quelque chose qui peut créer de nouvelles instances de classe pour nous. Voyons comment cela aide.

Dans le conteneur de service de notre exemple, nous pouvons écrire quelque chose comme ceci:

$this->app->bind('App\Interfaces\IPDFGenerator', 'App\Services\PDF\MilkyWayPDFGenerator');

Cela signifie essentiellement que chaque fois que quelqu'un demande un IPDFGenerator, donne-leur le MilkyWayPDFGenerator classe. Et après tout ce chant et cette danse, mesdames et messieurs, nous en arrivons au point où tout se met en place et le principe ouvert-fermé se révèle au travail!

Forts de toutes ces connaissances, nous pouvons réécrire notre méthode de contrôleur de téléchargement PDF comme ceci:

class InvoiceController extends Controller {
    public function generatePDFDownload(Request $request, IPDFGenerator $generator) {
        $generator->setContent($request->content);
        $pdfFile = $generator->generatePDF('invoice.pdf');

        return response()->download($pdfFile, [
            'Content-Type' => 'application/pdf',
        ]);
    }
}

Remarquez la différence?

Tout d'abord, nous recevons notre instance de classe de générateur PDF dans l'argument de fonction. Ceci est créé et nous est transmis par le conteneur de services, comme indiqué précédemment. Le code est également plus propre et il n'y a aucune mention de clés API, etc. Mais, surtout, il n'y a aucune trace du MilkyWay classe. Cela a également un avantage supplémentaire de rendre le code plus facile à lire (quelqu'un qui le lit pour la première fois ne dira pas, "Whoa! WTF est-ce MilkyWay?? » et s'en soucient constamment à l'arrière de la tête).

Mais le plus grand avantage de tous?

Cette méthode est maintenant fermée pour modification et résistante au changement. Permettez-moi de vous expliquer. Supposons que demain nous sentions que le MilkyWay le service est trop cher (ou, comme cela arrive souvent, leur support client est devenu merdique); en conséquence, nous avons essayé un autre service appelé SilkyWay et je veux y passer. Il ne nous reste plus qu'à écrire un nouveau IPDFGenerator classe wrapper pour SilkyWay et modifiez la liaison dans notre code de conteneur de service:

$this->app->bind('App\Interfaces\IPDFGenerator', 'App\Services\PDF\SilkyWayPDFGenerator');

C'est tout!!

Rien d'autre n'a besoin de changer, car notre application est écrite selon une interface (l'interface IPDFGenerator) au lieu d'une classe concrète. Les exigences commerciales ont changé, un nouveau code a été ajouté (une classe wrapper) et une seule ligne de code a été modifiée - tout le reste reste intact et toute l'équipe peut rentrer chez elle en toute confiance et dormir paisiblement.

Envie de dormir paisiblement? Suivez le principe ouvert-fermé! 🤭😆

“L” is for Liskov Substitution

Liskov-quoi ??

Cette chose ressemble à quelque chose tout droit sorti d'un manuel de chimie organique. Cela pourrait même vous faire regretter d'avoir choisi le développement logiciel en tant que carrière parce que vous pensiez que tout était pratique et pas de théorie.

«Vite, gamin! Quel est le plus grand nombre premier pouvant être exprimé sous forme de somme de deux nombres premiers? »

Mais retenez vos chevaux une seconde! Croyez-moi, ce principe est aussi simple à comprendre que intimidant en son nom. En fait, cela pourrait bien être le principe le plus facile des cinq à comprendre (enfin, euh… sinon le plus simple, alors au moins il aura l'explication la plus courte et la plus simple).

Cette règle dit simplement que le code qui fonctionne avec les classes parentes (ou interfaces) ne doit pas être interrompu lorsque ces classes sont remplacées par des classes enfants (ou des classes d'implémentation d'interface). L'exemple que nous avons terminé juste avant cette section est une excellente illustration: si je remplace le générique IPDFGenerator tapez l'argument de méthode avec le spécifique MilkyWayPDFGenerator Par exemple, vous attendez-vous à ce que le code se brise ou continue de fonctionner?

Continuez à travailler, bien sûr! En effet, la différence réside uniquement dans les noms, et l'interface ainsi que la classe ont les mêmes méthodes fonctionnant de la même manière, donc notre code fonctionnera comme avant.

Alors, quel est le problème avec ce principe? Eh bien, en termes plus simples, c'est tout ce que dit ce principe: assurez-vous que vos sous-classes implémentent toutes les méthodes exactement comme requis, avec le même nombre et le même type d'arguments, et le même type de retour. Si même un paramètre devait différer, nous continuerions sans le savoir à construire plus de code dessus, et un jour nous aurons le genre de gâchis puant dont la seule solution serait de le supprimer.

Là. Ce n'était pas si mal maintenant, n'est-ce pas? 😇

On peut en dire beaucoup plus sur la substitution de Liskov (consultez sa théorie et lisez sur les types covariants si vous vous sentez vraiment courageux), mais à mon avis, cela suffit pour le développeur moyen qui rencontre ces mystérieuses terres de modèles et principes pour la première fois.

“I” is for Interface Segregation

Ségrégation d'interface. . . hmm, ça ne sonne pas si mal, n'est-ce pas? On dirait que c'est quelque chose à voir avec la ségrégation. . . euh, séparer. . . les interfaces. Je me demande simplement où et comment.

Si vous pensiez dans ce sens, croyez-moi, vous avez presque fini de comprendre et d'utiliser ce principe. Si les cinq principes SOLID étaient des véhicules d'investissement, celui-ci offrirait la valeur la plus à long terme pour bien apprendre à coder (d'accord, je me rends compte que je dis cela à propos de chaque principe, mais vous savez, vous voyez l'idée).

Dépouillé du jargon à haute valeur ajoutée et distillé jusqu'à sa forme la plus basique, le principe de la séparation des interfaces a ceci à dire: plus les interfaces sont nombreuses et spécialisées dans votre application, plus votre code sera modulaire et moins étrange.

Regardons un exemple très courant et pratique. Chaque développeur Laravel rencontre le soi-disant modèle de référentiel dans sa carrière, après quoi il passe les prochaines semaines à traverser une phase cyclique de hauts et de bas, et finalement abandonne le modèle. Pourquoi? Dans tous les didacticiels couvrant le modèle de référentiel, il est conseillé de créer une interface commune (appelée référentiel) qui définira les méthodes nécessaires pour accéder ou manipuler les données. Cette interface de base pourrait ressembler à ceci:

interface IRepository {
    public function getOne($id);
    public function getAll();
    public function create(array $data);
    public function update(array $data, $id);
    public function delete($id);
}

Et maintenant, pour votre User modèle, vous êtes censé créer un UserRepository qui implémente cette interface; alors, pour votre Customer modèle, vous êtes censé créer un CustomerRepository qui implémente cette interface; vous avez eu l'idée.

Maintenant, il est arrivé dans l'un de mes projets que certains des modèles n'étaient pas censés être accessibles en écriture par personne d'autre que le système. Avant de commencer à rouler des yeux, considérez que la journalisation ou la maintenance d'une piste d'audit est un bon exemple concret de tels modèles en lecture seule. Le problème auquel j'ai été confronté était que, puisque j'étais censé créer des référentiels, tous implémentaient le IRepository interface, dites le LoggingRepository, au moins deux des méthodes de l'interface, update() et delete() ne m'ont servi à rien.

Oui, une solution rapide serait de les implémenter de toute façon et de les laisser vides ou de lever une exception, mais si compter sur de telles solutions de ruban adhésif me convenait, je ne suivrais pas le modèle de référentiel en premier lieu!

Heeeeelp! Je suis coincé. : '(

Cela signifie-t-il que tout est la faute du modèle de référentiel?

Non pas du tout!

En fait, un référentiel est un modèle bien connu et accepté qui apporte cohérence, flexibilité et abstraction à vos modèles d'accès aux données. Le problème est que l'interface que nous avons créée - ou devrais-je dire l'interface qui s'est popularisée dans pratiquement tous les tutoriels - est trop large.

Parfois, cette idée est exprimée en disant que l'interface est «fatiguée», mais cela signifie la même chose - l'interface fait trop d'hypothèses, et ajoute ainsi des méthodes qui sont inutiles à certaines classes mais qui sont quand même obligées de les implémenter, résultant dans un code fragile et déroutant. Notre exemple était peut-être un peu plus simple, mais imaginez quel désordre peut être créé lorsque plusieurs classes ont implémenté des méthodes qu'elles ne voulaient pas, ou celles qu'elles voulaient mais manquaient dans l'interface.

La solution est simple, et c'est aussi le nom du principe dont nous discutons: la séparation des interfaces.

Le fait est que nous ne devrions pas créer nos interfaces à l'aveuglette. Et nous ne devrions pas non plus faire d'hypothèses, peu importe notre expérience ou notre intelligence. Au lieu de cela, nous devrions créer plusieurs interfaces plus petites et spécialisées, en laissant les classes implémenter celles qui sont nécessaires et en laissant de côté celles qui ne le sont pas.

Dans l'exemple dont nous avons discuté, j'aurais pu créer deux interfaces au lieu d'une:  IReadOnlyRespository (contenant les fonctions getOne() et getAll()), Et IWriteModifyRepository (contenant le reste des fonctions). Pour les dépôts réguliers, je dirais alors class UserRepository implements IReadOnlyRepository, IWriteModifyRepository { . .. }. (Note latérale: des cas spéciaux peuvent encore survenir, et c'est bien car aucune conception n'est parfaite. Vous pourriez même vouloir créer une interface distincte pour chaque méthode, et ce sera bien aussi, en supposant que les besoins de votre projet sont aussi précis.)

Oui, il y a plus d'interfaces maintenant, et certains pourraient dire qu'il y a trop de choses à retenir ou que la déclaration de classe est maintenant trop longue (ou semble moche), etc., mais regardez ce que nous avons gagné: spécialisé, de petite taille, auto -contenu des interfaces qui peuvent être combinées selon les besoins et ne se gêneront pas. Tant que vous écrivez un logiciel pour gagner votre vie, rappelez-vous que c'est l'idéal auquel tout le monde s'efforce.

“D” is for Dependency Inversion

Si vous avez lu les parties précédentes de cet article, vous pourriez avoir l'impression de comprendre ce que ce principe essaie de dire. Et vous avez raison, en ce sens que ce principe est plus ou moins une répétition de ce dont nous avons discuté jusqu'à présent. Sa définition formelle n'est pas trop effrayante, alors regardons-la: les modules de haut niveau ne devraient pas dépendre de modules de bas niveau; les deux devraient dépendre d'abstractions.

Oui, ça a du sens. Si j'ai une classe de haut niveau (de haut niveau dans le sens où elle utilise d'autres classes plus petites et plus spécialisées pour effectuer quelque chose et ensuite prendre des décisions), nous ne devrions pas avoir cette classe de haut niveau dépendant d'un bas particulier. classe de niveau pour certains types de travaux. Au contraire, ils doivent être codés pour s'appuyer sur des abstractions (telles que les classes de base, les interfaces, etc.).

Pourquoi ?

Nous en avons déjà vu un excellent exemple dans la première partie de cet article. Si vous avez utilisé un service de génération de PDF et que votre code était jonché de new ABCService() classe, le jour où l'entreprise a décidé d'utiliser un autre service serait le jour dont on se souviendrait à jamais - pour toutes les mauvaises raisons! Nous devrions plutôt utiliser une forme générale de cette dépendance (créer une interface pour les services PDF, c'est-à-dire), et laisser quelque chose d'autre gérer son instanciation et nous la transmettre (en Laravel, nous avons vu comment le conteneur de services nous a aidés à le faire).

Dans l'ensemble, notre classe de haut niveau, qui contrôlait auparavant la création d'instances de classe de niveau inférieur, doit maintenant se tourner vers autre chose. Les rôles ont changé, et c'est pourquoi nous appelons cela un inversion de dépendances.

Si vous recherchez un exemple pratique, revenez à la partie de cet article où nous expliquons comment éviter que notre code ne dépende exclusivement du MilkyWay Classe PDF.

. . .

Devinez quoi, c'est tout! Je sais, je sais, c'était une lecture assez longue et difficile, et je tiens à m'en excuser. Mais mon cœur va au développeur moyen qui fait les choses de manière intuitive (ou de la façon dont elles lui ont été enseignées) et qui ne peut ni faire la tête ni la queue des principes SOLID. Et j'ai fait de mon mieux pour garder les exemples aussi proches d'un Développeur Laravel journée de travail possible; après tout, ce qui nous est utile, ce sont des exemples qui contiennent des classes Vehicle et Car - ou même des génériques avancés, des réflexions, etc. - alors qu'aucun de nous ne créera des bibliothèques.

Si vous avez trouvé cet article utile, veuillez laisser un commentaire. Cela confirmera mon notion que les développeurs sont vraiment j'ai du mal à comprendre ces concepts «avancés», et je serai motivé à écrire sur d'autres sujets similaires. Jusqu'à plus tard! 🙂

Merci à nos commanditaires
Plus de bonnes lectures sur le développement
Alimentez votre entreprise
Certains des outils et services pour aider votre entreprise à se développer.
  • Invicti utilise Proof-Based Scanning™ pour vérifier automatiquement les vulnérabilités identifiées et générer des résultats exploitables en quelques heures seulement.
    Essayez Invicti
  • Web scraping, proxy résidentiel, proxy manager, web unlocker, moteur de recherche et tout ce dont vous avez besoin pour collecter des données Web.
    Essayez Brightdata
  • Semrush est une solution de marketing numérique tout-en-un avec plus de 50 outils de référencement, de médias sociaux et de marketing de contenu.
    Essayez Semrush
  • Intruder est un scanner de vulnérabilités en ligne qui détecte les failles de cybersécurité de votre infrastructure, afin d'éviter des violations de données coûteuses.
    Essayez Intruder