web-dev-qa-db-fra.com

Fruit obj = nouvelle orange (); et Orange obj = nouvel Orange (); si les deux fonctionnent à l'identique dans mon code, lequel est le moins couplé?

Supposons que j'ai 2 classes (qui ne montrent pas les méthodes pour paraître plus simples):

public interface Fruit{
}

public class Orange implements Fruit{
}

, et supposons que je peux utiliser 2 façons d'initialiser Orange sans différence réelle (cela signifie que les deux peuvent compiler et fonctionner de la même manière dans mon code):

1. résumé:

Fruit obj=new Orange();

2. béton:

Orange obj=new Orange();

, selon Comprendre la "programmation vers une interface" , si je comprends bien, je devrais utiliser l'abstrait, car il correspond plus à la "programmation vers l'interface" et a moins de couplage.

Mais je ne comprends pas très bien pourquoi l'abstrait a moins de couplage, car je pense que l'abstrait a 2 mots-clés de classes: Fruit et Orange, tandis que le concret n'a qu'un mot-clé: Orange, ce qui signifie que l'abstrait dépend de plus Des classes. Bien que je sois d'accord, l'abstrait peut me permettre de passer d'Orange à un autre type (par exemple: Raisin) en effaçant et en tapant moins de caractères, il contient toujours 2 mots clés après le changement (Fruit et Raisin).

J'ai également constaté que l'autre inconvénient de l'abstrait est, si un jour, Orange doit supprimer l'interface ou passer à une autre interface:

public class Orange{
}

ou interface publique CircleShape {}

public class Orange implements CircleShape{
}

, l'abstrait doit être modifié, tandis que le concret n'a pas besoin de le faire. Je pense donc que le concret a moins de couplage que l'abstrait. Est-ce vrai? Sinon, quelle est l'idée fausse ici?

3
aacceeggiikk

Le couplage se produit ici entre Orange et le reste de votre programme. Si vous conservez une instance de Orange, vous pouvez éventuellement appeler une méthode spécifique à Orange et non à Fruit. C'est mauvais pour deux raisons: A) l'utilisation de méthodes spécifiques à Orange signifie que vous ne pouvez pas agir sur cette instance simplement comme une instance Fruit (viole le principe de substitution de Liskov), et B) cela implique que "Orange" n'est pas un "Fruit" en ce qu'il se comporte différemment.

En utilisant un exemple plus concret, supposons que vous ayez besoin d'une nouvelle liste et donc vous faites:

ArrayList<String> list = new ArrayList<String>();

Vous décidez que vous voulez passer ceci à une méthode, et donc bien sûr votre méthode devient:

public void handleList(ArrayList<String> list) {
    // ...
}

Si vous y pensez, la méthode handleList ne se soucie pas particulièrement du fait qu'il s'agit d'une instance de ArrayList ou même de List très probablement. Il doit traiter au moins une fois tous les objets de cette collection. Vous pouvez donc le réécrire en toute sécurité comme suit et cela vous permettrait potentiellement de passer d'autres implémentations (la vôtre incluse!):

public void handleList(Collection<String> list) {
    // ...
}

Si vous vouliez aller plus loin, vous utiliseriez un caractère générique (?) À la place de String et appeleriez toString () sur chaque élément de la collection. Le fait est que nous ne sommes pas obligés d'utiliser une instance de ArrayList. Nous pourrions utiliser LinkedList ou HashSet ou TreeSet à la place.

Pour revenir à votre exemple, idéalement dans votre programme, vous devez vous soucier du fait qu'il utilise Orange par opposition aux classes Grape ou Apple exactement dans une place dans votre programme. Après ce point, vous ne devez utiliser ces instances que comme Fruit, ou pour le dire autrement, vous ne vous souciez plus de la façon dont elles sont implémentées, vous vous souciez seulement de la façon dont elles sont utilisées .

