VIII. Objets▲
Lorsque vous concevez un programme, vous devez l'aborder aux nombreux niveaux. À la base, vous avez des détails spécifiques sur le problème que vous résolvez. Aux niveaux supérieurs, vous devez organiser le code pour qu'il ait du sens. Notre seul espoir pour gérer cette complexité est d'exploiter l'abstraction (traiter des choses semblables de façon semblable) et l'encapsulation (regrouper les détails connexes).
À elles seules, les fonctions sont insuffisantes pour les problèmes complexes. Plusieurs techniques groupent les problèmes en unités de comportements semblables. Une technique populaire est l'orientation objet (OO), ou la programmation orientée objet (POO), où les programmes travaillent avec des objets des entités discrètes, uniques, ayant leur propre identité.
VIII-A. Moose▲
Le système objet par défaut de Perl est minime, mais flexible. Sa syntaxe est un peu maladroite et révèle comment fonctionne un système objet. Vous pouvez construire de grandes choses sur celui-ci, mais vous ou quelqu'un d'autre devrez écrire beaucoup de code pour obtenir ce que certains autres langages fournissent gratuitement.
Moose est un système objet complet pour Perl. C'est une distribution complète disponible sur le CPAN et il ne fait pas partie du langage standard, mais c'est quand même intéressant de l'installer et de l'utiliser. Moose offre à la fois une façon simplifiée d'utiliser un système objet et des fonctionnalités avancées de langages tels que Smalltalk et Common Lisp.
Les objets Moose fonctionnent avec du Perl standard. Dans vos programmes, vous pouvez mélanger et assortir des objets écrits avec le système d'objets par défaut de Perl et Moose. Bien sûr, vous devrez écrire beaucoup plus de code pour obtenir ce que Moose vous fournit.
Documentation Moose
Voir Moose::Manual sur CPAN pour la documentation complète de Moose.
VIII-A-1. Classes▲
Un objet Moose est une instance concrète d'une classe, qui est un modèle décrivant les données et comportements spécifiques à l'objet. Une classe appartient généralement à un paquetage (PaquetagesPaquetages), qui lui donne son nom :
Cette classe Chat semble ne rien faire, mais c'est tout ce dont Moose a besoin pour créer une classe. Créez des objets (ou instances) de la classe Chat en utilisant la syntaxe :
Tout comme l'opérateur flèche déréférence une référence, cette flèche-ci appelle une méthode sur Chat.
VIII-A-2. Méthodes▲
Une méthode est une fonction associée à une classe. Une fonction peut appartenir à un espace de noms ; vous avez vu cela. De même, une méthode appartient à une classe.
Lorsque vous appelez une méthode, vous le faites avec un invoquant. Lorsque vous appelez new() sur Chat, le nom de la classe, Chat, est linvoquant de new(). Pensez à cela comme à l'envoi d'un message à une classe : « faites ce que new() fait. » Dans ce cas, l'envoi du message new l'appel de la méthode new() retourne un nouvel objet de la classe Chat.
Lorsque vous appelez une méthode sur un objet, cet objet est linvoquant :
Le premier argument d'une méthode est son invoquant ($self, par convention). Supposons qu'un Chat puisse miauler() :
Maintenant, n'importe quelle instance de Chat peut vous réveiller le matin, parce qu'il n'a pas encore mangé :
# le chat miaule toujours trois fois à 6 heures
my $reveil_vivant
=
Chat->
new;
$reveil_vivant-
>
miauler for 1
.. 3
;
Chaque objet peut avoir ses propres données distinctes. (Nous y reviendrons très prochainement.) Les méthodes qui lisent ou écrivent les données de leur invoquant sont des méthodes d'instance ; elles dépendent de la présence d'un invoquant approprié pour fonctionner correctement. Des méthodes (comme miauler()) qui n'accèdent pas aux données d'instance sont des méthodes de classe. Vous pouvez appeler des méthodes de classe sur des classes et des méthodes de classe et d'instance sur les instances, mais vous ne pouvez pas appeler des méthodes d'instance sur des classes.
Les constructeurs, qui créent des instances, sont évidemment des méthodes de classe. Moose vous fournit un constructeur par défaut, nommé new(). C'est pourquoi vous pouvez créer un objet Chat par la déclaration minimale de classe de tout à l'heure.
Les méthodes de classe sont effectivement des fonctions globales de l'espace de noms. N'ayant pas accès aux données de l'instance, elles ont peu d'avantages par rapport aux fonctions d'un espace de noms. La majorité du code OO utilise des méthodes d'instance pour lire et écrire des données d'instance.
VIII-A-3. Attributs▲
En Perl, chaque objet est unique. Les objets peuvent contenir des données privées associées à chaque objet unique vous pouvez entendre ces données appelées attributs, données d'instance, ou état de l'objet. Définissez un attribut en le déclarant comme élément de la classe :
Moose exporte la fonction has() que vous pouvez utiliser pour déclarer un attribut. Ce code se lit : « les objets Chat ont un attribut nom. Cet attribut est en lecture seule et est une chaîne de caractères. » Le premier argument, 'nom', est le nom de l'attribut. La paire d'arguments is=> 'ro' déclare que cet attribut est en lecture seule, donc vous ne pouvez pas modifier la valeur de l'attribut après l'avoir définie. Enfin, la paire isa => 'Str' déclare que la valeur de cet attribut peut être exclusivement une chaîne.
Dans cet exemple, Moose crée une méthode accesseur nommée nom() et vous permet de passer un paramètre nom au constructeur de Chat :
for my $nom
(qw( Tuxie Petunia Daisy )
)
{
my $chat
=
Chat->
new( nom =>
$nom
);
say
"Créé un chat nommé "
, $chat-
>
nom;
}
La documentation de Moose utilise des parenthèses pour séparer les noms des attributs des leurs caractéristiques :
Cela équivaut à :
has( 'nom'
, 'is'
, 'ro'
, 'isa'
, 'Str'
);
L'approche de Moose fonctionne bien pour les déclarations complexes :
has 'nom'
=>
(
is =>
'ro'
,
isa =>
'Str'
,
# options Moose avancées; perldoc Moose
init_arg =>
undef
,
lazy_build =>
1
,
);
... tandis que ce livre préfère une approche à faible ponctuation pour les déclarations simples. Choisissez le style qui vous offre le plus de clarté.
Moose se plaindra si vous essayez d'affecter à nom une valeur qui n'est pas une chaîne. Les attributs n'ont pas besoin d'avoir des types. Dans ce cas, tout passera :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'age'
, is =>
'ro'
;
}
my $invalide
=
Chat->
new( nom =>
'bizarre'
,
age =>
'pourpre'
);
Si vous ajoutez un type à votre déclaration d'attribut, Moose tentera de valider les valeurs assignées à cet attribut. Parfois, cette rigueur est inestimable.
Si vous marquez un attribut en lecture et écriture (avec is => rw), Moose créera unmutateur, c'est-à-dire une méthode qui peut changer la valeur de l'attribut :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'age'
, is =>
'ro'
, isa =>
'Int'
;
has 'diete'
, is =>
'rw'
;
}
my $gros
=
Chat->
new( nom =>
'Fatty'
,
age =>
8
,
diete =>
'fruits de mer'
);
say
$gros-
>
nom, ' mange '
, $gros-
>
diete;
$gros-
>
diete( 'Low Sodium Kitty Lo Mein'
);
say
$gros-
>
nom, ' mange maintenant '
, $gros-
>
diete;
Un accesseur ro utilisé comme mutateur lancera l'exception Cannot assign a value to a read-only accessor at ....
L'utilisation de ro ou rw est une question de conception, commodité et pureté. Moose n'impose aucune philosophie particulière dans ce domaine. Certaines personnes suggèrent de définir comme ro toutes les données d'instance, de sorte que vous devez les passer dans le constructeur (ImmutabilitéImmutabilité). Dans l'exemple Chat, age() pourrait être toujours un accesseur, mais le constructeur pourrait prendre l'année de naissance du chat et calculer lui-même l'âge en fonction de l'année en cours. Cette approche renforce le code de validation et assure que tous les objets créés contiennent des données valides.
Les données d'instance montrent une partie de la valeur de l'orientation objet. Un objet contient des données connexes et peut accomplir des comportements avec ces données. Une classe décrit ces données et comportements. Vous pouvez avoir plusieurs objets indépendants avec données d'instance séparées et traiter l'ensemble de ces objets de la même manière ; ils se comporteront différemment en fonction de leurs données d'instance.
VIII-A-4. Encapsulation▲
Moose vous permet de déclarer quels attributs possèdent les instances de classe (un chat a un nom) ainsi que les attributs de ces attributs (vous ne pouvez pas modifier le nom d'un chat, vous ne pouvez que le lire). Moose décide lui-même comment stocker ces attributs. Vous pouvez modifier cela si vous le souhaitez, mais permettre à Moose de gérer votre stockage encourage l'encapsulation : masquer les détails internes d'un objet aux utilisateurs externes de cet objet.
Examinez un changement de la façon dont les Chat gèrent leur âge. Au lieu de passer au constructeur une valeur pour un âge, passez l'année de naissance du chat et calculez l'âge en fonction des besoins :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'diete'
, is =>
'rw'
;
has 'annee_naiss'
, is =>
'ro'
, isa =>
'Int'
;
sub age
{
my $self
=
shift
;
my $annee
=
(localtime
)[5
] +
1900
;
return $annee
-
$self-
>
annee_naiss;
}
}
La syntaxe pour créer des objets Chat a certes changé, mais la syntaxe pour utiliser des objets Chat est restée la même. En dehors de Chat, age() se comporte comme auparavant. Le comment de son fonctionnement est un détail caché à l'intérieur de la classe Chat.
Compatibilité et API
Conservez l'ancienne syntaxe pour créer des objets Chat en personnalisant le constructeur généré de Chat pour permettre de passer un paramètre age. Calculez annee_naiss à partir de celui-ci. Voir perldoc Moose::Manual::Attributes.
Le calcul de l'âge présente un autre avantage. Une valeur d'attribut par défaut fera le nécessaire quand quelqu'un crée un nouvel objet Chat sans fournir une année de naissance :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'diete'
, is =>
'rw'
, isa =>
'Str'
;
has 'annee_naiss'
,
is =>
'ro'
,
isa =>
'Int'
,
default =>
sub {
(localtime
)[5
] +
1900
}
;
}
Le mot-clé default sur un attribut prend une référence de fonction. Vous pouvez utiliser directement une valeur simple comme un nombre ou une chaîne, mais, pour quelque chose de plus complexe, utilisez une référence de fonction qui retourne la valeur par défaut pour cet attribut lors de la construction d'un nouvel objet. Si le code de création d'un objet ne passe aucune valeur pour cet attribut au constructeur, l'objet prend la valeur par défaut :
... et ce chaton aura un âge de valeur 0 jusqu'à l'année prochaine.
VIII-A-4-a. Polymorphisme▲
L'encapsulation est utile, mais le pouvoir réel de l'orientation objet est beaucoup plus vaste. Un programme OO bien conçu peut gérer de nombreux types de données. Lorsque des classes bien conçues encapsulent les détails spécifiques des objets dans des endroits appropriés, quelque chose de curieux se produit : le code devient souvent moins spécifique.
Déplacer les détails de ce que le programme sait à propos des Chat individuels (les attributs) et ce que le programme sait que les Chat peuvent faire (les méthodes) dans la classe Chat signifie que le code qui traite des instances de Chat peut ignorer comment Chat fait ce qu'il fait.
Examinons une fonction qui affiche les détails d'un objet :
sub afficher_details
{
my $objet
=
shift
;
say
'Mon nom est '
, $objet-
>
nom;
say
'Mon âge est '
, $objet-
>
age;
say
'Je mange '
, $objet-
>
diete;
}
Il est évident (en contexte) que cette fonction marchera si vous lui passez un objet Chat. C'est moins évident qu'elle va faire la bonne chose pour n'importe quel objet avec les trois accesseurs appropriés, peu importe comment cet objet fournit ces accesseurs et peu importe quel type d'objet il est : Chat, Chaton ou ChatTigre. afficher_details() considère qu'un invoquant est valide s'il prend en charge trois méthodes, nom(), age() et diete(), qui ne prennent aucun argument et retournent chacune quelque chose qui peut être concaténé dans un contexte de chaîne. Vous pouvez avoir une centaine de classes différentes dans votre code, sans aucun rapport évident entre elles, mais elles pourront être utilisées dans cette fonction si elles sont conformes à ce comportement attendu.
Cette propriété est appelée polymorphisme. Cela signifie que vous pouvez remplacer un objet d'une classe par un objet d'une autre classe s'il fournit la même interface externe.
Typage du canard
Certains langages et environnements exigent que vous déclariez (ou au moins suggériez) une relation formelle entre deux classes avant d'autoriser un programme de remplacer une instance de l'une par une instance de l'autre. Perl fournit des moyens pour faire appliquer ces contrôles, mais ne les exige pas. Son système ad hoc par défaut vous permet de traiter les deux instances ayant des méthodes du même nom comme équivalentes. Certaines personnes appellent cela typage du canard, prétextant que tout objet qui peut cancaner() ou nasiller() ressemble suffisamment à un canard pour pouvoir être pris pour un canard.
Imaginez comment vous pourriez énumérer les animaux de tout un zoo sans cette fonction polymorphe. L'avantage de la généricité devrait être évident. De même, tous les détails spécifiques sur la façon de calculer l'âge d'un ocelot ou d'un poulpe peuvent appartenir à la classe concernée là où cela importe le plus.
Bien sûr, la simple existence d'une méthode appelée nom() ou age() n'implique pas en soi le comportement de cet objet. Un objet Chien peut avoir une méthode age() qui est un accesseur de sorte que vous pouvez découvrir que $rodney a onze ans, mais $lucky en a six. Un objet Fromage pourrait avoir une méthode age() vous permettant de contrôler la durée du stockage de $cheddar pour l'affiner. age() peut être un accesseur dans une classe, mais pas dans une autre :
# quel est l'âge du chat ?
my $annees
=
$zeppie-
>
age;
# stocker le fromage dans l'entrepôt pendant six mois
$fromage-
>
age;
Il est parfois utile de savoir que fait un objet et ce que cela signifie.
VIII-A-5. Rôles▲
Un rôle est une collection nommée de comportements et d'états. Voir les documents de conception P6 sur les rôles à http://design.perl6.org/S14.html et la recherche sur les traits Smalltalk à http://scg.unibe.ch/research/traits pour plus de détails. Alors qu'une classe organise des comportements et d'états dans un modèle pour objets, un rôle organise une collection nommée de comportements et d'états. Vous pouvez instancier une classe, mais pas un rôle. Un rôle est quelque chose qu'une classe fait.
Étant donné un Animal qui a un âge et un Fromage qui peut vieillir, une différence peut être que l'Animal a le rôle EtreVivant, tandis que le Fromage a le rôle Stockable :
Le mot-clé requires fourni par Moose::Role vous permet de répertorier les méthodes que ce rôle requiert dans ses classes composant. Autrement dit, tout ce que ce rôle doit faire est de fournir les méthodes nom(), age() et diete(). La classe Chat doit marquer explicitement le fait qu'elle remplit le rôle :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'diete'
, is =>
'rw'
, isa =>
'Str'
;
has 'annee_naiss'
,
is =>
'ro'
,
isa =>
'Int'
,
default =>
sub {
(localtime
)[5
] +
1900
}
;
with 'EtreVivant'
;
sub age {
... }
}
La ligne qui commence par le mot-clé with oblige Moose à composer le rôle EtreVivant dans la classe Chat. La composition garantit que tous les attributs et les méthodes du rôle font partie de la classe. EtreVivant exige à toute classe composante de fournir des méthodes nommées nom(), age() et diete(). Chat respecte ces contraintes. Si EtreVivant était composé dans une classe qui ne fournit pas ces méthodes, Moose lancerait une exception.
L'ordre compte !
Le mot-clé with appliquant des rôles à une classe doit apparaître après la déclaration des attributs afin que la composition puisse identifier tous les accesseurs générés.
Maintenant, toutes les instances de Chat retourneront la valeur vraie lorsqu'on leur demandera si elles fournissent le rôle EtreVivant. Les objets Fromage ne le doivent pas :
say
'Vivant !'
if $fluffy-
>
DOES('EtreVivant'
);
say
'Moisi !'
if $fromage-
>
DOES('EtreVivant'
);
Cette technique de conception sépare les capacités des classes et des objets de la mise en œuvre de ces mêmes classes et objets. Le comportement de calcul de l'année de naissance de la classe Chat pourrait être lui-même un rôle :
package CalculerAge::De::AnneeNaissance
{
use Moose::Role;
has 'annee_naiss'
,
is =>
'ro'
,
isa =>
'Int'
,
default =>
sub {
(localtime
)[5
] +
1900
}
;
sub age
{
my $self
=
shift
;
my $annee
=
(localtime
)[5
] +
1900
;
return $annee
-
$self-
>
annee_naiss;
}
}
Sortir ce rôle de la classe Chat rend le comportement disponible à d'autres classes. Maintenant, Chat peut composer les deux rôles :
package Chat
{
use Moose;
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'diete'
, is =>
'rw'
;
with 'EtreVivant'
,
'CalculerAge::De::AnneeNaissance'
;
}
Remarquez comment la méthode age() de CalculerAge::De::AnneeNaissance satisfait les exigences du rôle EtreVivant. Notez également que pour toute vérification effectuée par Chat, EtreVivant retourne une valeur vraie. Extraire age() dans un rôle a changé uniquement les détails du comment Chat calcule un âge. C'est toujours un EtreVivant. Chat peut choisir de mettre en œuvre son propre âge ou l'obtenir ailleurs. Tout ce qui compte c'est qu'il fournit une méthode age() qui satisfait la contrainte de EtreVivant.
Vous souvenez-vous comment le polymorphisme signifie que vous pouvez traiter de la même manière plusieurs objets ayant le même comportement ? Un objet peut mettre en œuvre le même comportement de multiples façons. C'est l'allomorphisme. Généraliser l'allomorphisme peut réduire la taille de vos classes et augmenter le code partagé entre elles. Il vous permet également de nommer des collections spécifiques et distinctes de comportements très utile pour tester les capacités plutôt que les implémentations.
Pour comparer des rôles à d'autres techniques de conception telles que les mixins, l'héritage multiple et le monkeypatching, voir http://www.modernperlbooks.com/mt/2009/04/the-why-of-perl-roles.html.
VIII-A-5-a. Rôles et DOES()▲
Lorsque vous composez un rôle dans une classe, la classe et ses instances retourneront la valeur vraie lorsque vous appelez DOES() sur elles :
say
'Ce Chat est vivant !'
if $chaton-
>
DOES( 'EtreVivant'
);
VIII-A-6. Héritage▲
Le système objet de Perl prend en charge l'héritage, qui établit une relation parent-enfant entre deux classes, tel que l'une spécialise l'autre. La classe enfant se comporte de la même manière que son parent elle a le même nombre et type d'attributs et peut utiliser les mêmes méthodes. Elle peut avoir des données et des comportements supplémentaires, mais vous pouvez utiliser n'importe quelle instance d'un enfant là où le code attend son parent. Dans un sens, une sous-classe fournit le rôle impliqué par l'existence de sa classe parente.
Rôles versus héritage
Devriez-vous utiliser des rôles ou l'héritage ? Les rôles offrent la sécurité au moment de la composition, une meilleure vérification de type, une meilleure factorisation du code et un contrôle plus fin sur les noms et comportements, mais l'héritage est plus familier aux développeurs expérimentés d'autres langages. Utilisez l'héritage lorsqu'une classe en étend vraiment une autre. Utilisez un rôle lorsqu'une classe a besoin d'un comportement supplémentaire et quand vous pouvez donner à ce comportement un nom significatif.
Considérez une classe SourceDeLumiere qui fournit deux attributs publics (active et puissance_en_bougies) et deux méthodes (allumer et éteindre) :
package SourceDeLumiere {
use Moose;
has 'puissance_en_bougies'
,
is =>
'ro'
,
isa =>
'Int'
,
default =>
1
;
has 'active'
,
is =>
'ro'
,
isa =>
'Bool'
,
default =>
0
,
writer =>
'_set_active'
;
sub allumer {
my $self
=
shift
;
$self-
>
_set_active(1
);
}
sub eteindre {
my $self
=
shift
;
$self-
>
_set_active(0
);
}
}
(Notez que l'option writer de active crée un accesseur privé, utilisable dans la classe pour définir la valeur.)
VIII-A-6-a. Héritage et attributs▲
Une sous-classe de SourceDeLumiere pourrait définir une puissance lumineuse industrielle qui fournit cent fois plus de lumière :
package SuperBougie
{
use Moose;
extends 'SourceDeLumiere'
;
has '+puissance_en_bougies'
, default =>
100
;
}
Le mot-clé extends est suivi par une liste de noms de classe à utiliser comme parents de la classe courante. Si c'était la seule ligne dans cette classe, les objets SuperBougie se comporteraient de la même manière que les objets SourceDeLumiere. Une instance SuperBougie aurait les attributs puissance_en_bougies et active ainsi que les méthodes allumer() et eteindre().
Le + au début du nom d'un attribut (comme puissance_en_bougies) indique que la classe courante fait quelque chose de spécial avec cet attribut. Ici, la super bougie surcharge (redéfinit) la valeur par défaut de la source de lumière, donc toute nouvelle SuperBougie créée donne autant de lumière que cent bougies habituelles.
Lorsque vous appelez allumer() ou eteindre() sur un objet SuperBougie, Perl cherchera la méthode dans la classe SuperBougie. S'il n'y a aucune méthode de ce nom dans la classe enfant, Perl regardera dans chaque classe parente, de manière récursive. Dans ce cas-ci, ces méthodes se trouvent dans la classe de SourceDeLumiere.
L'héritage des attributs fonctionne de façon similaire (voir perldoc Class::MOP).
VIII-A-6-b. Ordre de recherche des méthodes▲
Un appel de méthode implique toujours une stratégie de recherche. Cette stratégie établit comment Perl choisit la méthode appropriée. Cela peut sembler évident, compte tenu de la simplicité de la classe Chat, mais une grande partie de la puissance de l'OO vient de la recherchedes méthodes.
L'ordre de recherche des méthodes (ou l'ordre de résolution des méthodes ou MRO) est évident pour les classes ayant une seule parente. Regardez dans la classe de l'objet, puis dans sa parente et ainsi de suite jusqu'à ce que vous trouviez la méthode ou épuisiez les classes parentes. Les classes qui héritent de plusieurs parentes (héritage multiple) Aeroglisseur étend tant Bateau que Voiture nécessitent une recherche plus délicate. Le raisonnement sur l'héritage multiple est complexe. Évitez l'héritage multiple lorsque cela est possible.
Perl utilise une stratégie de résolution de méthode en profondeur. Il recherche la classe de la première parente nommée et récursivement dans toutes les parentes de cette parente avant de rechercher dans les autres classes parentes de la classe courante. Le pragma mro (PragmasPragmas) fournit des stratégies de rechange comme la stratégie C3 MRO qui recherche dans les parentes immédiates d'une classe donnée avant de chercher dans l'une de leurs parentes.
Voir perldoc mro pour plus de détails.
VIII-A-6-c. Héritage et méthodes▲
Comme pour les attributs, les sous-classes peuvent redéfinir des méthodes. Imaginez une lumière que vous ne pouvez pas éteindre :
L'appel de eteindre() sur un bâton lumineux ne fait rien, même si la même méthode de SourceDeLumiere fait quelque chose. La recherche de méthode trouvera la méthode de la sous-classe. Vous n'avez peut-être pas voulu faire cela. Quand vous le faites, utilisez le mot-clé override de Moose pour exprimer clairement votre intention.
À l'intérieur d'une méthode redéfinie, super() de Moose vous permet d'appeler la méthode de la classe parente que vous êtes en train de surcharger :
Package SourceDeLumiere::Grincheux
{
use Carp 'carp'
;
use Moose;
extends 'SourceDeLumiere'
;
override allumer =>
sub
{
my $self
=
shift
;
carp "Impossible d'allumer une source de lumière allumée !"
if $self-
>
active;
super();
}
;
override eteindre =>
sub
{
my $self
=
shift
;
carp "Impossible d'éteindre une source de lumière éteinte!"
unless $self-
>
active;
super();
}
;
}
Cette sous-classe ajoute un avertissement lorsque vous essayez d'allumer ou d'éteindre une source de lumière se trouvant déjà dans l'état demandé. La fonction super() recherche ensuite la mise en œuvre dans la parente la plus proche de la méthode courante, par ordre normal de résolution de méthode Perl. (Voir perldoc Moose::Manual::MethodModifiers pour plus d'options de recherche.)
VIII-A-6-d. Héritage et isa()▲
La méthode isa() de Perl retourne vrai si son invoquant est ou étend une classe nommée. Cet invoquant peut être le nom d'une classe ou une instance d'un objet :
say
'On dirait une SourceDeLumiere'
if $bougeoir-
>
isa( 'SourceDeLumiere'
);
say
'Les Hominidés ne s'
allument pas'
unless $chimpanze->isa( '
SourceDeLumiere' );
VIII-A-7. Moose et Perl OO▲
Moose offre de nombreuses fonctionnalités au-delà de l'OO par défaut de Perl. Certes, vous pouvez construire vous-même tout ce que vous obtenez avec Moose (Références liéesRéférences liées), ou le bricoler avec une série de distributions du CPAN, mais Moose en vaut vraiment la peine. C'est un ensemble cohérent, avec une bonne documentation. De nombreux projets importants l'utilisent avec succès. Sa communauté de développement est mature et attentive.
Moose s'occupe des constructeurs, des destructeurs, des accesseurs et de l'encapsulation. Vous devez faire le travail de déclarer ce que vous voulez, mais ce que vous obtenez en retour est sûr et facile à utiliser. Les objets Moose peuvent étendre et utiliser des objets du système objet standard de Perl.
Moose permet également la métaprogrammation manipuler vos objets par le biais de Moose même. Si vous vous êtes déjà demandé quelles méthodes sont disponibles sur une classe ou un objet ou quels attributs un objet prend en charge, cette information est disponible :
my $metaclass
=
Singe::Pantalons->
meta;
say
'Les instances de Singe::Pantalons ont les attributs :'
;
say
$_
->
nom for $metaclass-
>
get_all_attributes;
say
'Les instances de Singe::Pantalons supportent les méthodes :'
;
say
$_
->
fully_qualified_name
for $metaclass-
>
get_all_methods;
Vous pouvez même voir quelles classes étendent une classe donnée :
my $metaclass
=
Singe->
meta;
say
' Singe est la superclasse de :'
;
say
$_
for $metaclass-
>
subclasses;
Voir perldoc Class::MOP::Class pour plus d'informations sur les opérations de métaclasse et perldoc Class::MOP pour des informations de métaprogrammation Moose.
Moose et son protocole métaobjet (ou MOP) offrent la possibilité d'une meilleure syntaxe pour déclarer et travailler avec des classes et des objets en Perl. Ce code est valide :
use MooseX::Declare;
role EtreVivant {
requires qw( name age diet )
}
role CalculerAge::De::AnneeNaissance {
has 'annee_naiss'
,
is =>
'ro'
,
isa =>
'Int'
,
default =>
sub {
(localtime
)[5
] +
1900
}
;
method age
{
return (localtime
)[5
] +
1900
-
$self-
>
annee_naiss;
}
}
class Chat with EtreVivant
with CalculerAge::De::AnneeNaissance
{
has 'nom'
, is =>
'ro'
, isa =>
'Str'
;
has 'diete'
, is =>
'rw'
;
}
Le module CPAN MooseX::Declare utilise Devel::Declare pour ajouter la nouvelle syntaxe Moose spécifique. Les mots-clés class, role et method réduisent la quantité de code standard nécessaire pour écrire du bon code orienté objet en Perl. Remarquez en particulier la nature déclarative de cet exemple, ainsi que l'absence de my $self = shift; dans age().
Si vous utilisez Perl 5.14 ou une version plus récente, Devel::Declare est moins utile ; Perl prend lui-même en charge un système d'ajout de mots-clés à la syntaxe. Dans ce cas, un module d'adaptation de la syntaxe comme MooseX::Méthode::Signatures ou Moops peut être plus à votre goût.
Alors que Moose ne fait pas partie du standard Perl, sa popularité garantit qu'il est disponible sur de nombreuses distributions de systèmes d'exploitation. Les distributions Perl telles que Strawberry Perl et ActivePerl (deux distributions de Perl pour Windows) l'incluent aussi. Même si Moose est un module du CPAN et pas une bibliothèque standard, sa propreté et sa simplicité le rendent essentiel à la programmation Perl moderne.
Une version light de Moose
Moose n'est pas une petite bibliothèque, mais il est puissant. Une autre solution populaire est Moo, une bibliothèque plus légère qui est presque entièrement compatible avec Moose. Moo n'a pas certaines des fonctionnalités de métaprogrammation de Moose, mais la majorité du code n'en a pas besoin. Vous pouvez facilement commencer un projet avec Moo et migrer sans aucune difficulté à Moose si et quand vous avez besoin de puissance supplémentaire.
VIII-B. Références liées▲
Le système objet standard Perl est volontairement minimal. Il n'a que trois règles :
- une classe est un paquetage ;
- une méthode est une fonction ;
- une référence (liée à une classe) est un objet.
Vous pouvez construire tout le reste sur ces trois règles. Ce minimalisme peut être peu pratique pour les grands projets en particulier, les possibilités pour une plus grande abstraction par métaprogrammation (Génération de codeGénération de code) sont maladroites et limitées. Moose (MooseMoose) est un meilleur choix pour les programmes modernes de plus de quelques centaines de lignes, bien que beaucoup de code existant utilise toujours l'OO par défaut de Perl.
Vous avez vu déjà les deux premières règles. La commande interne bless (le sens commun du verbe to bless est « bénir ») associe le nom d'une classe à une référence. Cette référence est maintenant un invoquant valide, et Perl effectuera sur elle la recherche de méthodes, en utilisant la classe associée.
Un constructeur est une méthode qui crée et « bénit » une référence, c'est-à-dire la lie à une classe. Par convention, les constructeurs ont généralement le nom new(), mais ce n'est pas une exigence. Les constructeurs sont également presque toujours des méthodes de classe.
bless prend deux opérandes, une référence et un nom de classe, et évalue la référence. La référence peut être toute référence valide, vide ou non. Il n'est pas nécessaire que la classe existe déjà. Vous pouvez même utiliser bless à l'extérieur d'un constructeur ou d'une classe... mais vous violez l'encapsulation en exposant les détails de la construction de l'objet en dehors d'un constructeur. Un constructeur peut être aussi simple que :
Par conception, ce constructeur reçoit le nom de la classe comme invoquant de la méthode. Vous pouvez également coder en dur le nom d'une classe, au détriment de la flexibilité. Un constructeur paramétrable permet la réutilisation par héritage, délégation ou exportation.
Le type de référence utilisé est pertinent uniquement pour la façon dont l'objet stocke ses propres données d'instance. Il n'a pas d'autre effet sur l'objet résultant. Les références de hachage sont les plus communes, mais vous pouvez lier tout type de référence :
my $array_obj
=
bless
[], $class
;
my $scalar_obj
=
bless
\$scalar
, $class
;
my $func_obj
=
bless
\&
some_func, $class
;
Les classes Moose définissent des attributs d'objets de façon déclarative, mais l'OO par défaut de Perl est laxiste. Une classe représentant des joueurs de basket-ball qui stocke le numéro de maillot et la position pourrait utiliser un constructeur comme celui-ci :
... et créer des joueurs comme ceci :
my $joel
=
Joueur->
new( numero =>
10
,
position =>
'pivot'
);
my $damien
=
Joueur->
new( numero =>
0
,
position =>
'meneur'
);
Les méthodes de la classe peuvent accéder directement aux attributs des objets en tant qu'éléments du hachage :
sub format
{
my $self
=
shift
;
return '#'
. $self-
>{
numero}
. ' joue '
. $self-
>{
position}
;
}
... mais un programme utilisant la classe peut aussi le faire, donc tout changement de la représentation interne de l'objet peut nuire à un autre programme. Il est bien plus sûr d'utiliser des accesseurs :
... et maintenant vous commencez à écrire vous-même ce que Moose vous offre gratuitement. Mieux encore, Moose encourage les gens à utiliser des accesseurs au lieu de l'accès direct en cachant le code de génération d'accesseur. Adieu, la tentation.
VIII-B-1. Recherche de méthode et héritage▲
Sur une référence liée, un appel de méthode de la forme :
my $numero
=
$joel-
>
numero;
... recherche le nom de la classe associée à la référence liée $joel dans ce cas, Joueur. Ensuite, Perl recherche dans Joueur une fonction (rappelez-vous que Perl ne fait aucune distinction entre fonctions dans un espace de noms et méthodes) nommée numero(). Si une telle fonction n'existe pas et que Joueur étend une classe parente, Perl regarde dans la classe parente (et ainsi de suite et ainsi de suite) jusqu'à ce qu'il trouve numero(). Si Perl trouve numero(), il appelle cette méthode avec $joel comme invoquant.
Garder propres les espaces de noms
Le module du CPAN namespace::autoclean peut aider à éviter les collisions accidentelles entre fonctions importées et méthodes.
Moose fournit le mot-clé extends pour suivre les relations d'héritage, mais le système objet de Perl utilise une variable globale de paquetage nommée @ISA. (10) Le mécanisme de résolution de méthode recherche dans chaque @ISA d'une classe pour trouver les noms de ses classes parentes. Si la classe JoueurAccidente hérite de la classe Joueur, vous pouvez écrire :
package JoueurAccidente
{
@JoueurAccidente
::ISA =
'Joueur'
;
}
Le pragma parent (PragmasPragmas) est plus propre (des programmes anciens peuvent aussi utiliser le pragma base, mais parent a remplacé base depuis Perl 5.10) :
Moose a son propre métamodèle qui stocke des informations plus amples sur l'héritage. Celui-ci lui permet d'offrir des possibilités supplémentaires de métaprogrammation.
Vous pouvez hériter de plusieurs classes parentes :
... bien que les mises en garde au sujet de l'héritage multiple et la complexité de la recherche de méthode s'appliquent. Envisagez à la place les rôles (RôlesRôles) ou les modificateurs de méthode de Moose.
VIII-B-2. AUTOLOAD▲
S'il n'y a aucune méthode utilisable dans la classe de linvoquant ou dans l'une de ses superclasses, Perl recherchera ensuite une fonction AUTOLOAD() (AUTOLOADAUTOLOAD) dans chaque classe selon l'ordre de résolution de méthode choisi. Perl appellera la première fonction AUTOLOAD() qu'il trouve pour fournir ou refuser la méthode souhaitée.
AUTOLOAD() rend l'héritage multiple encore beaucoup plus difficile à comprendre.
VIII-B-3. Surcharge de méthode et SUPER▲
Comme avec Moose, vous pouvez surcharger des méthodes dans l'OO standard de Perl. Contrairement à Moose, Perl ne prévoit aucun mécanisme explicite pour indiquer votre intention de surcharger la méthode d'une classe parente. Encore pire, n'importe quelle fonction que vous prédéclarez, déclarez, ou importez dans la classe fille peut surcharger une méthode ayant le même nom dans la classe parente. Même si vous oubliez d'utiliser le système override de Moose, au moins il a le mérite d'exister. L'OO basique de Perl n'offre pas une telle protection.
Pour surcharger une méthode dans une classe fille, déclarez simplement une méthode du même nom que la méthode dans la parente. Dans une méthode surchargée, vous pouvez appeler la méthode de la classe parente avec le préfixe SUPER:: :
sub overridden
{
my $self
=
shift
;
warn
'Appel à overridden() dans la classe enfant !'
;
return $self-
>
SUPER::overridden( @_
);
}
Le préfixe SUPER:: du nom de la méthode indique au mécanisme de recherche de méthode de rechercher une méthode redéfinie du nom approprié. Vous pouvez fournir vos propres arguments à la méthode surchargée, mais la plus grande partie du code réutilise @_. Pensez à bien récupérer l'invoquant si vous le faites.
Linconvénient de SUPER::
SUPER:: a un dysfonctionnement déroutant : il recherche dans la classe mère du paquetage dans lequel la méthode surchargée a été compilée. Si vous avez importé cette méthode depuis un autre paquetage, Perl recherchera volontiers dans la mauvaise classe mère. Le désir de rétrocompatibilité ascendante a gardé en place ce dysfonctionnement. Le module SUPER du CPAN offre une solution de contournement. super() de Moose ne souffre pas du même problème.
VIII-B-4. Stratégies pour faire face aux références liées▲
Les références liées peuvent sembler minimales, difficiles et déroutantes. Elles le sont. Moose est beaucoup plus facile à utiliser, alors utilisez-le autant que possible. Si vous devez maintenir du code qui utilise des références liées, ou si vous ne pouvez pas encore convaincre votre équipe à utiliser Moose pleinement, vous pouvez contourner certains des problèmes de références liées avec de la discipline.
- ne mélangez pas fonctions et méthodes dans la même classe ;
- utilisez un fichier .pm unique pour chaque classe, sauf s'il s'agit d'une petite classe auxiliaire autonome utilisée depuis un seul endroit ;
- suivez les recommandations standard de Perl OO, en nommant les constructeurs new() et en utilisant $self comme nom de l'invoquant dans votre documentation ;
- utilisez des accesseurs le plus possible, même au sein des méthodes de votre classe. Un module comme Class::Accessor permet d'éviter le code répétitif ;
- évitez AUTOLOAD() autant que possible. Si vous devez l'utiliser, utilisez des déclarations de fonctions avancées (Déclarer des fonctionsDéclarer des fonctions) pour éviter toute ambiguïté ;
- attendez-vous à ce que quelqu'un quelque part crée un jour une sous-classe (ou délègue, ou réimplémente l'interface) de vos classes. Facilitez-leur la tâche en ne supposant pas les détails sur le fonctionnement interne de votre code, en utilisant la forme à deux arguments de bless et en décomposant vos classes dans des unités plus petites responsables de code ;
- utilisez des modules auxiliaires comme Role::Tiny pour permettre une meilleure utilisation et réutilisation.
VIII-C. Réflexion▲
La réflexion (ou l'introspection) est le processus d'interrogation d'un programme au sujet de lui-même pendant l'exécution. En traitant le code sous forme de données, vous pouvez gérer le code de la même manière que les données. C'est le principe derrière la génération du code (Génération de codeGénération de code).
Class::MOP de Moose (Class::MOPLa classe Class::MOP) simplifie de nombreuses tâches de réflexion pour les systèmes d'objets. Si vous utilisez Moose, son système de métaprogrammation vous sera utile. Sinon, plusieurs autres idiomes Perl vous aideront à inspecter et manipuler les programmes en cours d'exécution.
VIII-C-1. Vérifier si un module est chargé▲
Si vous connaissez le nom d'un module, vous pouvez vérifier si Perl estime qu'il a chargé ce module en regardant dans le hachage %INC. Lorsque Perl charge du code avec use ou require, il stocke dans %INC une entrée dont la clé est le chemin du fichier du module à charger et la valeur est le chemin complet sur le disque vers ce module. En d'autres termes, le chargement de Modern::Perl fait effectivement :
$INC
{
'Modern/Perl.pm'
}
=
'.../lib/site_perl/5.12.1/Modern/Perl.pm'
;
Les détails du chemin varieront en fonction de votre installation. Pour tester si Perl a chargé avec succès un module, convertir le nom du module dans la forme canonique de fichier et vérifier l'existence de cette clé dans %INC :
sub module_loaded
{
(my $nom_module
=
shift
) =~
s!
::!/!
g;
return exists
$INC
{
$nom_module
. '.pm'
}
;
}
Comme dans le cas de @INC, tout code peut manipuler %INC n'importe où. Certains modules (tels que Test::MockObject ou Test::MockModule) manipulent %INC pour de bonnes raisons. Selon votre niveau de paranoïa, vous pouvez vérifier vous-même le chemin d'accès et le contenu attendu du paquetage.
La fonction is_class_loaded() du module Class::Load de CPAN le fait pour vous sans vous obliger à manipuler explicitement %INC.
VIII-C-2. Vérifier si un paquetage existe▲
Pour vérifier qu'un paquetage existe quelque part dans votre programme si du code quelque part a exécuté une directive package avec un nom, vérifiez si le paquetage hérite de UNIVERSAL. Tout ce qui étend UNIVERSAL doit d'une façon ou d'une autre fournir la méthode can(). Si ce paquetage n'existe pas, Perl lèvera une exception concernant un invoquant invalide ; donc englobez cet appel dans un bloc eval :
Une autre approche consiste à explorer les tables de symboles de Perl. Mais là, débrouillez-vous (si vous savez ce que vous faites).
VIII-C-3. Vérifier si une classe existe▲
Comme Perl ne fait aucune distinction forte entre paquetages et classes, le mieux que vous puissiez faire sans Moose est de vérifier si un paquetage du nom de la classe attendue existe. Vous pouvez tester si le paquetage fournit new() en utilisant can(), mais rien ne garantit que le new() que vous trouverez peut-être soit une méthode ou un constructeur.
VIII-C-4. Vérifier le numéro de version d'un module▲
Les modules n'ont pas à fournir des numéros de version, mais chaque paquetage hérite de la méthode VERSION() de la classe parente universelle UNIVERSAL (Le paquetage UNIVERSALLe paquetage UNIVERSAL) :
my $version
=
$module-
>
VERSION;
VERSION() retourne le numéro de version du module donné, s'il est défini. Sinon, elle retourne undef. Si le module n'existe pas, la méthode retournera également undef.
VIII-C-5. Vérifier si une fonction existe▲
Pour vérifier si une fonction existe dans un paquetage, appelez can() comme une méthode de classe sur le nom du paquetage :
say
"
$func
() exists"
if $pkg-
>
can( $func
);
Perl lancera une exception si $pkg n'est pas un invoquant valide ; englobez l'appel de méthode dans un bloc eval si vous avez des doutes sur sa validité. Attention, une fonction mise en œuvre à l'aide de AUTOLOAD() (AUTOLOADAUTOLOAD) peut fournir une réponse fausse si le paquetage de la fonction n'a pas prédéclaré la fonction ou surchargé can() correctement. C'est un bogue dans l'autre paquetage.
Utilisez cette technique pour déterminer si la méthode import() d'un module a importé une fonction dans l'espace de noms courant :
say
"
$func
() importée!"
if __PACKAGE__->
can( $func
);
Comme pour la vérification de l'existence d'un paquetage, vous pouvez faire vous-même le tour des tables de symboles, si vous avez la patience pour cela.
VIII-C-6. Vérifier si une méthode existe▲
Il n'y a aucun moyen d'introspection infaillible pour distinguer une fonction d'une méthode.
VIII-C-7. Rechercher dans les tables de symboles▲
Une table de symboles est un type spécial de hachage dont les clés sont les noms des symboles globaux du paquetage et les valeurs sont des typeglob. Une typeglob est une structure interne de données qui peut contenir une partie ou lentièreté d'un scalaire, un tableau, un hachage, un descripteur de fichier et une fonction.
Accédez à une table de symboles comme à un hachage en ajoutant de doubles deux-points au nom du paquetage. Par exemple, la table de symboles pour le paquetage SingeGrinder est disponible comme %SingeGrinder::.
Vous pouvez tester l'existence des noms de symboles spécifiques dans une table de symboles avec l'opérateur exists (ou manipuler la table de symboles pour ajouter ou supprimer des symboles, si vous le souhaitez). Sachez pourtant que certains changements dans les standards Perl ont modifié les détails de ce que les typeglobs stockent, et quand, et pourquoi.
Consultez la section « Symbol Tables » dans perldoc perlmod pour plus de détails, puis préférez les autres techniques dans cette section sur l'introspection. Si vous devez vraiment manipuler des tables de symboles et des typeglobs, pensez à utiliser plutôt le module Package::Stash du CPAN.
VIII-D. Perl OO avancé▲
La création et l'utilisation d'objets en Perl avec Moose sont faciles (MooseMoose). La conception de bons programmes ne l'est pas. Vous devez trouver l'équilibre entre trop de conception et pas assez. Seule l'expérience pratique peut vous aider à comprendre les techniques de conception les plus importantes, mais plusieurs principes peuvent vous guider.
VIII-D-1. Préférer la composition à l'héritage▲
Les débutants dans la conception OO abusent souvent de l'utilisation de l'héritage pour réutiliser le code et exploiter le polymorphisme. Le résultat est une hiérarchie de classes profonde avec dispersion des responsabilités dans les mauvais endroits. Maintenir ce code est difficile qui sait où ajouter ou modifier un comportement ? Qu'advient-il lorsque le code dans un endroit se trouve en conflit avec le code déclaré ailleurs ?
L'héritage n'est qu'un des nombreux outils pour les programmeurs OO. Ce n'est pas toujours le bon outil ; c'est même souvent le mauvais outil. Une Voiture peut étendre Vehicule::AvecRoues (une relation est-une), mais Voiture peut mieux contenir plusieurs objets Roue comme attributs d'instance (une relation a-une).
La décomposition des classes complexes en entités plus petites, focalisées (des classes ou des rôles) améliore l'encapsulation et réduit la possibilité qu'une classe ou un rôle en fasse trop. Les entités plus petites, plus simples, et mieux encapsulées sont plus faciles à comprendre, tester et maintenir.
VIII-D-2. Principe de responsabilité unique▲
Lorsque vous concevez votre système objet, examinez les responsabilités de chaque entité. Par exemple, un objet Employe peut représenter de l'information spécifique sur le nom d'une personne, ses coordonnées et autres données personnelles, alors qu'un objet Travail peut représenter ses responsabilités professionnelles. La séparation de ces entités par leurs responsabilités permet à la classe Employe de s'occuper uniquement du problème de la gestion de l'information spécifique sur qui est la personne et à la classe Travail de représenter ce que fait la personne. (Par exemple, deux Employe peuvent avoir un arrangement de partage de Travail, ou un Employe peut avoir le Travail de directeur financier et celui de directeur général.)
Lorsque chaque classe possède une responsabilité unique, vous améliorez l'encapsulation des données et des comportements spécifiques dans la classe et réduisez le couplage entre classes.
VIII-D-3. Ne vous répétez pas▲
La complexité et la duplication compliquent le développement et la maintenance. Le principe DRY (Don't Repeat Yourself) doit vous inciter à rechercher et éliminer la duplication au sein du système. La duplication existe dans les données ainsi que dans le code. Au lieu de répéter les informations de configuration, les données de l'utilisateur et d'autres objets dans votre système, créez une seule représentation canonique de cette information à partir de laquelle vous pouvez générer les autres artefacts.
Ce principe permet de réduire la possibilité que des parties importantes de votre système se désynchronisent. Il vous aide également à trouver la représentation optimale du système et de ses données.
VIII-D-4. Principe de substitution de Liskov▲
Le principe de substitution de Liskov suggère que vous devez être en mesure de remplacer une spécialisation d'une classe ou d'un rôle par l'original sans violer l'API de l'original. Autrement dit, un objet doit être aussi ou plus général en ce qui concerne ce qu'il attend et au moins aussi précis sur ce qu'il produit, que l'objet qu'il remplace.
Imaginez deux classes, une classe mère Dessert et sa classe fille TarteAuNoixDePecan. Si les classes suivent le principe de substitution Liskov, vous pouvez remplacer chaque utilisation des objets Dessert avec des objets TarteAuNoixDePecan dans la suite de tests, et tous les tests devraient réussir. Voir « IS-STRICTLY-EQUIVALENT-TO-A » de Reg Braithwaite pour plus de détails, http://weblog.raganwald.com/2008/04/is-strictly-equivalent-to.html.
VIII-D-5. Sous-types et coercitions▲
Moose vous permet de déclarer et d'utiliser des types et les étend à travers des sous-types pour former des descriptions de plus en plus spécialisées de ce que représentent vos données et comment elles se comportent. Ces annotations de type aident à vérifier si les données sur lesquelles vous voulez travailler dans des fonctions ou des méthodes spécifiques sont appropriées et même à préciser les mécanismes de coercition des données d'un type vers un autre type.
Par exemple, vous pouvez permettre aux gens de fournir des dates sous forme de chaînes de caractères en entrée à un GrandLivre comptable tout en les représentant en interne comme des instances de type DateTime. Vous pouvez le faire en créant un type Date et en ajoutant une coercition sur le type chaîne. Voir Moose::Util::TypeConstraints et MooseX::Types pour plus d'informations.
VIII-D-6. Immutabilité▲
Les débutants OO traitent souvent des objets comme s'ils étaient des suites d'enregistrements qui utilisent des méthodes pour obtenir et définir des valeurs internes. Cette technique simple conduit à la malheureuse tentation de répartir les responsabilités de l'objet dans l'ensemble du système.
Avec un objet bien conçu, vous lui dites quoi faire, pas comment le faire. En règle générale, si vous vous retrouvez accéder aux données de l'instance d'objet (même à l'aide d'accesseurs) à l'extérieur de l'objet lui-même, il se peut que vous ayez trop d'accès aux données internes d'un objet.
Vous pouvez éviter cet accès inapproprié en rendant vos objets immuables. Fournissez les données nécessaires à leurs constructeurs, puis interdisez toute modification de ces informations à partir de l'extérieur de la classe. N'exposez à aucune méthode de modification les données de l'instance. Une fois que vous avez construit un tel objet, vous savez qu'il est toujours dans un état valide. Vous ne pourrez jamais modifier ses données pour le mettre dans un état non valide.
Cela nécessite une discipline énorme, mais les systèmes qui en résultent sont robustes, testables et maintenables. Certains modèles de conception vont même jusqu'à interdire la modification des données d'instance au sein de la classe elle-même, mais cela est beaucoup plus difficile à réaliser.