2011-08-11 3 views
3

Je suis actuellement en train de mettre en œuvre des solveurs itératifs, qui fonctionnent en améliorant successivement l'estimation de la solution à un problème spécifique. Comme la solution est un ensemble de données plutôt important, le raffinement est effectué en place.Encapsulation d'un objet mutable dans un objet en lecture seule

J'ai implémenté un simple pattern Observer/Observable afin de pouvoir observer l'algorithme pendant les itérations. En particulier, le solveur fournit une méthode

Foo getCurrentSolution() 

qui renvoie l'estimation actuelle de la solution. L'observateur est alors libre de faire quelques calculs, basés sur l'estimation actuelle (par exemple: décider si la solution est assez bonne et si les itérations peuvent être arrêtées). Foo est mutable, mais bien sûr, si l'observateur modifie l'estimation actuelle de la solution, cela peut ruiner les itérations du solveur.

Par conséquent, getCurrentSolution() devrait vraiment retourner une copie défensive. Mais cela nécessite du temps et de la mémoire sur les gros problèmes, donc je suis venu avec une autre idée, qui est getCurrentSolution() retourner un nouveau ReadOnlyFoo(bar), où foo est l'estimation actuelle (mutable) de la solution, privée au solveur. L'idée est que ReadOnlyFoo a presque la même interface que Foo, seules les méthodes qui pourraient modifier les données sont "désactivées" (elles lèvent une exception). Tous les détails de certaines classes factices sont donnés ci-dessous.

Ma question est la suivante: cette approche est-elle une bonne pratique? Y a-t-il un meilleur modèle?

Merci! Sébastien

public abstract class AbstractFoo{ 
    public abstract double getValue(); 

    public abstract void setValue(final double x); 

    public abstract AbstractFoo add(AbstractFoo bar); 

    public void addToSelf(AbstractFoo bar){ 
     setValue(getValue + bar.getValue()); 
    } 
} 

public class Foo extends AbstractFoo{ 
    private double value; 

    public Foo(final double x){ 
     value = x; 
    } 

    public double getValue(){ 
     return value; 
    } 

    public void setValue(final double x){ 
     value = x; 
    } 

    public AbstractFoo add(AbstractFoo bar){ 
     return new Foo(value + bar.getValue()); 
    } 
} 

public final class FooReadOnly extends AbstractFoo{ 
    private final Foo foo; 

    public FooReadOnly(AbstractFoo foo){ 
     this.foo = foo; 
    } 

    public double getValue(){ 
     return foo.getValue(); 
    } 

    public void setValue(final double x){ 
     throw new NotImplementedException("read only object"); 
    } 

    public AbstractFoo add(AbstractFoo bar){ 
     return foo.add(bar); 
    } 

    public void addToSelf(AbstractFoo bar){ 
     throw new NotImplementedException("read only object"); 
    } 
} 

Répondre

3

Je définirais une interface Solution ne contenant que les méthodes de lecture seule, et une classe mutable MutableSolution contenant toutes les méthodes, et rendre la méthode getCurrentSolution() retourner une instance Solution. De cette façon, vous n'avez pas besoin de créer une copie défensive ou d'envelopper votre solution dans un wrapper en lecture seule.

Bien sûr, l'observateur pourrait encore lancer la solution à MutableSolution, mais ce ne serait pas un accident. Si vous souhaitez vous protéger contre les conversions, alors écrivez une classe wrapper ReadOnlySolution implémentant Solution et déléguant au MutableSolution enveloppé. Ceci est similaire à votre proposition, sauf que la signature de la méthode indique clairement que l'objet n'est pas modifiable.

+0

Ce serait certainement plus propre, mais supposez que je ne peux pas changer l'implémentation de 'Solution'? Je ne peux pas le faire hériter de 'MutableSolution' ... – Sebastien

+0

Puis définissez une interface, avec les méthodes en lecture seule seulement, qui est retournée par getCurrentSolution, et définissez une classe implémentant cette interface et déléguant à votre objet mutable. –

+0

Je ne suis pas satisfait de l'autre option, donc je pense que je vais aller pour celui-ci. Merci beaucoup. – Sebastien

1

C'est en fait l'approche que la classe Collections fait avec unmodifiableList(...) etc. Il retourne un emballage qui contient la liste originale mais jette des exceptions dans les méthodes qui modifient la collection.

+0

Je n'étais pas au courant de cela. Je vais regarder dedans. Quelqu'un a cependant soulevé ce problème: si vous ajoutez un nouveau mutateur à l'objet enveloppé, cette approche n'est plus sûre. – Sebastien

+0

Droite. Ce problème n'existe pas avec ma solution. –

1

Je ne ferais pas ça. Si on utilise AbstractFoo même d'une interface commune (qui pourrait exister dans votre implémentation réelle), alors il ne sait pas à l'avance si l'instance courante est mutable ou non. Ainsi, l'utilisateur risque de perdre des exceptions non vérifiées.

Et, pour un objet immuable, étant non modifiable, ce n'est pas exceptionnel du tout. En d'autres termes: je ne voudrais pas utiliser execption pour signaler, que l'on a tenté de modifier une instance de FooReadOnly.

Au moins j'ajouter une méthode abstraite boolean isModifiable() à la classe abstraite AbstractFoo afin que nous puissions tester, si nous pouvons modifier l'objet.Et dans ce cas, nous n'aurions pas besoin de lancer des exceptions - l'implémentation des méthodes de modification pourrait simplement ne rien faire.

+0

+1 pour la suggestion à propos des exceptions. – Sebastien

+0

Je ferais quand même en sorte que les méthodes lancent des exceptions, afin de préciser que le développeur n'a pas respecté le contrat: n'appelez pas les méthodes setter si isModifiable est false. J'utiliserais cependant le standard java.lang.UnsupportedOperationException. –

+0

Je n'ai jamais aimé l'astuce 'UnsupportedOperationException' qu'ils font par exemple dans' Iterator'. Supposons que vous receviez une instance de 'Iterator' d'une méthode et que vous vouliez l'utiliser pour supprimer un élément: vous devrez' try' et 'attraper' une exception non vérifiée juste pour savoir si * cet * itérateur supporte la suppression d'éléments. Pas toutes les lignes de code dans le JRE est un chef-d'œuvre et mérite d'être copié;) –

0

Pourquoi une telle solution surdimensionnée? Pourquoi ne pas avoir un attribut de classe et readOnly booléen? Ensuite, vérifiez checkWriteable() pour chaque setter.

+0

Il devrait y avoir un drapeau non seulement pour 'readOnly', mais aussi pour' immutable'. Ils ne signifient pas la même chose. Le code qui veut savoir et utiliser immédiatement l'état actuel de la solution pourrait être satisfait avec un wrapper en lecture seule à un objet qui changera dans le futur, mais le code qui veut un instantané de l'état actuel devrait faire une copie défensive de l'objet qui lui a été donné * à moins qu'il ne soit déjà connu pour être immuable *. C'est dommage qu'il y ait si peu de support dans de nombreuses bibliothèques pour que les objets signalent s'ils sont immuables ou non. – supercat

Questions connexes