Cela dit, il peut être pratique d'avoir une classe commune abstraite avec des fonctionnalités communes entre les implémentations. Ce n'est pas un substitut pour une interface. Une interface définit comment il sera utilisé . Une classe abstraite est destinée à réduire et à simplifier vos implémentations. En théorie, vous pouvez avoir plusieurs classes abstraites avec chacune avec plusieurs implémentations concrètes, toutes dérivant d'une seule interface que vous utilisez dans votre programme.

Pour plus de lecture, jetez un oeil à cet article expliquant un peu le principe de substitution de Liskov, en particulier la partie concernant le principe ouvert fermé. Vous devez avoir une compréhension claire dans votre programme où vous définissez des objets et où vous les utilisez. S'ils ne se trouvent pas au même endroit, vous devriez certainement utiliser des interfaces pour définir comment ils sont utilisés .

3
Neil

En règle générale, vous devez utiliser la plus petite quantité d'informations nécessaires.

Dans votre exemple, si Orange implémente l'interface Fruit, et si votre code fonctionne correctement sans savoir que l'objet est un objet Orange, alors il doit être déclaré en tant qu'interface Fruit. Cela signifie qu'il n'est pas nécessaire de changer quoi que ce soit si vous voulez avoir un objet Strawberry, sauf en changeant la première ligne en "Fruit obj = new Strawberry ();". Bien sûr, si des parties du code suivant attendent un objet Orange et utilisent des méthodes qui ne sont pas présentes dans l'interface Fruit, vous commencez par "Orange obj = new Orange ();" Et vous savez que votre code ne fonctionnera pas uniquement avec un objet Strawberry. C'est difficile, cela nécessite des changements de code, mais ces changements de code sont nécessaires.

Si vous changez l'interface qu'Orange implémente - eh bien, vous ne pouvez pas simplement changer cette interface. Vous implémenterez les deux interfaces. Ou vous supprimerez les méthodes nécessaires pour Fruit et ajouterez des méthodes pour la nouvelle interface. Votre code créant l'objet Orange ne fonctionnera plus. Déclarant Orange obj = nouvel Orange (); ne vous aurait pas aidé car vous appeliez très probablement des méthodes d'interface qui n'existent plus. Changer l'interface est un changement majeur avec des conséquences majeures. Il est peu probable que votre classe FruitSalad puisse fonctionner avec un objet Orange s'il n'implémente pas l'interface Fruit.

2
gnasher729

Cela revient à la question de

Pourquoi résumons-nous quand même?

Le point d'abstraction est de faire plus avec moins. Quand vous voyez le code comme règles comme ne parlez pas aux hommes que vous ne connaissez pas qui portent des costumes sombres et des chapeaux noirs et ne parlez pas à des étrangers vous avez deux règles, cette dernière étant plus abstraite que la première.

Vous vous retrouvez donc avec une règle abstraite contenant moins de mots, étant plus générale et traitant ainsi plus de cas.

Mais qu'en est-il de parler aux médecins qui peuvent être a) des étrangers et b) des hommes?

Ensuite, la règle n'a pas de sens et vous devez faire face aux exceptions à la règle.

Qu'est-ce que cela a à voir avec votre problème?

Avec une interface de Fruit vous avez fait une abstraction. Et chaque instruction traitant de $things qui se comportent comme fruits est très général et pourrait être appliqué à de nombreuses choses fruitées.

Vous ne voulez pas écrire de code pour chaque Fruit que vous connaissez.

Mais comme dans l'exemple ci-dessus, cela s'effondre lorsque vous avez besoin d'un comportement Orange, qui peut être plus riche que niquement comportement fruité.

Donc "implémenter une interface" est un règle générale ce qui signifie:

C'est une bonne idée de regrouper des objets partageant un comportement commun avec une interface et d'écrire des instructions pour que ces objets écrivent moins de code que pour chaque objet individuel. Mais parfois, cette règle ne tient pas.

P.S .: "Couplage" signifie dans ce "selon la spécialisation" ou étant moins général. Ainsi, lorsque vous établissez des règles générales, vous évitez la spécialisation = couplage.

0
Thomas Junk