Le changement de nom d'une méthode peut-il préserver l'encapsulation?

9

Je lisais cette page , sur le moment où les getters / setters sont justifiés, et l'OP a donné l'exemple de code suivant:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

La réponse acceptée indique:

Soit dit en passant, dans votre exemple, je donnerais à la classe Fridgeles méthodes putCheese()et takeCheese(), au lieu de get_cheese() et set_cheese(). Ensuite, vous auriez toujours l'encapsulation.

Comment l'encapsulation est-elle préservée en la renommant de get / set en putCheese()/ takeCheese()Vous obtenez évidemment / définissant une valeur, alors pourquoi ne pas simplement la laisser comme get / set?

Dans la même réponse, il indique également:

Avoir des getters et des setters ne rompt pas en soi l'encapsulation. Ce qui rompt l'encapsulation, c'est l'ajout automatique d'un getter et d'un setter pour chaque membre de données (chaque champ, en java java), sans y penser.

Dans ce cas, nous n'avons qu'une seule variable, cheeseet vous voudrez peut-être prendre et retourner le fromage au réfrigérateur, donc une paire get / set dans ce cas est justifiée.

QueenSvetlana
la source
7
putCheeseajouterait du fromage au réfrigérateur et takeCheesel'enlèverait - ce sont des abstractions orientées domaine (de niveau supérieur), plutôt que des getters et setters de champ d'objet (qui sont des abstractions de programmation informatique (de bas niveau)).
Erik Eidt

Réponses:

17

Je pense que tu ne comprends pas. Cela ne veut pas dire que vous devez renommer le setter et le getter, mais avoir des méthodes qui ajoutent et retirent des éléments du réfrigérateur. c'est à dire

public class Fridge
{
    private int numberOfCheeseSlices;

    public void AddCheeseSlices(int n)
    {
         if(n < 0) { 
             throw new Exception("you cant add negative cheese!");
         }
         if(numberOfCheeseSlices + n > this.capacityOfFridge) { 
             throw new Exception("TOO MUCH CHEESE!!");
         }
         ..etc
         this.numberOfCheeseSlices += n;
    }
}

Maintenant, la variable privée est encapsulée. Je ne peux pas simplement le régler sur tout ce que je veux, je peux seulement ajouter et retirer des tranches de fromage du réfrigérateur en utilisant les méthodes, qui à leur tour garantissent que les règles de logique commerciale du fromage que je veux sont appliquées.

Ewan
la source
2
C'est vrai, mais vous pouvez toujours le laisser comme setCheese()avoir la même logique dans le setter. Pour moi, de toute façon, vous essayez de cacher le fait que vous utilisez un setter en renommant la méthode, mais votre getter / setting évidemment quelque chose.
QueenSvetlana
21
@QueenSvetlana ce n'est pas un passeur. C'est une opération. Vous dites au réfrigérateur «voici un autre fromage», et il l'ajoute à son nombre de fromages encapsulés internes. Un passeur dirait "vous avez maintenant 5 fromages". Ce sont des opérations fonctionnellement différentes, pas seulement des noms différents.
Ant P
1
vous pourriez l'appeler setCheese, mais ce serait déroutant car cela ne définirait pas la valeur du fromage.
Ewan du
6
Notez qu'il y a un bogue de dépassement d'entier ici. S'il y a déjà plus de 0 fromage, AddCheese(Integer.MAX_VALUE)devrait échouer, mais ne le fera pas.
user253751
3
qui seraient entièrement couverts dans la section ..etc
Ewan
5

Les getters et setters rompent l'encapsulation à chaque fois , par définition. On pourrait dire que parfois nous devons le faire. Avec cela à l'écart, voici mes réponses:

Comment l'encapsulation est-elle préservée en la renommant de get / set en putCheese () / takeCheese () Vous obtenez évidemment / définissez une valeur, alors pourquoi ne pas simplement la laisser comme get / set?

La différence réside dans la sémantique, c'est-à-dire la signification de ce que vous écrivez. L'encapsulation ne consiste pas seulement à protéger, mais à cacher l'état interne. L'état interne ne doit même pas être connu à l'extérieur, mais il est prévu que l'objet propose des méthodes pertinentes pour l'entreprise (parfois appelées "comportement") pour manipuler / utiliser cet état.

Donc get, ce setsont des termes techniques qui semblent n'avoir rien à voir avec le domaine "réfrigérateur", alors putque cela takepourrait avoir du sens.

Toutefois , si putou takefaire réel sens dépend toujours des exigences et ne peut être jugé objectivement. Voir le point suivant:

Dans ce cas, nous n'avons qu'une seule variable, le fromage, et vous voudrez peut-être prendre et retourner le fromage au réfrigérateur, donc une paire get / set dans ce cas est justifiée.

Cela ne peut être objectivement déterminé sans plus de contexte. Votre exemple contient une go_shopping()méthode ailleurs. Si c'est à cela qu'il Fridgesert, alors il n'a pas besoin getou set, ce dont il a besoin Fridge.go_shopping(). De cette façon, vous avez une méthode dérivée des exigences, toutes les "données" dont il a besoin sont locales et vous avez un comportement réel au lieu d'une structure de données à peine voilée.

N'oubliez pas que vous ne construisez pas un générique réutilisable Fridge. Vous construisez un Fridgepour vos besoins uniquement. Tout effort dépensé pour en faire plus que ce qu'il doit être est en fait un gaspillage.

Robert Bräutigam
la source
10
Getters and setters break encapsulation every single time- C'est un peu trop formulé à mon goût. Les méthodes (y compris les getters et setters) ont le même objectif que les portes lors d'un concert: elles permettent une entrée et une sortie ordonnées du lieu.
Robert Harvey
8
"Les getters et les setters brisent l'encapsulation à chaque fois, par définition" - par quelle définition? Moi aussi, j'ai un problème avec cela, car les getters et setters ne sont qu'une partie de l'interface publique, comme tout autre membre public; ils ne cassent l'encapsulation que s'ils exposent des détails d'implémentation considérés comme internes par conception (c'est-à-dire par une décision du programmeur (ou d'une équipe)). Le fait que les getters et les setters soient souvent ajoutés au hasard, avec un contrôle de couplage après coup, est une tout autre affaire.
Filip Milovanović
2
@ FilipMilovanović Fair point. Comme je le mentionne dans la réponse, "l'encapsulation" est utilisée pour masquer les éléments internes de l'objet (== état de l'objet == variables d'instance). Les getters et setters publient les objets internes. Par conséquent, selon ces définitions, ils sont en perpétuel conflit. Maintenant, si nous devons parfois rompre l'encapsulation est une question différente, qui doit être traitée séparément. Pour être clair, en disant que les setters / getters cassent toujours l'encapsulation, je ne dis pas que c'est toujours "faux", si cela aide.
Robert Bräutigam
5
les getters et setters ne "cassent pas l'encapsulation littéralement toujours". Les getters et les setters ne font souvent même pas référence aux membres en dessous directement, n'effectuent aucune sorte d'opération, ou sont uniquement définis de manière exclusive, vous pourriez avoir seulement un getter ou seulement un setter. Il rompt l'encapsulation lorsque les getters et les setters ne font rien de plus que l'accès aux membres et sont tous deux définis pour les mêmes champs. Même cela peut être nécessaire pour les mainteneurs de bibliothèque qui ne veulent pas casser les ABI / API avec des modifications internes et éviter la recompilation du client.
quand
4
Ok, les getters et setter ne cassent l'encapsulation que 99% du temps. Heureux? Et, en exposant le type de l'objet encapsulé, ils cassent définitivement l'encapsulation. Une fois que vous en avez un, public int getFoo()il est presque impossible, en pratique, de passer à un autre type.
user949300
3

Presque tout cela montre une incompréhension fondamentale de l'encapsulation et de la manière dont elle s'applique.

La réponse initiale selon laquelle vous rompiez l'encapsulation est tout simplement fausse. Votre application peut avoir besoin de simplement définir la valeur du fromage dans le réfrigérateur au lieu d'augmenter / diminuer ou d'ajouter / supprimer. De plus, ce n'est pas de la sémantique, peu importe comment vous l'appelez, si vous avez besoin d'accéder et / ou de modifier des attributs, vous ne rompez pas l'encapsulation en les fournissant. Enfin, l'encapsulation ne consiste pas vraiment à "cacher", il s'agit de contrôler l'accès à l'état et aux valeurs qui ne devraient pas avoir besoin d'être publiques ou manipulées en dehors de la classe, tout en l'accordant à ceux qui le devraient et en effectuant la tâche mise à disposition en interne.

Un getter ou un setter ne rompt pas l'encapsulation lorsqu'il existe un besoin légitime d'obtenir ou de définir une valeur. C'est pourquoi les méthodes peuvent être rendues publiques.

L'encapsulation consiste à conserver les données et les méthodes qui modifient ces données directement ensemble dans un seul endroit logique, la classe.

Dans ce cas particulier, il est clairement nécessaire de modifier la valeur du fromage dans l'application. Quelle que soit la façon dont cela est fait, via get / set ou add / remove, tant que les méthodes sont encapsulées dans la classe, vous suivez le style orienté objet.

