63 votes

Peut-on exposer l'état d'un objet immuable?

Ayant trouvé par hasard le concept des objets immuables récemment, je voudrais connaître les bonnes pratiques pour le contrôle de l'accès à l'état. Même si l'objet est une partie de mon cerveau me donne envie de vivre dans la peur à la vue de membres du public, je ne vois pas de problèmes techniques avec quelque chose comme ceci:

public class Foo {
    public final int x;
    public final int y;

    public Foo( int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Je me sentirais plus à l'aise de déclarer les champs private et de fournir des méthodes de lecture pour chacun, mais cela semble trop complexe lorsque l'état est explicite en lecture seule.

Quelle est la meilleure pratique pour l'accès à l'état d'un objet immuable?

62voto

Kevin Workman Points 6311

Cela dépend entièrement de la manière dont vous allez utiliser l'objet. Les champs publics ne sont pas fondamentalement pervers, il est simplement mauvais de tout rendre public. Par exemple, la classe java.awt.Point rend publics ses champs x et y, qui ne sont même pas définitifs. Votre exemple semble être une bonne utilisation des champs publics, mais vous ne voudrez peut-être pas exposer tous les champs internes d'un autre objet immuable. Il n'y a pas de règle fourre-tout.

31voto

Paul J Abernathy Points 531

J'ai pensé à la même chose dans le passé, mais généralement finir par faire de variables privées et à l'aide des getters et setters ainsi que, plus tard, je vais encore avoir la possibilité d'apporter des modifications à la mise en œuvre, tout en gardant la même interface.

Cela ne me rappellent quelque chose que j'ai lu récemment dans "Code Propre" par Robert C. Martin. Dans le chapitre 6, il donne une perspective légèrement différente. Par exemple, à la page 95, il déclare

"Les objets cacher leurs données derrière des abstractions et d'exposer les fonctions qui opèrent sur des données. Structure de données exposer leurs données et n'ont pas véritablement fonctions."

Et à la page 100:

La quasi-encapsulation de haricots, il semble OO les puristes se sentir mieux, mais généralement ne fournit aucun autre avantage.

Basé sur le code de l'échantillon, la classe Foo semble être une structure de données. Ainsi, d'après ce que j'ai compris de la discussion dans le Code Propre (ce qui est plus que juste les deux citations que j'ai donné), le but de cette classe est d'exposer des données, pas fonctionnalités, et avoir des accesseurs et mutateurs n'a probablement pas faire beaucoup de bien.

Encore une fois, dans mon expérience, j'ai généralement allés de l'avant et a utilisé le "haricot" approche des données privées avec des getters et setters. Mais là encore, personne ne m'a jamais demandé d'écrire un livre sur comment écrire un code de meilleure qualité, afin peut-être que Martin a quelque chose à dire.

11voto

Marko Topolnik Points 77257

Si votre objet est suffisamment utilisé en local pour que vous ne vous préoccupiez plus de la modification ultérieure des modifications d'API, il n'est pas nécessaire de placer des getters par-dessus les variables d'instance. Mais il s’agit d’un sujet général, non spécifique aux objets immuables.

L'avantage d'utiliser des accesseurs provient d'une couche supplémentaire d'indirection, qui peut s'avérer utile si vous concevez un objet qui sera utilisé à grande échelle et dont l'utilité s'étendra dans un avenir imprévisible.

6voto

Brian Agnew Points 143181

Indépendamment de l'immuabilité, vous êtes encore en les exposant à la mise en œuvre de cette classe. À un certain stade, vous aurez envie de changer la mise en œuvre (ou peut-être produire diverses dérivations par exemple à l'aide de l'exemple de Point, vous voudrez peut-être un Point de classe à l'aide des coordonnées polaires), et votre code client est exposé à ce.

Le schéma ci-dessus peut être utile, mais je préfère généralement le restreindre à très localisées instances (par exemple: passage de n-uplets de l'information autour - j'ai tendance à trouver que les objets apparemment sans rapport avec l'info, cependant, sont de mauvais dispositifs d'encapsulation, ou que l'info est liée, et mon tuple transforme en un véritable objet)

5voto

The Spooniest Points 504

La chose importante à garder à l'esprit est que les appels de fonction de fournir une interface universelle. Tout objet peut interagir avec d'autres objets à l'aide d'appels de fonction. Tout ce que vous avez à faire est de définir le droit de signature, et vous allez loin. Le seul hic, c'est que vous avez à interagir uniquement par le biais de ces appels de fonction, qui souvent fonctionne bien, mais peut être maladroit dans certains cas.

La raison principale pour exposer les variables d'état directement serait d'être en mesure d'utiliser primitive directement les opérateurs sur ces champs. Quand il est bien fait, cela peut améliorer la lisibilité et la commodité: par exemple, l'ajout de nombres Complexes avec +, ou l'accès à une collection assortie avec []. Les avantages de ce qui peut être surprenant, à condition que votre utilisation de la syntaxe suit des conventions traditionnelles.

Le hic, c'est que les opérateurs ne sont pas une interface universelle. Seulement un ensemble très spécifique de types intégrés pouvez les utiliser, ils ne peuvent être utilisés de la manière que la langue s'attend, et vous ne pouvez pas définir de nouveaux. Et donc, une fois que vous avez défini votre interface publique à l'aide de primitives, vous êtes vous-même en utilisant des primitives, et seulement cette primitive (et d'autres choses qui peuvent être facilement moulé). Pour utiliser autre chose, vous avez à danser autour de cette primitive à chaque fois que vous interagissez avec elle, et qui vous tue d'un SÈCHE perspective: les choses peuvent devenir très fragile, très rapidement.

Certaines langues font les opérateurs dans une interface universelle, mais Java ne fonctionne pas. Ce n'est pas un acte d'accusation de Java: ses concepteurs ont choisi délibérément de ne pas inclure la surcharge d'opérateur, et ils avaient de bonnes raisons de le faire. Même lorsque vous travaillez avec des objets qui semblent correspondre à bien avec la traditionnelle significations des opérateurs, de les faire fonctionner d'une manière qui a du sens peut être étonnamment nuancé, et si vous n'avez pas absolument des ongles, vous allez payer pour ça plus tard. Il est souvent beaucoup plus facile de faire une fonction de l'interface lisible et utilisable que pour passer par ce processus, et souvent même le vent avec un meilleur résultat que si vous aviez utilisé les opérateurs.

Il y ont été compromis impliqués dans cette décision, cependant. Il y sont des moments où un opérateur basé sur l'interface fonctionne vraiment mieux qu'une fonction de base, mais sans la surcharge d'opérateur, cette option n'est pas disponible. En essayant d'incorporer les opérateurs en aucune façon de vous enfermer dans certaines décisions de conception que vous n'avez probablement pas vraiment envie d'être gravé dans la pierre. La Java des designers pensé que ce compromis a été utile, et ils pourraient même avoir été correcte à ce sujet. Mais les décisions comme cela ne vient pas sans quelques retombées, et ce genre de situation est l'endroit où les retombées hits.

En bref, le problème n'est pas d'exposer votre mise en œuvre, en soi. Le problème est de vous enfermer dans la mise en œuvre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X