Je comprends l'intention du principe ouvert-fermé. Il est destiné à réduire le risque de casser quelque chose qui fonctionne déjà en le modifiant, en vous disant d'essayer de l'étendre sans le modifier.
Cependant, j'ai eu du mal à comprendre comment ce principe est appliqué dans la pratique. À ma connaissance, il existe deux façons de l'appliquer. Avant et après un éventuel changement:
Avant: programmez les abstractions et «prédisez l'avenir» autant que vous le pouvez. Par exemple, une méthode
drive(Car car)
devra changer si desMotorcycle
s sont ajoutés au système à l'avenir, donc elle viole probablement OCP. Mais la méthodedrive(MotorVehicle vehicle)
est moins susceptible de devoir changer à l'avenir, elle adhère donc à l'OCP.Cependant, il est assez difficile de prédire l'avenir et de savoir à l'avance quels changements vont être apportés au système.
Après: lorsqu'une modification est nécessaire, étendez une classe au lieu de modifier son code actuel.
La pratique n ° 1 n'est pas difficile à comprendre. Cependant, c'est la pratique n ° 2 que j'ai du mal à comprendre comment postuler.
Par exemple (je l'ai pris à partir d' une vidéo sur YouTube): disons que nous avons une méthode dans une classe qui accepte des CreditCard
objets: makePayment(CraditCard card)
. Un jour Voucher
s est ajouté au système. Cette méthode ne les prend pas en charge, elle doit donc être modifiée.
Lors de la mise en œuvre de la méthode, nous n'avons pas réussi à prédire l'avenir et à programmer en termes plus abstraits (par exemple makePayment(Payment pay)
, nous devons donc maintenant modifier le code existant.
La pratique # 2 dit que nous devrions ajouter la fonctionnalité en étendant au lieu de modifier. Qu'est-ce que ça veut dire? Dois-je sous-classer la classe existante au lieu de simplement changer son code existant? Dois-je faire une sorte de wrapper juste pour éviter de réécrire du code?
Ou le principe ne fait-il même pas référence à «comment modifier / ajouter correctement des fonctionnalités», mais plutôt à «comment éviter d'avoir à apporter des modifications en premier lieu (c'est-à-dire programmer des abstractions)?
Réponses:
Les principes de conception doivent toujours être mis en balance les uns avec les autres. Vous ne pouvez pas prédire l'avenir, et la plupart des programmeurs le font horriblement lorsqu'ils essaient. C'est pourquoi nous avons la règle de trois , qui concerne principalement la duplication, mais s'applique également à la refactorisation pour tout autre principe de conception.
Lorsque vous n'avez qu'une seule implémentation d'une interface, vous n'avez pas besoin de vous soucier beaucoup d'OCP, à moins qu'il ne soit clair où les extensions auraient lieu. En fait, vous perdez souvent la clarté lorsque vous essayez de sur-concevoir dans cette situation. Lorsque vous le prolongez une fois, vous le refactorisez pour le rendre compatible OCP si c'est la façon la plus simple et la plus claire de le faire. Lorsque vous l'étendez à une troisième implémentation, vous vous assurez de la refactoriser en tenant compte d'OCP, même si cela nécessite un peu plus d'efforts.
En pratique, lorsque vous n'avez que deux implémentations, la refactorisation lorsque vous en ajoutez une troisième n'est généralement pas trop difficile. C'est lorsque vous le laissez dépasser ce point qu'il devient difficile à entretenir.
la source
Je pense que vous regardez trop loin dans le futur. Résoudre le problème actuel d'une manière flexible qui adhère à ouvert / fermé.
Disons que vous devez implémenter une
drive(Car car)
méthode. Selon votre langue, vous avez deux options.Pour les langages qui prennent en charge la surcharge (C ++), utilisez simplement
drive(const Car& car)
À un moment donné, vous en aurez peut-être besoin
drive(const Motorcycle& motorcycle)
, mais cela ne gênera pasdrive(const Car& car)
. Aucun problème!Pour les langues qui ne prennent pas en charge la surcharge (Objective C), incluez ensuite le nom du type dans la méthode
-driveCar:(Car *)car
.À un moment donné, vous en aurez peut-être besoin
-driveMotorcycle:(Motorcycle *)motorcycle
, mais encore une fois, cela n'interfèrera pas.Cela permet
drive(Car car)
d'être fermé à la modification, mais est ouvert à l'extension à d'autres types de véhicules. Cette planification future minimaliste qui vous permet de faire le travail aujourd'hui, mais vous empêche de vous bloquer à l'avenir.Essayer d'imaginer les types les plus élémentaires dont vous avez besoin peut conduire à une régression infinie. Que se passe-t-il lorsque vous souhaitez conduire un Segue, un vélo ou un Jumbo jet. Comment construisez-vous un seul type abstrait générique qui peut rendre compte de tous les appareils que les gens utilisent et utilisent pour la mobilité?
la source
Il s'agit également de ne pas casser tous les objets qui s'appuient sur cette méthode en ne modifiant pas le comportement des objets déjà existants. Une fois qu'un objet a annoncé un changement de comportement, il est risqué car vous modifiez le comportement connu et attendu de l'objet sans savoir exactement ce que les autres objets attendent de ce comportement.
Ouaip.
"N'accepte que les cartes de crédit" est défini comme faisant partie du comportement de cette classe, via son interface publique. Le programmeur a déclaré au monde que la méthode de cet objet ne prend que les cartes de crédit. Elle l'a fait en utilisant un nom de méthode peu clair, mais c'est fait. Le reste du système s'appuie sur cela.
Cela avait peut-être du sens à l'époque, mais maintenant, si cela doit changer maintenant, vous devriez créer une nouvelle classe qui accepte des choses autres que les cartes de crédit.
Nouveau comportement = nouvelle classe
En aparté - Une bonne façon de prédire l'avenir est de penser au nom que vous avez donné à une méthode. Avez-vous donné un nom de méthode de sondage vraiment général comme makePayment à une méthode avec des règles spécifiques dans la méthode quant au paiement exact qu'elle peut effectuer? C'est une odeur de code. Si vous avez des règles spécifiques, celles-ci doivent être clarifiées par le nom de la méthode - makePayment doit être makeCreditCardPayment. Faites-le lorsque vous écrivez l'objet la première fois et d'autres programmeurs vous en remercieront.
la source