web-dev-qa-db-fra.com

Liskov Substitution Principe Incooussement? Comment comprendre?

Le principe de substitution de Liskov déclare:

"Si S est un sous-type de T, les objets de type T peuvent être remplacés par des objets de type S sans modifier aucune des propriétés souhaitables du programme (exactitude, tâche effectuée, etc.)"

  • Les conditions préalables ne peuvent être renforcées dans le sous-type
  • Les conciliements ne peuvent pas être affaiblis dans le sous-type
  • Les invariants doivent être préservés dans le sous-type

Maintenant, imaginez la relation suivante entre personne et employé:

export default class Person {
    age: number;

    constructor () {
        this.age = 0;
    }

    setAge (age: number) {
        if (age < 0) throw new Error("Age must be 0 or above");
        this.age = age;
    }
}
import Person from "./Person";

export default class Employee extends Person {
    salary: number;

    constructor () {
        super();
        this.salary = 0;
    }

    setAge (age: number) {
        if (age < 18) throw new Error("Age must not be under 18");
        super.setAge(age);
    }

    setSalary (salary: number) {
        this.salary = salary;
    }
}

Lorsque nous appelons la méthode Settage à l'aide d'une instance de personne, nous pouvons utiliser n'importe quel âge supérieur à 0. Cependant, si nous remplacons une instance de personne par une instance de l'employé, selon l'âge, le programme va casser parce que l'âge doit être au-dessus de l'âge. 18.

J'ai 3 questions:

  1. Cela signifie que les conditions préalables ont été renforcées dans le sous-type?
  2. Avons-nous cassé le LSP?
  3. Comment réparer cette relation afin de ne pas casser le LSP?
1
Rodrigo Branas

Oui il casse le LSP. Spécifiquement, la méthode setAge est un problème. La solution imo est de supprimer la méthode ou de changer sa componditionnement.

Par ce que je veux dire, la componition implicite de setAge(age: number) est assert(this.age == age). Si vous modifiez cela, vous pouvez ajuster la condition préalable afin que la personne et l'employé puisse conserver la méthode.

Pour la personne:

    setAge (age: number) {
        assert(true); // precondition
        if (age >= 0) { 
            this.age = age;
        }
        assert(this.age >= 0); // post-condition
    }

Pour employé:

    setAge (age: number) {
        assert(true); // precondition
        if (age >= 18) { 
            this.age = age;
        }
        assert(this.age >= 18); // post-condition
    }

Avec ce qui précède, j'ai:

  • Non renforcé la condition préalable (ils sont tous deux identiques.)
  • Ne pas affiner la postcondition (en fait, j'ai la force.)
  • Préservé l'invariant (l'invariant implicite est age >= 0

Vous remarquerez que le véritable changement de ce qui précède est que setAge n'est plus une demande, mais c'est une suggestion et qui fait toute la différence.

En général, la manière de s'assurer que vos classes sont conformes à la LSP de leur donner des méthodes où vous leur dites des événements qui se sont produits en dehors de l'objet plutôt que de leur dire d'effectuer des actions à l'intérieur. C'est pourquoi dans de nombreux systèmes, vous voyez des méthodes appelées quelque chose comme xDidHappen au lieu de doX.

Lorsque vous dites à un objet que X est arrivé, vous le laissez jusqu'à l'objet de décider quoi faire en réponse. C'est exactement ce que les objets sont censés faire. Lorsque vous dites à un objet de "faire x" (ou dans ce cas "SET X"), vous supprimez l'agence de l'objet. Ce n'est plus en contrôle de son propre état. Quelque chose en dehors de l'objet appelle maintenant tous les coups. Ce n'est pas oo; C'est la programmation impérative dans OO Vêtements.

0
Daniel T.

Au moins pour ce cas particulier, la stratégie correcte me semble assez simple.

Débarrassez-vous de setAge entièrement. Au moins à mon avis, c'est une fonction mal choisie d'avoir en premier lieu.

Au lieu de cela, je aurais le constructeur emmener l'anniversaire de la personne comme un paramètre. En supposant que votre intention soit de représenter uniquement des personnes vivantes, le constructeur de Person peut vérifier que l'anniversaire spécifié n'est pas plus que (dire) il y a 250 ans (mais si c'est pour les gens en général, probablement pas fais ça). Pour Employee Cela vérifierait évidemment que c'est au moins 18 ans.

Stocker l'anniversaire (plutôt que l'âge) évite un certain nombre de problèmes, notamment celui que vous avez souligné, mais également la nécessité de mettre à jour constamment des âges, car ils changent chaque année.

Mais comme de côté, même lorsque vous utilisez setAge, le problème que vous avez spécifié ne devrait pas exister. Même lorsqu'il est invoqué via la base, lorsque vous appelez setAge, il devrait invoquer le setAge pour la classe dérivée. En tant que tel, vous essayez de définir un âge de (dire) 5 ans sur un objet employé devraient être rejetés dans tous les cas.

0
Jerry Coffin