points de vue - Mot-clé - programmation orientée objetles déambulations d'un codeur2023-06-18T04:35:57+02:00Miguel Moquillonurn:md5:2d53372d0fa8f28ff50e71462648e0b8DotclearL'extensibilité d'objets métiers dans différents langagesurn:md5:d9f16855bbdf5f44d33ab8ef1572ced22018-04-27T21:29:00+02:002022-08-19T14:01:06+02:00Miguel MoquillonTechniques de programmationHaskellJavaprogrammation fonctionnelleprogrammation impérativeprogrammation orientée objetSmalltalk<p>Il arrive fréquemment qu’avec l’évolution des besoins dans le temps, les objets métiers qui ont été définis auparavant nécessitent d’être étendu par l’ajout de nouvelles fonctionnalités. Selon la nature des langages de programmation, mais aussi selon les caractéristiques propres aux langages, les méthodes d’extensions varient et peuvent être plus ou moins aisées à mettre en œuvre, en particulier lorsque les extensions sont fournies dans des modules (paquetages, bibliothèques, …) à part et que l’existant ne doit pas être impacté par ces ajouts (ou du moins le minimum possible).</p>
<p>Dans ce petit billet je voudrais vous présenter certaines d’entre elles, et en particulier dans le contexte présenté ci-dessus, et ceci avec trois langages de programmations différents : Java, un langage impératif (orienté classe), Smalltalk, un des rares langages qui soient vraiment orienté objet, et Haskell, un langage fonctionnel.</p> <p>Pour illustrer les différentes techniques d’extensibilité, je vous propose un exemple simple de la gestion d’utilisateurs. Celui-ci s’articule autour du type <code>User</code> et des opérations classiques liées à son profil et à son authentification. Par exemple, en Java :</p>
<pre>
<code class="language-java">public class User {
private Identifier id;
private final String firstName;
private String lastName;
private Credentials credentials = null;
protected User() {
// for the persistence engine
}
public User(final String firstName, final String lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.credentials = Credentials.none();
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public String getFullName() {
return this.firstName + ' ' + this.lastName;
}
public void setLastName(final String newLastName) {
this.lastName = newLastName
}
public void setCredentials(final Credentials newCredentials) {
this.credentials = newCredentials.copy();
}
public void authenticate(final Credentials auth) throws AuthenticationException {
this.credentials.check(auth);
}
}</code></pre>
<p>Nous omettons dans notre exemple, pour plus de clarté, tout ce qui est en rapport avec la persistance.</p>
<p>Smalltalk est plus qu’un langage. C’est avant tout un environnement de développement et d’exécution interactif virtuel complet. La programmation se fait essentiellement via ses outils (en l’occurrence le navigateur de classes et le débogueur) et celle-ci n’est pas structurée autour de fichiers comme avec les autres langages. Tout est maintenu dans une image de machine virtuelle (à l’image des VM VMWare ou VirtualBox) . Aussi, pour plus clarté, dans ce qui suit, j’adopte la notation en vigueur de la documentation sur ce langage :</p>
<pre>
<code class="language-smalltalk">Object subclasses: #User
instanceVariableNames:'firstName lastName credentials'
classVariableNames:''
poolDictionaries:''
User >> initializeFirstName: aFirstName lastName: aLastName
firstName := aFirstName.
lastName := aLastName
User class >> firstName: aFirstName lastName: aLastName
|user|
user := self new initializeFirstName: aFirstName lastName: aLastName.
^ user
User >> lastName: newLastName
lastName := newLastName
User >> lastName
^ lastName
User >> fullName
^ self firstName, ' ', self lastName
User >> credentials: newCredentials
credentials := newCredentials
User >> authenticateWith: inputCredentials
credentials check: inputCredentials </code></pre>
<p>Et pour finir en Haskell :</p>
<pre>
<code class="language-haskell">data User = User { firstName :: String,
lastName :: String,
credentials :: Credentials
} deriving (Show)
fullName u = firstName u ++ ' ' ++ lastName u
authenticate u c = checkCredentials (credentials u) c </code></pre>
<p>Quelque temps après, il est décidé de rajouter une fonctionnalité de messagerie instantanée et le choix a été de déléguer sa gestion à un service extérieur, l’application proposant juste le client. Pour ce faire, il est nécessaire de rajouter à l’utilisateur les informations relatives à la messagerie instantanée. Par exemple, avec un service XMPP, à minima le domaine et les crédences propres à la connexion au service Jabber distant. Avec des langages comme Java il existe différentes façon de faire :</p>
<ul>
<li>rajouter directement à la classe <code>User</code> les informations nécessaires,</li>
<li>étendre la classe User par une nouvelle, par exemple <code>ChatUser</code>,</li>
<li>décorer la classe <code>User</code> par une nouvelle classe ou mieux par un <em>trait</em> (un ensemble transverse de propriétés qui peut être tissé à des objets enfin de les enrichir en comportement) et qui serait propre au service de messagerie instantanée.</li>
</ul>
<p>Dans notre contexte, tout ce qui a trait aux utilisateurs est défini dans un module cœur de l’application et le service de messagerie instantanée est apporté par autre un module, optionnel. De plus on veut étendre le module cœur par les nouvelles fonctionnalités mais sans à entacher celui-ci. Au regard de ceci, la première solution est à éviter. Reste les deux dernières. La dernière solution est séduisante parce qu’elle permet à différents modules de décorer les objets métiers en fonctionnalités additionnelles sans que ceux-ci en aient connaissance. En Java, en général, une classe wrapper est écrite :</p>
<pre>
<code class="language-java">public class ChatUser extends User {
private User wrapped;
private Credentials chatCredentials;
private String chatDomain;
protected ChatUser() {
// for the persistence engine
}
protected void setDecoratedUser(final User user) {
this.wrapped = user;
}
public static ChatUser decorate(final User user) {
ChatUser chatUser = ChatUserRepository.getInstance().get(user.getId());
chatUser.setDecoratedUser(user);
return chatUser;
}
public Credentials getChatCredentials() {
return this.chatCredentials;
}
public void setChatCredentials(final Credentials credentials) {
this.chatCredentials = credentials;
}
public String getChatDomain() {
return this.chatDomain;
}
public void setChatDomain(final String domain) {
this.chatDomain = domain;
}
...
}</code></pre>
<p>Dans notre exemple, les propriétés additionnelles à un utilisateur et relatives à la messagerie instantanée sont directement récupérées d’une source de données avant de décorer avec celles-ci un utilisateur de notre application. Les autres méthodes héritées de la classe <code>User</code> délèguent leurs appels aux méthodes correspondantes de l’objet encapsulé. Les IDE en général permettent de générer rapidement celles-ci. Comme les nouvelles propriétés ne seront utilisées que par le code du module responsable de la messagerie instantanée, la décoration ne se fera donc que par lui. Là où cette approche est plus délicate à mettre en œuvre est lorsqu’il y a plusieurs modules qui décorent chacun l’objet métier et que certaines décorations peuvent être transverses et utilisées conjointement par un même code :</p>
<pre>
<code class="language-java">User aUser = ...; // get in fact a ChatUser decorating the DelegationUser that wraps the User
String fullName = aUser.getFullName(); // ok
String chatDomain = ((ChatUser)aUser).getChatDomain(); // ok
User delegate = ((DeletgationUser)aUser).getDelegation().getDelegate(); // ooops aUser isn't a DelegationUser in first place and hence it doesn't understand this invocation!</code></pre>
<p>On peut s’en sortir par différentes approches, par exemple :</p>
<ul>
<li>le code utilisateur utilise <em>explicitement</em> chaque décoration de l’objet métier, par exemple en décorant à nouveau l’objet métier ;</li>
<li>on met en place de façon simplifiée un POM (Protocol Object Model) dans lequel on associe à chaque objet métier son modèle, un objet générique. Celui-ci encapsule en fait l’objet concret auquel il est associé et fait appel à la rétrospection pour requêter celui-ci. Avec cette approche, il suffit juste de demander au modèle, sous forme de chaîne de caractères en Java, telle propriété de décoration ; on passe donc d’une approche de programmation statique à une approche dynamique avec les problèmes que ça peut soulever dans un langage à typage statique.</li>
</ul>
<p>Avec Smalltalk, les choses sont beaucoup plus simples. La solution la plus directe est d’enrichir <em>indirectement</em> l’objet métier, chose que l’on peut faire aussi en Ruby, en Groovy, et même en Kotlin. Toutefois, en Smalltalk, non seulement chaque module peut enrichir les objets d’autres modules (et ceux du langage), mais on peut aussi savoir qui a rajouté quoi. Avec notre notation, ça donnerai ceci :</p>
<pre>
<code class="language-smalltalk">User addInstVarNamed: 'chatDomain chatCredentials'.
User >> chatDomain
^ chatDomain
User >> chatDomain: domain
chatDomain := domain
User >> chatCredentials
^ chatCredentials
User >> chatCredentials: newCredentials
chatCredentials := newCredentials
</code></pre>
<p>Cette solution a l’avantage de répondre simplement au cas de décorations transverses et utilisées conjointement par un même code. Une autre solution serait, par ce même mécanisme, d’ajouter comme variable d’instance dans la classe <code>User</code> l’objet qui rassemble les propriétés à enrichir, par exemple un objet <code>ChatProfile</code>, et d’utiliser le mécanisme interne de délégation de Smalltalk pour rediriger les envois de messages non compris par <code>User</code> à ses délégués (en l’occurrence <code>ChatProfile</code>) ; ceci se fait simplement en surchargeant la méthode <code>doesNotUnderstand:</code> dont un exemple est donné ci-dessous :</p>
<pre>
<code class="language-smalltalk">User >> doesNotUnderstand: aMessage
|return|
return := aMessage sendTo: chatProfile.
^ (return class = ChatProfile)
ifTrue: [ ^ self ]
ifFalse: [ ^ return ]
</code></pre>
<p>Évidemment, nous pouvons rendre le code plus générique en définissant, en lieu et place d’une variable d’instance <code>chatProfile</code>, une liste de délégués dans laquelle est rajouté par le module de la messagerie instantanée une instance de <code>ChatProfile</code>. Il suffit alors dans l’implémentation de <code>doesNotUnderstand:</code> de parcourir l’ensemble des délégués pour vérifier si l’un d’eux sache répondre au message reçu en vue de le lui envoyer. Avec cette approche, on rend l’objet métier <code>User</code> extensible en permettant à chaque module de pouvoir ajouter aisément un décorateur.</p>
<p>Pour finir, voyons comment faire en Haskell. La solution la plus simple est de définir directement les fonctions qui permettent d’obtenir les informations additionnelles à un utilisateur dans le module correspondant à la nouvelle fonctionnalité. En effet, dans l’approche fonctionnelle, les fonctions sont les briques de base de la composition. Par exemple, de façon simplifiée :</p>
<pre>
<code class="language-haskell">module MyApp.Chat(chatCredentials, chatDomain) where
import MyApp.Core.User
import MyApp.Core.Security
...
data ChatInfo = { domain :: String,
credentials :: Credentials }
deriving (Show, Read)
loadChatInfo u = do
...
chatCredentials user = do
let c <- loadChatInfo user
return credentials c
chatDomain user = do
let c <- loadChatInfo user
return domain c
</code></pre>
<p>Toutefois l’approche la plus correcte et la plus extensible serait de profiter des types algébriques pour définir l’ensemble des propriétés qui sont propre d’une part au profil des utilisateurs de l’application, et d’autre part qui sont propre au profil des utilisateurs de la messagerie instantanée, sachant qu’il existe un lien entre un compte utilisateur et celui de la messagerie instantanée. Une solution pourrait être celle-ci :</p>
<pre>
<code class="language-haskell">module MyApp.Core.User (
Profile(..),
User(..)
) where
import MyApp.Core.Security
...
class Profile a where
firstName :: a -> String
lastName :: a -> String
fullName :: a -> String
credentials :: a -> Credentials
data User = User String String Credentials deriving (Show, Read)
instance Profile User where
firstName (User f _ _) = f
lastName (User _ l _) = l
fullName (User f l _) = f ++ " " ++ l
credentials (User _ _ c) = c
</code></pre>
<p>et</p>
<pre>
<code class="language-haskell">module MyApp.Chat (
ChatUser(..),
ChatProfile(..)
) where
import MyApp.Core.User
import MyApp.Core.Security
...
class Profile c => ChatProfile c where
chatDomain :: c -> String
chatCredentials :: c -> Credentials
data ChatUser u = ChatUser u String Credentials deriving (Show, Read)
instance Profile u => Profile (ChatUser u) where
firstName (ChatUser u _ _) = firstName u
lastName (ChatUser u _ _) = lastName u
fullName (ChatUser u _ _) = fullName u
credentials (ChatUser u _ _) = credentials u
instance Profile u => ChatProfile (ChatUser u) where
chatDomain (ChatUser _ domain _) = domain
chatCredentials (ChatUser _ _ credentials) = credentials
</code></pre>
<p>Ceci nécessite évidemment de devoir réécrire le module des utilisateurs si cette approche n’a pas déjà été adoptée. Néanmoins, ensuite, les profils utilisateurs pourront être enrichis par des modules extérieurs sans nécessiter de modification des modules cœurs. De plus, en général, dans une application en Haskell, l’approche de modélisation des fonctions métiers par les types algébriques est couramment utilisée.</p>
<p>Ce petit billet est terminé et j’espère que vous aurez apprécié celui-ci. Ce dernier vous donne d’un simple regard les techniques usuelles d’extensibilité de l’existant dans différents langages et la facilité avec laquelle celles-ci peuvent se faire selon, non seulement la nature du langage, mais aussi le paradigme de programmation sous-jacent.</p>Exemple d'immutabilité en Javaurn:md5:7182459a661f719ee89bc0f66b0608472017-03-11T17:01:00+01:002018-04-27T20:31:02+02:00Miguel MoquillonTechniques de programmationJavaprogrammation orientée objet<p>Dans un billet précédent sur l’égalité d’identité et celle de valeurs, je vous ai parlé d’objets immuables pour lesquels l’égalité de valeur et l’égalité d’identité se confondent. J’ai souvent vu dans divers blogues sur l’immutabilité en Java l’utilisation du mot clé <em>final</em>. J’ai toujours trouvé son usage pour réaliser l’immutabilité comme absurde et surtout par trop contraignant. Pour moi, il ne sert à rien de qualifier les propriétés des objets comme <em>final</em> étant donné que celles-ci doivent être encapsulées selon les principes de la programmation orienté objet. Non, l’immutabilité des objets devrait au contraire se faire au niveau du comportement et surtout des mutateurs de ces objets.</p> <p>Pour illustrer ceci, je vous propose de reprendre l’exemple du billet précédent avec la classe <code>CalendarEvent</code>. Dans cet exemple, les événements d’un agenda sont représentés par les objets métiers de <code>CalendarEvent</code> :</p>
<pre class="brush: java">
public interface CalendarEvent {
static CalendarEvent on(final Period period) {
return new CalendarEventFactory().makeEventOn(period);
}
CalendarEvent planOn(final Calendar calendar);
CalendarEvent withTitle(String aTitle);
CalendarEvent withDescription(String aDescription);
...
}
</pre>
<p>Le comportement de ces objets métiers est quant à lui réalisé par l’interface suivante (qui peut être une classe abstraite) :</p>
<pre class="brush: java">
interface CalendarEventBehaviour extends CalendarEvent {
CalendarEvent setCalendar(final Calendar calendar);
@Override
default CalendarEvent planOn(final Calendar calendar) {
return Transaction.perform(() ->
CalendarEventRepository.get().put(this.setCalendar(calendar))
);
}
@Override
default CalendarEvent update() {
return Transaction.perform(() ->
CalendarEventRepository.get().put(this)
);
...
}
</pre>
<p>Et la structure est donnée par la classe concrête :</p>
<pre class="brush: java">
class CalendarEventStructure implements CalendarEventBehaviour, Cloneable {
private OffsetDateTime startDateTime;
private OffsetDateTime endDateTime;
private boolean allDay;
private String title;
private String description;
private String location;
private Calendar calendar;
CalendarEventStructure(final OffsetDateTime startDateTime, final OffsetDateTime endDateTime) {
this.startDateTime = startDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.endDateTime = endDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.allDay = this.startDateTime.isEqual(
this.startDateTime.toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC)) &&
this.endDateTime.isEqual(this.endDateTime.toLocalDate()
.atStartOfDay()
.atOffset(ZoneOffset.UTC));
}
@Override
public CalendarEventStructure withTitle(String aTitle) {
this.title = aTitle;
return this;
}
...
}
</pre>
<p>Voici un exemple d’utilisation d’un tel code :</p>
<pre class="brush: java">
CalendarEvent event = CalendarEvent.on(Period.on(today))
.withTitle(aTitle)
.withDescription(aDescription)
.inLocation(aLocation);
</pre>
<p>Afin de rendre immuable nos objets <code>CalendarEvent</code>, une réalisation simple serait pour chaque mutateur de cloner l’objet courant, de valoriser l’attribut du clone et de retourner ce dernier. Or, si ceci est suffisant pour une simple modification d’une propriété d’un événement calendaire, ça s’avère plutôt lourd pour initialiser l’événement juste instancié. En effet, à chaque initialisation d’une des propriétés de l’événement, on clonerait celui-ci ! Une pratique courante, et typique des langages comme Java, serait d’initialiser l’objet à son instanciation (avec le constructeur). Or, pour un objet comportant un nombre important de propriétés, ceci peut vite devenir là aussi lourd et surtout désagréable à lire. Non, j’ai souvent préféré l’approche de Smalltalk dans lequel l’initialisation de l’objet est distincte de sa construction. C’est ici qu’une classe de construction (<em>builder</em> en anglais) prend tout son intérêt : elle créée un objet <code>CalendarEvent</code> et l’initialise à la demande avant de retourner l’objet prêt ; ceci ne peut évidemment pas se faire si les attributs de la classe <code>CalendarEventStructure</code> sont tous marqués <em>final</em>.</p>
<pre class="brush: java">
public interface CalendarEventBuilder {
static CalendarEventStructure.MyCalendarEventBuilder get() {
return new CalendarEventStructure.MyCalendarEventBuilder();
}
CalendarEventBuilder withTitle(String aTitle);
CalendarEventBuilder withDescription(String aDescription);
CalendarEventBuilder inLocation(String aLocation);
CalendarEvent build();
}
</pre>
<p>L’interface qui définit le constructeur propose une méthode statique pour obtenir une instance de la classe qui implémente l’interface. Cette classe concrète est interne à la classe <code>CalendarEventStructure</code> :</p>
<pre class="brush: java">
public class CalendarEventStructure implements CalendarEventBehaviour, Cloneable {
...
static class MyCalendarEventBuilder implements CalendarEventBuilder {
private CalendarEventStructure event;
MyCalendarEventBuilder() {
}
CalendarEventBuilder between(final OffsetDateTime start, final OffsetDateTime end) {
this.event = new CalendarEventStructure(start, end);
return this;
}
@Override
public CalendarEventBuilder withTitle(String aTitle) {
this.event.title = aTitle;
return this;
}
@Override
public CalendarEventBuilder withDescription(String aDescription) {
this.event.description = aDescription;
return this;
}
@Override
public CalendarEventBuilder inLocation(String aLocation) {
this.event.location = aLocation;
return this;
}
...
@Override
public CalendarEvent build() {
return this.event;
}
}
}
</pre>
<p>Ce constructeur est utilisé en lieu et place de la fabrique dans <code>CalendarEvent</code> :</p>
<pre class="brush: java">
interface CalendarEvent {
static CalendarEventBuilder on(final Period period) {
return CalendarEventBuilder.get().between(period.getStartDateTime(), period.getEndDateTime());
}
...
}
</pre>
<p>Ce qui donnerait à l’utilisation :</p>
<pre class="brush: java">
CalendarEvent event = CalendarEvent.on(Period.on(today))
.withTitle(aTitle)
.withDescription(aDescription)
.inLocation(aLocation)
.build();
</pre>
<p>Ce qui me gêne ici est la méthode <code>build()</code> pour la raison que c’est une méthode technique utilisée avec des méthodes à connotation métier. Je n’ai jamais vraiment été fan de ce genre de constructeur à cause de cette méthode finale. Une manière de remédier à ceci est de la remplacer par une méthode métier des événements. La méthode qui me parait la plus pertinente ici serait celle de planification. Pour ce faire, il est nécessaire de séparer la planification des événements sur un agenda de leur persistance même. Ce qui donnerait :</p>
<pre class="brush: java">
public interface CalendarEventBuilder {
...
CalendarEvent planOn(final Calendar aCalendar);
}
</pre>
<p>et :</p>
<pre class="brush: java">
public class CalendarEventStructure implements CalendarEventBehaviour, Cloneable {
...
static class MyCalendarEventBuilder implements CalendarEventBuilder {
private CalendarEventStructure event;
...
@Override
public CalendarEvent planOn(final Calendar aCalendar) {
this.event.calendar = aCalendar
return this.event;
}
}
}
</pre>
<p>La méthode de planification qui était dans <code>CalendarEvent</code> est remplacée par une méthode de sauvegarde de l’événement.</p>
<pre class="brush: java">
interface CalendarEvent {
...
CalendarEvent save();
...
}
</pre>
<pre class="brush: java">
interface CalendarEventBehaviour extends CalendarEvent {
...
@Override
default CalendarEvent save() {
return Transaction.perform(() ->
CalendarEventRepository.get().put(this)
);
}
...
}
</pre>
<p>Ce qui donne finalement :</p>
<pre class="brush: java">
CalendarEvent event = CalendarEvent.on(Period.on(today))
.withTitle(aTitle)
.withDescription(aDescription)
.inLocation(aLocation)
.planOn(myCalendar);
</pre>
<p>ou :</p>
<pre class="brush: java">
CalendarEvent event = CalendarEvent.on(Period.on(today))
.withTitle(aTitle)
.withDescription(aDescription)
.inLocation(aLocation)
.planOn(myCalendar)
.save();
</pre>
<p>C’est tout de même plus agréable à lire.</p>
<p>Si on prend du recul vis à vis de notre classe de construction, on pourrait dire que celle-ci ressemble plus à un brouillon d’un événement que l’on n’a pas encore planifié. Brouillon que l’on pourrait reprendre plus tard. Dans d’autres contextes métiers, ces brouillons pourraient même servir de modèle pour créer de nouveaux objets métiers avec des modifications légères, contextuelles. Notre <code>CalendarEventBuilder</code> pourrait être renommé en <code>CalendarEventDraft</code> ; nous sommes passés d’une classe à consonance technique à une classe métier.</p>Une histoire d'objets obèses ...urn:md5:5240674d3df14ebdcb586f95181b5b572017-03-06T22:06:00+01:002018-04-27T20:31:53+02:00Miguel MoquillonTechniques de programmationJavaprogrammation orientée objet<p>Il arrive dans un projet en Java de se trouver, selon le métier ou le domaine adressé, avec des classes d’objets obèses en méthodes qu’elles soient publics ou propres aux objets de la classe. Or, sachant que l’on passe plus de temps à lire, voir à toucher du code existant qu’à en écrire de nouveaux, ceci peut vite devenir pénible. Evidemment, avec nos IDE actuels, il est facile de naviguer entre les différentes méthodes et propriétés d’une classe. Mais en général ceci signifie que l’on sait, déjà, à peu près ce que l’on cherche ou que l’on connait a minima les responsabilités ou certaines particularités d’implémentation de la classe. Lorsqu’on doit toucher du code inconnu ou au mieux revenir sur du code au bout de 6 mois, nous aimons bien identifier aisément les parties à utiliser ou à retoucher et accéder à l’essentiel sans se perdre dans les méandres de la ou des classes inspectées. En effet, il peut être difficile, avec de telles classes, de démêler le comportement de l’objet, ce qui le caractérise, du reste. En tout cas c’est mon cas. Pour éviter de tels embonpoints, je vous propose d’utiliser les approches de certains langages fonctionnels comme Haskell (ou OCaml), dans lesquels les types et les fonctions sur ces types sont séparés (au sein d’un même module tout de même).</p> <p>Le problème de l’obésité de classes métier en Java (mais aussi dans d’autres langages impératifs) est fréquent. Par exemple, sur un moteur de gestion des événements sur un agenda, nous sommes arrivés, avec un collègue, à une classe <code>CalendarEvent</code> de plus de 1000 lignes de code. Une solution serait de découper les responsabilités de ces objets en sous-responsabilités dans d’autres classes d’objets qui composeraient in fine la classe d’objet finale. Une autre solution serait de découper la classe, par exemple, dans notre cas, <code>CalendarEvent</code>, en deux parties, une classe qui rassemblerait les propriétés et les accesseurs de notre modèle et une interface qui regrouperait le comportement métier (avec leur implémentation par défaut, merci Java 8).</p>
<pre class="brush: java">
public class CalendarEvent implements CalendarEventBehaviour, Cloneable {
private OffsetDateTime startDateTime;
private OffsetDateTime endDateTime;
private boolean allDay;
private String title;
private String description;
private String location;
private Calendar calendar;
public static CalendarEvent on(final Period period) {
return new CalendarEvent(period.getStartDateTime(), period.getEndDateTime());
}
private CalendarEvent(final OffsetDateTime startDateTime, final OffsetDateTime endDateTime) {
this.startDateTime = startDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.endDateTime = endDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.allDay = this.startDateTime.isEqual(
this.startDateTime.toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC)) &&
this.endDateTime.isEqual(this.endDateTime.toLocalDate()
.atStartOfDay()
.atOffset(ZoneOffset.UTC));
}
...
}
</pre>
<pre class="brush: java">
interface CalendarEventBehaviour {
CalendarEvent self();
default CalendarEvent planOn(final Calendar calendar) {
return Transaction.perform(() ->
CalendarEventRepository.get().put(self().setCalendar(calendar))
);
}
...
}
</pre>
<p>L’interface <code>CalendarEventBehaviour</code>, qui définit l’ensemble du comportement de <code>CalendarEvent</code>, n’est visible qu’au niveau du package Java qui comprend aussi notre classe. L’utilisation ici d’une interface en lieu et place d’une classe permet à <code>CalendarEvent</code> de pouvoir faire partie d’un arbre d’héritage (Java ne supportant que l’héritage simple). Vous remarquerez la définition de la méthode <code>self()</code> qui permet de retrouver l’instance sur laquelle le comportement s’applique ; oui, ça ressemble fortement à une simulation pauvre d’un mixin, technique bien connue des rubyistes.</p>
<p>Or, si cette solution <i>marche</i>, elle n’est pas, à mon avis, satisfaisante. En effet, il est important, dans une conception orientée objet, de définir la représentation d’un modèle métier par l’ensemble de son comportement et que celui-ci soit exposé. Or, ici, le comportement est caché de l’extérieur. On pourrait alors inverser : <code>CalendarEvent</code> rassemblerait le comportement de l’objet et la structure de la classe serait déportée dans une autre classe. Par exemple :</p>
<pre class="brush: java">
public interface CalendarEvent {
CalendarEventStructure self();
static CalendarEvent on(final Period period) {
return new CalendarEventStructure(period.getStartDateTime(), period.getEndDateTime());
}
CalendarEvent withTitle(String aTitle);
CalendarEvent withDescription(String aTitle);
CalendarEvent inLocation(String aLocation);
default CalendarEvent planOn(final Calendar calendar) {
return Transaction.perform(() ->
CalendarEventRepository.get().put(self().setCalendar(calendar))
);
}
...
}
</pre>
<pre class="brush: java">
class CalendarEventStructure implements CalendarEvent, Cloneable {
private OffsetDateTime startDateTime;
private OffsetDateTime endDateTime;
private boolean allDay;
private String title;
private String description;
private String location;
private Calendar calendar;
CalendarEventStructure(final OffsetDateTime startDateTime, final OffsetDateTime endDateTime) {
this.startDateTime = startDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.endDateTime = endDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.allDay = this.startDateTime.isEqual(
this.startDateTime.toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC)) &&
this.endDateTime.isEqual(this.endDateTime.toLocalDate()
.atStartOfDay()
.atOffset(ZoneOffset.UTC));
}
public CalendarEvent withTitle(String aTitle) {
this.title = aTitle;
return this;
}
...
}
</pre>
<p>Déjà, un détail me gêne dans cette approche. En l’occurrence, la présence de la méthode <em>technique</em> <code>self()</code> dans <code>CalendarEvent</code> alors qu’elle ne devrait pas. A moins que l’on préfère exposer les méthodes utilisées dans les implémentations par défaut afin de pouvoir les utiliser en lieu et place de cette méthode <code>self()</code>. Ce qui, finalement, revient au même, voir en pire.</p>
<p>Pour corriger ce problème de l’exposition d’opérations techniques dans l’interface, on pourrait très bien imaginer de découper notre classe en trois parties dans un même package Java : une première qui définirait notre objet métier en exposant son comportement, une autre qui implémenterait ce comportement, juste le comportement, avec les fonctions techniques nécessaires à cette implémentation, mais pas les accesseurs, et enfin une troisième qui lui donnerait une structure dotée de ses accesseurs.</p>
<p>D’abord l’interface qui définit l’objet métier :</p>
<pre class="brush: java">
public interface CalendarEvent {
static CalendarEvent on(final Period period) {
return new CalendarEventStructure(period.getStartDateTime(), period.getEndDateTime());
}
CalendarEvent planOn(final Calendar calendar);
CalendarEvent withTitle(String aTitle);
CalendarEvent withDescription(String aDescription);
...
}
</pre>
<p>Ensuite l’implémentation juste du comportement. Celui-ci peut-être réalisé sous la forme d’une interface avec des méthodes par défaut quand c’est possible ou alors, ce qui est fréquent, sous forme d’une classe abstraite :</p>
<pre class="brush: java">
interface CalendarEventBehaviour extends CalendarEvent {
CalendarEvent setCalendar(final Calendar calendar);
@Override
default CalendarEvent planOn(final Calendar calendar) {
return Transaction.perform(() ->
CalendarEventRepository.get().put(this.setCalendar(calendar))
);
}
@Override
default CalendarEvent update() {
return Transaction.perform(() ->
CalendarEventRepository.get().put(this)
);
...
}
</pre>
<p>Et enfin la dernière classe qui définit finalement la structure même de l’objet métier :</p>
<pre class="brush: java">
class CalendarEventStructure implements CalendarEventBehaviour, Cloneable {
private OffsetDateTime startDateTime;
private OffsetDateTime endDateTime;
private boolean allDay;
private String title;
private String description;
private String location;
private Calendar calendar;
CalendarEventStructure(final OffsetDateTime startDateTime, final OffsetDateTime endDateTime) {
this.startDateTime = startDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.endDateTime = endDateTime.withOffsetSameInstant(ZoneOffset.UTC);
this.allDay = this.startDateTime.isEqual(
this.startDateTime.toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC)) &&
this.endDateTime.isEqual(this.endDateTime.toLocalDate()
.atStartOfDay()
.atOffset(ZoneOffset.UTC));
}
@Override
public CalendarEventStructure withTitle(String aTitle) {
this.title = aTitle;
return this;
}
...
}
</pre>
<p>Vous aurez remarqué que l’interface métier expose une méthode statique (de classe) pour la construction des objets ou, plus exactement, l’instanciation de la classe concrète. Il y a là un couplage entre l’interface et son implémentation qui fera dire à certains que c’est mal. Pour couper ce cycle entre les deux, il suffit de remplacer l’instanciation directe par l’utilisation d’une fabrique :</p>
<pre class="brush: java">
class CalendarEventFactory {
public CalendarEvent makeEventOn(final Period period) {
return new CalendarEventStructure(period.getStartDateTime(), period.getEndDateTime());
}
}
</pre>
<pre class="brush: java">
public interface CalendarEvent {
static CalendarEvent on(final Period period) {
return new CalendarEventFactory().makeEventOn(period);
}
...
}
</pre>
<p>Avec cette approche, notre classe d’objet obèse initiale est découpée en trois parties distinctes, chacune avec un rôle bien défini, ce qui permet d’avoir des classes de tailles plus correctes et de pouvoir naviguer directement dans celle(s) qui nous intéresse(nt) sans s’y perdre.</p>L'égalité d'identité et l'égalité de valeur en Javaurn:md5:e89a3122b17d5ea646a9ad37928f95ab2016-11-29T13:53:00+01:002018-04-27T20:32:25+02:00Miguel MoquillonTechniques de programmationJavaprogrammation orientée objet<p>Dans certains langages de programmation, comme Java, il n’y a pas de méthodes ou d’opérateurs distincts entre l’égalité d’identité et celle de valeur. Si, dans un programme classique écrit dans un langage comme Java, l’égalité d’identité (de l’OID pour Object IDentifier) pourrait se faire avec l’opérateur <code>==</code> et celle de valeur avec la méthode <code>equals</code> surchargée, il n’en va plus de même dès qu’il s’agit d’objets persistés. Et là, in fine, c’est le drame : que compare t’on avec la méthode <code>equals</code> ? la valeur des objets ou leur identité ?</p> <p>Cette question est importante car elle met en exergue une différence de sémantique qui, mal appréciée ou pas du tout appréhendée, pourrait aboutir à utiliser tantôt l’une, tantôt l’autre, aboutissant inévitablement à des bogues cachés, voir même, dans certaines situations d’usage, à un plantage.</p>
<p>Cette question est d’autant plus pertinente en Java où la méthode <code>equals</code>, par ses propres règles de codage, peut véhiculer les deux sémantiques. Je m’explique. Par défaut, la méthode <code>equals</code> d’un objet en Java effectue une égalité d’identité (qui se trouve être l’adresse mémoire de l’objet, comme dans les autres langages orientés objet). Nous pourrions par conséquence en déduire que la sémantique véhiculée par cette méthode est celle d’une égalité d’identité et donc nous y maintenir. Or, une des règles de codage en Java stipule que lorsqu’un objet peut être comparé, la méthode <code>equals</code> doit être consistante avec la méthode <code>compareTo</code> ; c’est-à-dire que :</p>
<pre class="brush: java">
myObject.compareTo(anotherObject) == 0 <=> myObject.equals(anotherObject) == true
</pre>
<p>Or la méthode <code>compareTo</code> effectue une comparaison de valeur ce qui fait que, par extension, la méthode <code>equals</code> devient alors une égalité de valeur !</p>
<p>A première vue, nous pourrions croire alors qu’il n’y a pas de sémantique bien définie avec la méthode <code>equals</code>. En fait, il en est rien. Sa sémantique est bien celle d’une égalité d’identité et le problème ici relève plus de l’utilisation de la comparaison entre objets. En effet, il est fréquent de rencontrer dans du code une implémentation de la méthode <code>compareTo</code> dès qu’une comparaison entre différents objets d’une même classe est à faire. Par exemple, dans le cas d’un tri. Or, ceci peut n’avoir aucun sens vis à vis du concept véhiculé par ces objets. Prenons un exemple. Imaginons que l’on souhaite trier des événements calendaires par leur date de début. Une des solutions pourrait être d’implémenter l’interface <code>Comparable</code> et sa méthode <code>compareTo</code> pour comparer les dates de début. Or ceci est une erreur conceptuelle ; on désire comparer ici des dates, pas les événements calendaires eux même. En effet, la méthode <code>compareTo</code> est une opération de comparaison de valeurs entre deux objets, autrement dit une opération de comparaison d’états entre deux objets. Or, la date de début d’un événement n’est qu’une caractéristique parmi d’autres de son état. Qu’en est-il de sa date de fin ? De sa récurrence ? De son intitulé ? etc. Comparer deux événements n’a de sens que pour indiquer s’ils sont in fine soit différents, soit égaux en valeur, et ça ce n’est pas la sémantique de la méthode <code>compareTo</code>. Pour comparer les événements par leur date de début, on utilisera plutôt un objet de type <code>Comparator</code> qui transportera lui la sémantique de comparaison que l’on voudra sur les événements et qui portera en fait sur une ou plusieurs de leurs propriétés.</p>
<p>Mais alors, qu’en est-il de l’intérêt de l’interface <code>Comparable</code> et de sa méthode <code>compareTo</code> ? Vous connaissez la classe <code>String</code> ? Cette classe qui représente une chaîne de caractères en Java est immutable et constante. Ceci signifie qu’une même chaîne de caractères est représentée par une même instance et ne peut être modifiée ; nous avons ici une égalité à la fois d’identité et de valeur. Lorsque vous comparez deux variables distinctes avec une même chaîne de caractères, en fait vous comparez derrière le même objet (les variables sont des références qui pointent ici sur la même instance). Si vous modifiez une chaîne de caractères, en fait, vous n’obtenez qu’une autre chaîne de caractères, donc une autre instance avec une autre valeur ; celle sur laquelle a été demandée la modification n’a pas changé. Et par conséquent, la méthode <code>equals</code> de String, donnant une égalité à la fois d’identité et de valeur, est consistante avec la méthode <code>compareTo</code>. C’est dans ce contexte que cette méthode prend tout son sens : lorsque l’identité et la valeur d’un objet sont intrinsèquement liés, autrement dit lorsque vos objets sont immutables.</p>
<p>En conséquence, la méthode <code>equals</code> en Java est une opération d’égalité d’identité et devrait le rester dans vos programmes. Si une égalité par valeur doit se faire entre deux de vos objets avec la méthode <code>equals</code>, alors soit vos objets doivent être immutables et vous pouvez surcharger la méthode <code>equals</code> en ce sens, soit vous devez définir une nouvelle méthode, comme par exemple <code>sameAs</code>.</p>
<p>Maintenant, les choses sont un tant soit peu un peu plus délicats avec les objets persistés. En effet, ce qui est stocké dans une source de données, quelle qu’elle soit (une source NoSQL ou une base de données), c’est l’état des objets, leur valeur, et en aucune manière les objets mêmes. (Ou alors vous disposez d’une source de données orientée objet et, mise à part dans le monde Smalltalk, je n’en connais aucune qui soit utilisée dans les autres écosystèmes de programmation.) Aussi, si par deux fois un même événement calendaire est demandé, le programme obtiendra, sans utilisation de caches, deux instances différentes de ce même événement et construites à partir d’une même valeur récupérée de la source de données. La méthode <code>equals</code> retournera donc, par défaut, <code>false</code> lorsqu’un bout de code voudra savoir s’il s’agit d’un même événement. Dans ce contexte, la sémantique d’égalité d’identité est brisée.</p>
<p>À la différence des objets à vie courte, les objets persistants ont une durée de vie qui va au delà du temps d’exécution du programme, voir même ont une vie au delà d’un processus donné. L’identité par adresse mémoire ne suffit donc plus. C’est là qu’intervient l’identité de persistance ou de stockage. En effet, cette identité assure l’unicité de l’objet dans la source de données et peut donc être utilisée aussi dans le programme pour marquer l’identité des objets en lieu et place de leur adresse mémoire. Pour aller plus loin et assurer aussi son unicité au delà des sources de données, l’idéal serait que cette identité soit une valeur unique et universelle (UUID). Ainsi, dès que vous devez manipuler des objets persistés, vous pouvez surcharger la méthode <code>equals</code> pour qu’elle compare l’égalité des identifiants de persistance des objets si ces derniers sont persistés, sinon leur valeur d’adresse si l’un d’eux ne l’est pas :</p>
<pre class="brush: java">
public boolean equals(Object other) {
if (! other instanceof CalendarEvent) {
return false;
}
CalendarEvent otherEvent = (CalendarEvent) other;
if (this.getId() != null && otherEvent.getId() != null) {
return this.getId().equals(otherEvent.getId());
}
return this == otherEvent;
}
</pre>
<p>(Nous pouvons aussi s’assurer ici que la méthode <code>getId</code> renvoie toujours une valeur sous forme de <i>Value Object</i>, soit le hash code dans le cas où l’objet n’est pas encore persisté, sinon l’identifiant de persistance.)</p>L'approche Micro-Services et le paradigme Orienté-Objeturn:md5:bb23b87fcda53c739379c81ad94404e82015-11-21T17:23:00+01:002017-03-12T21:57:56+01:00Miguel MoquillonTechniques de programmationArchitectureArchitecture orientée servicesprogrammation orientée objetSmalltalkWeb<p>J’aime à dire que l’évolution en informatique ne connait que très peu de ruptures et ressemble à une grande spirale dans laquelle les nouvelles technologies et approches ne sont que des reprises de plus anciennes que l’on a dépoussiéré et adapté aux attentes et au temps actuels ; c’est ce que l’on appelle l’innovation. <a href="http://microservices.io/" hreflang="en">Les micro-services</a>, le grand buzz de l’année 2015, n’échappent pas à cette régle. (On est friand aussi de buzz en informatique). Mais qu’est-ce que les micro-services ? En fait, rien de spécial. Ce n’est ni plus ni moins que le découpage des responsabilités des applications dans des services Web dédiés et qui communiquent entre eux par messages. Du <a href="https://fr.wikipedia.org/wiki/Architecture_orient%C3%A9e_services" hreflang="fr">SOA</a> ? Du <a href="https://fr.wikipedia.org/wiki/Representational_State_Transfer" hreflang="fr">REST</a> ? … Oui, tout ça mais ce n’est plus à la mode, il faut parler maintenant de micro-services.</p> <p>En fait, SOA, REST et les autres approches orientées services ne donnent aucun indication de comment doivent être architecturé les services entre eux. Ce sont les bonnes pratiques, l’expérience qui a guidé ces choix. Jusqu’à présent, dans les architectures orientées services, les applications, ou services, ne répondent qu’un seul métier mais peuvent couvrir plusieurs responsabilités et ils sont étagés les uns par rapport aux autres : les services les plus hauts étant ceux accédés par les utilisateurs et ceux les plus bas sont ceux qui manipulent les données métiers <em>brutes</em> et qui donc accèdent directement aux sources de données. L’approche architecturale micro-service impose un schéma dans lequel chaque service ne doit couvrir qu’une seule responsabilité et peut par conséquence, pour ce faire, accéder directement à une source de données et communiquer avec un ou plusieurs autres services. Attention donc au syndrome du plat de spaghetti ; les éditeurs <a href="https://fr.wikipedia.org/wiki/Enterprise_service_bus" hreflang="fr"">d’ESB</a> peuvent se frotter les mains. La grande difficulté avec ce style d’architecture, nous le voyons bien, est de savoir correctement urbaniser nos services mais aussi et surtout d’adapter cette urbanisation aux évolutions ; l’urbanisation devient prédondérante et peut s’avérer complexe. Pour ce coût, qui peut s’avérer assez cher, les avantages indéniables sont :</p>
<ul>
<li>une bien meilleur montée en charge verticale,</li>
<li>une montée en charge horizontale qui peut être adaptée à chaque service,</li>
<li>une meilleur isolation aux fautes,</li>
<li>une meilleur productivité, les services étant relativement petits : ils sont plus faciles, pour les développeur, à appréhender et à travailler dessus,</li>
<li>une meilleur maintenance, chaque service ayant chacun leur propre cycle de vie,</li>
<li>un déployement court,</li>
<li>des services bien plus réutilisables entre différentes applications qu’auparavant. </li>
</ul>
<p>Il semblerait donc que l’on a affaire à une nouvelle approche qui propose une meilleur façon d’architecturer les services. En fait elle n’est pas si nouvelle que ça et, comme le titre du billet le suggère, ça a voir avec le paradigme orienté objet.</p>
<p>La programmation orientée objet a été définie la première fois par Alan Kay à la fin des années 1960. Alan Kay, Dan Ingalls et les autres membres de l’équipe étaient à l’époque profondément impressionnés par la conception d’<a href="https://fr.wikipedia.org/wiki/ARPANET" hreflang="fr">ARPANET</a>, l’ancêtre d’Internet. Ce réseau mettait en relation des ordinateurs entre eux et permettait aux utilisateurs localisés dans des lieux géographiques différents de communiquer par le biais de protocoles donnés (mails, FTP, talk qui est l’ancêtre de la messagerie instantanée, etc.). Guidés par la célèbre formule de Bob Barton,</p>
<blockquote>
<p>The basic principal of recursive design is to make the parts have the same power as the whole.</p>
</blockquote>
<p>Alan Kay et les autres s’inspirèrent des principes derrières ARPANET dans leur conception d’un nouveau langage de programmation, Smalltalk. C’est de ce langage qu’est né la programmation orientée objet (n’en déplaise les contradicteurs qui voient Simula comme le premier langage orienté objet, ce qu’il n’est pas). Pour schématiser, dans ce paradigme de programmation, les objets, des entités logicielles de première classe, peuvent être vue comme des petits ordinateurs qui communiquent entre eux par envoie de messages (l’équivalent des protocoles dans ARPANET et donc d’Internet), et chaque objet ne convoi qu’une et une seule responsabilité.</p>
<p>A côté de ça, Smalltalk a été conçu pour être plus qu’un simple langage de programmation, mais un véritable environnement complet de développement et d’exécution. Il était déstiné à faire tourner le <a href="https://en.wikipedia.org/wiki/Dynabook" hreflang="en">Dynabook</a>, l’ancêtre du PC portable moderne ; ce ne devait pas être une application sur un OS mais l’OS même de la machine. En fait, dans sa conception, il n’y a pas de notions d’applications dans Smalltalk ; tout devait être objet et Smalltalk en était lui-même un (représentant la machine qu’il fait fonctionner). Si on devait donc faire le rapprochement entre le style d’architecture micro-services et le paradigme orienté objet, les services sont des objets réparties dans différentes VM (donc machines) et qui communiquent entre eux par messages (en utilisant le mécanisme et donc le protocole des environnements orientés objets).</p>
<p>Il a fallu in fine attendre 2015 pour que l’approche véhiculée par la programmation orientée objet (celle d’Alan Kay, pas celle dénaturée de Ole-Johan Dahl et de Kristen Nygaard) finissent par s’appliquer dans la réalisation d’architectures d’applications distribuées. La différence est que l’approche micro-services est agnostique d’une quelconque technologie : les services peuvent être réalisés dans des langages et selon des paradigmes différents et qu’ils peuvent tourner en natif, dans des VM ou encore dans des conteneurs (Docker, …). La seule contrainte est le choix du mécanisme et du protocole d’échange de messages entre services qui doivent être commun et donc imposés pour tous ; en général, actuellement, c’est le protocole HTTP qui est choisi selon un mécanisme d’échange basé sur REST. </p>
<p> </p>Une histoire de slotsurn:md5:37df2868ec64fd2cb96663fca117d7a82014-03-04T11:41:00+01:002014-03-22T23:24:57+01:00Miguel MoquillonTechniques de programmationMéta-programmationprogrammation impérativeprogrammation orientée objetslot<p><img src="https://www.moquillon.fr/public/slot.png" alt="card slot" style="float:left; margin: 0 1em 1em 0;" title="card slot, mar. 2014" />L’amélioration de la structuration du code d’un programme, avec la distinction des considérations techniques de celles métier, est le Saint Graal que poursuivent sans fins les développeurs. A cette fin, de nombreuses techniques ont fait leur apparition dont nous pouvons citer les <em>traits</em> ou les <em>annotations</em>. A côté de ceux-ci, il existe une technique élégante et uniforme que sont les <em>slots</em><a href="https://www.ruby-lang.org/fr/" hreflang="fr" title="Site Web du langage Ruby"></a>. Mais, que sont ces derniers et en quoi peuvent ils nous aider dans notre quête ?</p> <p>Un adage veut qu’un bon développeur soit un développeur paresseux. Ce qui se cache derrière cette paresse est l’aptitude du codeur à automatiser ses tâches redondantes et souvent ennuyeuses et de tout faire pour faciliter son boulot. L’avantage de cette attitude est évidemment pour l’organisation un gain de productivité à terme. En fait, à y regarder de près, le développeur ne fait que s’aligner aux lois de la physique : minimiser l’entropie.
Un des aspects de cette caractéristique est, pour le programmeur, de minimiser la duplication de codes et, pour ce faire, il va user de techniques et de moyens qu’il connaît mais aussi qu’il a à sa disposition via l’outillage et surtout son langage de programmation.
La structuration du code est l’une de ces techniques. Elle est vieille de plus de 30 ans mais n’a cessé et ne cesse de continuer à évoluer. L<em>‘AOP</em> (Aspect-Oriented Programming), les <em>traits</em>, les <em>mixins</em>, les <em>closures</em>, etc. ne sont que des aspects de celle-ci dans leur objectif de modulariser non seulement le code métier mais aussi et surtout le code transversal au métier et aux fonctionnalités du logiciel. Avec l<em>‘AOP</em>, nous pouvons définir des composants d’ordre technique comme la génération de traces ou la validation des autorisations d’appel d’opérations. Ces composants sont ensuite tissés aux objets métiers à l’exécution ou à la compilation ; l<em>‘AOP</em> permet de maintenir d’un côté les caractéristiques techniques et de l’autre les modules métiers sans les entacher de considération techniques. Les <em>mixins</em> et les <em>traits</em> sont deux concepts différents permettant de définir des propriétés, d’ordre technique ou fonctionnelle, transversales aux objets métiers puis de les associer à ces derniers de manière statique ou dynamique. Par exemple, l’ordonnancement ou encore la persistance peut être définie dans un <em>mixin</em> ou un <em>trait</em>, indépendamment des objets métiers des applications. Ces deux techniques ont été popularisés respectivement par les langages <a href="https://www.ruby-lang.org/fr/" hreflang="fr" title="Le site Web du langage Ruby">Ruby</a> et <a href="http://selflanguage.org/" hreflang="en" title="Le site Web du langage Self">Self</a>. Les annotations, popularisés par <a href="http://www.java.com/fr/" hreflang="fr" title="Site Web du langage Java">Java</a>, constituent une autre technique qui permet de qualifier sémantiquement des objets et d’attacher à celles-ci un comportement qui peut aller jusqu’à la génération de codes ; par exemple, des objets peuvent être annotés avec des marqueurs de gestion des transactions ou encore d’annotations métiers propres au domaine de l’application.</p>
<p>Pourtant, il existe un autre concept popularisé par Self en même temps que les <em>traits</em> : les <em>slots</em>. Ces derniers ne sont pas nouveaux puisqu’ils furent introduit par CLOS (Common Lisp Object System). (Dans CLOS comme dans Self, ils sont appelés <em>descripteurs de slot</em>). On retrouve ces derniers dans le langage <a href="http://iolanguage.org/" hreflang="en" title="Site Web du langage IO">Io</a> et actuellement un travail est réalisé pour les intégrer à terme dans <a href="http://www.pharo-project.org/home" hreflang="en" title="Le site Web du langage Pharo">Pharo</a>. Mais qu’est-ce qu’un <em>slot</em> ou <em>descripteur de slot</em> ? Dans le contexte d’un langage orienté-objet, un <em>slot</em> est la réification du point d’accès aux propriétés d’un objet ou, plus exactement, du mécanisme d’envoi/réception de messages (cf. mon <a href="https://www.moquillon.fr/index.php/post/2012/12/21/Les-Programmations-Orient%C3%A9es-Objet">billet</a> sur les différentes POO). Tout accès aux propriétés d’un objet se fait par l’intermédiaire des slots qui, non seulement peuvent contrôler l’accès en lecture et en écriture de ces propriétés, mais aussi contenir des informations sémantiques sur celles-ci. Ils constituent une interface homogène d’accès aux propriétés de l’objet. Dans Self et <a href="http://newspeaklanguage.org/" hreflang="en" title="Le site Web du langage Newspeak">Newspeak</a>, le slot est un objet particulier qui agit comme une méthode retournant toujours une valeur (que celle-ci soit calculée dans le cas d’une méthode ou mémorisée dans le cas d’un attribut). Selon les langages de programmation, les slots peuvent n’être définis que pour l’accès aux champs d’un objet. Parce que c’est aussi un objet, le slot peut être manipulé comme n’importe quel autre objet du langage et, par voie de conséquence, il est possible alors de lui affecter aussi un comportement. Pour illustrer ceci, voici un petit exemple dans un pseudo-langage dans lequel le caractère point représente un slot (l’accès à un slot se fait par le point) :</p>
<pre>
Point.x := 3
Point.move := (abs, ord) { myself x += abs; myself y += ord; myself }
Point.move.afterInvocation := () { View current updateView(myself) }
</pre>
<p>A la première ligne, le slot réfère un champs <code>x</code> tandis qu’à la seconde ligne le slot réfère une opération <code>move</code>. Le premier retournera la valeur 3 tandis que le second, une fois son action accomplie, retournera par défaut le point lui même. La dernière ligne spécifie quoi faire à la suite de l’invocation de l’opération : mettre à jour l’IHM dans laquelle est dessiné le point. L’attribut <code>myself</code> des slots désigne l’instance de l’objet qui contient le slot. Dans la représentation ci-dessus, par homogénéité, les propriétés d’un slot (par exemple <code>afterInvocation</code>) sont elles aussi des slots (dans ce cas, il existe un slot terminal qui référence ou stocke directement la valeur de l’attribut ou les instructions de la méthode). Nous aurions pu écrire le code comme suit pour plus de lisibilité :</p>
<pre>
Point setSlot($x, 3)
Point setSlot($move, (abs, ord) { myself x += abs; myself y += ord; myself } )
Point.move setSlot($afterInvocation, () { View current updateView(myself) } )
</pre>
<p>dans lequel le signe $ désigne un symbole.</p>
<p>Parce qu’un slot peut représenter un point d’accès vers un autre objet, les relations inter-objets deviennent mécaniquement des citoyens de première classe au même titre que l’objet (ou la classe dans un langage orienté-classes). Pour illustrer ceci, imaginons un employeur qui a plusieurs employés et des employés qui est rattaché à un et un seul employeur :</p>
<pre>
Employer.employees := []
Employer.employees.onWrite := (e) { (e employer = myself) ifFalse(
{ e employer(myself) })
}
Employee.employer := None(Employer)
Employee.employer.onWrite : = (e) { (e employees contains(myself)) ifFalse(
{ e employees add(myself) })
}
</pre>
<p>Dans le code ci-dessus, chaque nouveau employé de l’employeur mettra automatiquement la référence <code>employer</code> de l’employé avec son nouveau employeur. De même, chaque mise-à-jour de l’employeur de l’employé ajoutera ce dernier parmi les employés de son nouveau employeur :</p>
<pre>
anEmployee := Employee()
anEmployer := Employer()
anEmployer employees add(anEmployee)
Assertion assertThat(anEmployee employer, is(anEmployer))
</pre>
<p>Les slots <code>employees</code> et <code>employer</code> représentent donc des relations qui peuvent être manipulés comme de simples objets et qui permettent de maintenir la cohérence des relations entre l’employeur et les employés. Nous pouvons alors introduire un mécanisme de création et de relations hiérarchiques de slots comme avec les objets (ou les classes d’objets) :</p>
<pre>
ManyToOneSlot := Slot {
oppositeSlot: Slot,
oppositeClass: Class,
initialize: (anInstance, theOppositeSlot, theOppositeClass) {
myself := anInstance
oppositeClass := System class(theOppositeClass)
oppositeSlot := oppositeClass slot(theOppositeSlot)
},
onWrite: (anInstance) {
((oppositeSlot read: anInstance) = myself ) ifFalse(
{ oppositeSlot write(myself, anInstance) } )
}
}
OneToManySlot := Slot {
oppositeSlot: Slot,
oppositeClass: Class,
initialize: (anInstance, theOppositeSlot, theOppositeClass) {
myself := anInstance
oppositeClass := System class(theOppositeClass)
oppositeSlot := oppositeClass slot(theOppositeSlot)
},
onWrite: (anInstance) {
((oppositeSlot read(anInstance)) contains(myself) ) ifFalse(
{ oppositeSlot write(myself, anInstance) } )
}
}
Employer := Object {
employees: ManyToOneSlot(self, $employer, $Employee)
}
Employee := Object {
employer: OneToManySlot(self, $employees, $Employer)
}
</pre>
<p>De la même façon, il est alors possible de définir des slots dédiés à la persistance ou encore de combiner les slots entre eux pour réaliser des traitements transverses. Ceci et le fait d’ajouter du comportement aux slots ouvrent la porte à la méta-programmation, à la réflexion, sans commune mesure avec les autres concepts. Ce que permettent l’AOP, les annotations, certains usages des mixins et des traits, peuvent toutes être réalisés par les slots et ceci d’une manière uniforme et cohérente avec les concepts de base du langage.</p>
<p>Ce petit billet vous a donné un petite présentation du concept de <em>slot</em>, illustrée avec un pseudo-langage. J’espère qu’il vous aura donné un aperçu de la puissance et de l’expressivité qui se cache derrière ce simple concept.</p>Instant Apache Wicket 6urn:md5:0d4c59cba2256b6f784ce8238e6c541c2013-10-23T14:53:00+02:002014-03-04T11:47:03+01:00Miguel MoquillonRevueJavaprogrammation orientée objetWebWicket<p>Voici un petit compte-rendu du livre <em><a href="http://www.packtpub.com/instant-apache-wicket-6/book?utm_source=Create.com&utm_medium=link&utm_campaign=Apache+" hreflang="en" title="Apache Wicket 6 Starter">Instant Apache Wicket 6</a></em> édité par Packpub. Ce compte-rendu a aussi été publié sur le site de l’<a href="http://www.alpesjug.fr" hreflang="fr" title="Alpes JUG">Alpes JUG</a> dont je fais partie. <a href="http://www.packtpub.com/instant-apache-wicket-6/book?utm_source=Create.com&utm_medium=link&utm_campaign=Apache+" hreflang="en" title="Apache Wicket 6 Starter at Packpub"><img src="https://www.moquillon.fr/public/Apache_Wicket_6_Starter_Instant.jpg" alt="Apache Wicket 6 Starter" style="float:left; margin: 0 1em 1em 0;" title="Apache Wicket 6 Starter, oct. 2013" /></a></p> <p>Le livre permet d’être rapidement opérationnel sur le framework Wicket au travers d’une application web simple ; une sorte de “Hello World!” enrichie avec un processus d’authentification. En suivant l’écriture du code de l’application exemple, dans un unique livre, le lecteur a un aperçu à la fois sur les principaux concepts de Wicket et sur certaines de ses techniques avancées.</p>
<p>Malheureusement, la qualité de l’ensemble n’est pas au rendez-vous. Les auteurs ont confondu vitesse et précipitation. Tout d’abord, dans l’exemple, les modèles des pages web sont basés sur le HTML5. Or, la création de modèles de pages web dans Wicket repose sur un ensemble d’extensions XML, ce qui ne colle pas toujours avec la syntaxe HTML5. Il aurait été plus judicieux de choisir sa contrepartie XHTML5. Deuxièmement, les extraits de code dans la section qui concerne l’usage de <em>wicket-auth-roles</em> pour gérer le processus d’authentification ne fonctionnent pas car le code n’est pas complet. Il faut regarder les extraits de code à la fin du livre pour en avoir une vision complète. Pour finir, le traitement des requêtes par Wicket est illustré sans explication quant aux rôles et aux responsabilités de chacun des composants de Wicket qui interviennent dans le processus.</p>
<p>En conclusion, le livre peut présenter un certain intérêt pour les développeurs qui souhaitent découvrir ou utiliser rapidement le framework Wicket sans avoir à parcourir le Web à la recherche d’exemples présentant chacune des facettes de Wicket. Cependant il aurait été plus intéressant, à mon avis, de monter dans le livre comment profiter de la puissance de Wicket pour écrire une application Web 2.0 moderne avec une forte utilisation de codes Javascript et d’AJAX (ou de Web Sockets), et donc illustrer ainsi en quoi Wicket facilite la vie du développeur par rapport aux autres solutions couramment utilisées.</p>Les Programmations Orientées Objeturn:md5:d302d629db13b22203fdb67f8215ab662012-12-21T22:53:00+01:002013-04-12T14:33:36+02:00Miguel MoquillonTechniques de programmationprogrammation orientée objetSmalltalk<p>La Programmation Orienté Objet (POO pour les intimes) est de nos jours la lingua franca de la programmation dite impérative. Pourtant, fort est de constater que celle couramment usitée est loin de l’approche définie par son auteur, Alan Kay, au point que l’on peut dire qu’il existe actuellement en fait deux approches orientées objet !</p> <p>En quoi les deux approches diffèrent elles ? On pourrait résumer cette différence par ceci :</p>
<ul>
<li>dans l’approche la plus pratiquée, l’accent du développement est mis sur la structuration des objets avec une volonté d’encapsuler leur état. Débutant avec Simula 67, elle est de nos jours commune et véhiculée par des langages comme C++ ou Java.</li>
<li>dans l’approche originale, l’accent est au contraire mis sur le comportement des objets, caractérisé par l’ensemble des messages qu’ils comprennent, la structure de ceux-ci et leur changement d’état étant encapsulé ; la structuration est reléguée au second rang. Initiée avec Smalltalk, elle est véhiculée de nos jours par des langages comme <a href="http://www.pharo-project.org/home" hreflang="en" title="Pharo, an open-source Smalltalk environment">Smalltalk</a>, Io ou Ruby.</li>
</ul>
<p>D’où vient cette différence ? Une explication peut être donnée avec ces propos d’Alan Kay :</p>
<blockquote><p>The “official” computer science world started to regard Simula as a possible vehicle for defining abstract data types (even by one of its inventors), and it formed much of the later backbone of ADA. […] Instead, the objects should be presented as site of higher level behaviors more appropriate for use as dynamic components.</p></blockquote>
<p>A l’origine, les concepteurs de Smalltalk et pères de la POO se sont inspirés des techniques utilisées dans d’autres langages (Lisp, Sketchpad, JOSS, …) pour implémenter les concepts sous-jacents à la POO, et parmi ceux-là les constructions utilisées dans Simula les ont fortement inspirés. A cette époque, on travaillait sur un moyen de structurer le code à l’aide de types abstraits de données. C’est dans ce contexte que certains, comme Ole-Johan Dahl et Kristen Nygaard, les auteurs de Simula, ne virent de la POO que comme une meilleure structuration du code à l’aide de constructions idoines : classes de données, instanciation, sous-typage, attachement dynamique (ces deux derniers permettant le polymorphisme), etc. C’est sous cette tendance qu’ont émergés d’autres langages comme Pascal Object ou C++ qui, à leur tour, ont influencé plusieurs générations de développeurs. C’est ainsi que, pour beaucoup, la POO se résume à écrire des structures (les classes) regroupant à la fois les champs (appelés attributs) et les opérations (appelées méthodes), dont l’interface (les parties accessibles depuis l’extérieur) forme le type ; cette approche est plutôt <em>une programmation orientée classe</em> dans laquelle la classe fusionne le concept de module avec celui de type.</p>
<p>Pour mieux comprendre les concepts sous-jacents à la POO et ce qu’avaient en tête son auteur, je recommande fortement la lecture de <a href="http://gagne.homedns.org/~tgagne/contrib/EarlyHistoryST.html" hreflang="en">l’histoire de Smalltalk</a>. On peut y lire, entre autre, que la POO est née de plusieurs idées, dont l’une d’elle peut être résumée par ces propos de Bob Barton :</p>
<blockquote><p>The basic principal of recursive design is to make the parts have the same power as the whole.</p></blockquote>
<p>Et dans leur vision, les objets sont perçus comme des cellules d’un organisme cellulaire ou des petits ordinateurs dans un réseau par le biais duquel ils interagissent par messages.</p>
<p>Plus concrètement, dans cet <a href="http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en" hreflang="en">échange de mails</a>, Alan Kay expliquent ce qu’est la POO :</p>
<blockquote><p>OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.</p></blockquote>
<p>Alan Kay ne parle aucunement de structuration. Il met en avant d’abord les concepts de <em>messages</em>, puis d<em>‘encapuslation</em> à la fois des effets de bords et des détails techniques et d’implémentation, et de … résolution retardée ! L’expression <q>extreme late-binding of all things</q> peut surprendre mais est essentielle pour plusieurs choses dont le polymorphisme. Pour comprendre ceci, il faut d’abord savoir qu’en POO le concept de <em>message</em> est décoléré de celui de <em>méthode</em> : une méthode est la réponse propre à l’objet à un message donné (autrement dit son code d’implémentation). Ensuite, tout objet qui sait répondre à un message, <ins>quelque soit son type</ins>, doit pouvoir être utilisé comme receveur. Donc, la méthode à exécuter en réponse à un message ne peut être connue que lorsque le type réel de l’objet est identifié, ce qui ne peut se faire que tardivement, c’est-à-dire lorsque celui-ci est effectivement inféré (le message est transmis à l’objet). Ceci permet à un objet dont le type satisfait l’interface attendue d’être utilisé en tant que receveur et ceci sans nécessairement entrer dans une relation de sous-typage avec l’interface ; celle-ci peut d’ailleurs être <em>dynamique</em> au sens où elle définit uniquement les messages attendus dans un contexte donné. On dit alors que le type de l’objet est un élément de la classe de types représentée par cette interface ; autrement dit il satisfait l’interface définie par la classe de types. En effet, en POO, le polymorphisme provient d’un typage d’ordre supérieur et des relations de sous-classement (les classes ici en tant qu’ensembles polymorphiques de types, cf. <a href="http://www.google.fr/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CDUQFjAA&url=http%3A%2F%2Fwww.cs.cmu.edu%2F~aldrich%2Fcourses%2F819%2Ff-bounded.pdf&ei=thTUUOaDILHL0AWE4IDAAQ&usg=AFQjCNEQohGDFtk686NRe8l06_UJiTF-jA&bvm=bv.1355534169,d.d2k" hreflang="en" title="F-Bound Polymorphisme">le Polymorphisme F-Bound</a> de William Cook et al. ou les articles de Anthony J.H. Simons sur <a href="http://www.jot.fm/issues/issue_2003_05/column2/" hreflang="en" title="A Class is a Type Family">la théorie de la classification dans le JOT, Journal Of Object Technology</a>) ; les relations entre objets ne reposent plus sur le type des objets mais sur l’appartenance de leur type à des classes de types. (Ce que les développeurs Ruby appellent, in vulgare sermo, le <q>duke typing</q>.) Les langages comme C++, Java ou C#, quant à eux, ont rejeté le concept de message, et réalisent le polymorphisme par des relations de sous-typage (principe de Liskov) et par la surcharge de méthodes, mais celui-ci offre des possibilités en deçà de celui de la POO ; c’est pourquoi, pour étendre les possibilités polymorphiques, ils permettent, via la généricité, et dans une certaine mesure, un typage de second ordre (quantification universelle ou, mieux, contrainte). Voici un exemple de type de second ordre contraint en Java :</p>
<pre>
public class Persistable<T extends Persistable<T>> {
...
}
</pre>
<p>Je conclurai brièvement cette article avec cette phrase du <a href="http://www.growing-object-oriented-software.com/" hreflang="en" title="Growing Object-Oriented Software">GOOS</a> (Growing Object-Oriented Software) :</p>
<blockquote><p>Your domain model is not in the classes you create in your source code, but in the messages that the objects pass to one another when they communicate at runtime.</p></blockquote>