Pour plus de précision, je vais donner un exemple de la façon dont l'encapsulation est rompue en fournissant un accès indépendamment du nom de la méthode ou de l'exécution logique.

Supposons que votre réfrigérateur ait une "durée de vie", juste un certain nombre de tiques avant que le réfrigérateur ne soit plus opérationnel (pour des raisons d'argument, le réfrigérateur ne peut pas être réparé). Logiquement, il est impossible qu'un utilisateur (ou le reste de votre application) puisse modifier cette valeur. Cela devrait être privé. Il ne serait visible qu'à travers, par exemple, un attribut public différent appelé "isWorking". À l'expiration de la durée de vie, le réfrigérateur fonctionne en interne et fonctionne à faux.

L'exécution du décompte à vie et son actionnement de l'interrupteur isWorking sont toutes internes au réfrigérateur, rien à l'extérieur ne devrait / ne devrait pouvoir effectuer le processus. isWorking ne doit être visible que par conséquent, un getter ne rompt pas l'encapsulation. Cependant, l'ajout d'accesseurs pour des éléments du processus de durée de vie briserait votre encapsulation.

Comme la plupart des choses, la définition de l'encapsulation n'est pas littérale, elle est relative. Devriez-vous voir X en dehors de la classe? Devriez-vous être en mesure de changer Y? Est-ce que tout ce qui s'applique à votre objet ici dans cette classe ou la fonctionnalité est-elle répartie sur plusieurs classes?

user343330
la source
Regardons cela de façon pragmatique. Vous dites que l'encapsulation n'est pas rompue si le code est conçu de cette façon ou s'il existe une raison "légitime". Cela signifie essentiellement que nous ne pouvons jamais dire que l'encapsulation est rompue, car le développeur n'a qu'à dire qu'elle "avait l'intention" de cette façon, ou qu'il y avait une raison "légitime". Cette définition est donc fondamentalement inutile, n'est-ce pas?
Robert Bräutigam
Non, je dis que l'encapsulation n'est pas interrompue simplement en fournissant un accès. Et cela n'a rien à voir avec ce que dit un programmeur, mais quels sont les besoins de l'application. Une application doit avoir accès pour manipuler les objets, l'encapsulation consiste à contrôler l'accès. Une application peut avoir besoin de "définir" un attribut d'un objet, mais la logique et / ou les calculs requis pour effectuer cette opération "définir" doivent être encapsulés dans la classe d'objet.
user343330
Je pense que c'est là que nous différons fondamentalement, vous dites: "Une application peut avoir besoin de" définir "un attribut d'un objet ...". Je ne pense pas. Le choix d'une conception qui implique la définition ou l'obtention d'un attribut appartient au développeur. C'est un choix! Il existe de nombreuses façons de coder quelque chose, toutes n'impliquent pas l'obtention ou la définition de valeurs de variable d'instance.
Robert Bräutigam
Pourrait être ... Habituellement, la conception est basée sur la satisfaction d'un besoin, que l'un ou l'autre soit choisi par le programmeur en fonction de l'environnement commercial. Dans les deux cas cependant, si l'application a un besoin fondamental de manipuler une valeur qui fait partie d'un objet, vous devez rendre cette fonctionnalité accessible d'une manière ou d'une autre. Fournir un point d'entrée pour effectuer le travail ne rompt pas l'encapsulation. Prenez une application de vente. À un moment donné, votre transaction devrait calculer la taxe, ce faisant, dans l'objet de transaction, elle resterait encapsulée, mais l'application devrait pouvoir indiquer la transaction.CalcTax
user343330
Les setters sont de mauvais exemples en raison de leur simplicité, pensez en termes de fonction qui fait beaucoup plus de travail qui se traduit par la mise à jour d'un attribut d'objet par rapport à quelque chose en dehors de la classe qui fait le travail et dit ensuite object.attribute = x. Le premier est une bonne encapsulation tout en fournissant un accès approprié, le second ne l'est pas. En ce qui concerne les getters, l'application doit-elle connaître uniquement cette partie d'un objet pour faire autre chose qui ne peut pas être contenu dans la classe d'objets? si oui, l'exposer ne rompt pas votre encapsulation. sinon, encapsulez-le dans la classe d'objets
user343330
1

Il ne s'agit pas seulement de renommer une méthode. Les deux méthodes fonctionnent différemment.

(Imaginez ceci dans votre esprit)

get_cheese et set_cheese expose le fromage. putCheese () et takeCheese () gardent le fromage caché et prennent soin de le gérer et donnent à l'utilisateur un moyen de le gérer. L'observateur ne voit pas le fromage, il ne voit que deux méthodes pour le manipuler.

Daneesha
la source