web-dev-qa-db-fra.com

La définition d'une classe vide pour représenter une entité de domaine est-elle nécessairement un signe de mauvaise conception de l'architecture?

Je suis actuellement confronté au scénario suivant:

  • Lors de la définition des exigences de mon système sur une feuille de papier, j'ai décrit deux entités différentes A et B (par exemple Cat et Dog)
  • Après avoir répertorié ce que A et B devraient faire, ils ont fini par avoir exactement les mêmes exigences de comportement (par exemple, le comportement d'un Animal).
  • Pour refléter cela, j'ai implémenté A et B en tant que classes et toutes les deux héritant de C (par exemple la classe abstraite Animal).

Est-ce que A et B doivent être conservés en tant que classes ou doivent-ils être supprimés de l'architecture, ne conservant que C? Est-ce le signe d'un problème dans l'architecture de cette solution?


Exemple:

Supposons que j'implémente un système d'animalerie et que j'ai les exigences suivantes:

  • Mon animalerie peut soigner des chats et des chiens
  • Lorsqu'un chat est soigné, le nombre de fourrures mortes doit être nul
  • Lorsqu'un chien est soigné, le nombre de fourrures mortes doit être nul

Pour résoudre ce problème, j'implémente les tests suivants:

[Test]
public void When_DogIsGroomed_Should_DeadFurCountBeZero()
{
    PetShop petShop = new PetShop();
    Dog dog = new Dog();
    petShop.Groom(dog);
    Assert.AreEqual(0, dog.DeadFurCount);
}

[Test]
public void When_CatIsGroomed_Should_DeadFurCountBeZero()
{
    PetShop petShop = new PetShop();
    Cat cat = new Cat();
    petShop.Groom(cat);
    Assert.AreEqual(0, cat.DeadFurCount);
}

Avec le code d'implémentation suivant:

public abstract class Animal()
{
    public int DeadFurCount { get; private set; }

    internal void Groom()
    {
        this.DeadFurCount = 0;
    }
}
public class PetShop()
{
    public void Groom(Animal animal)
    {
        animal.Groom();
    }
}
public class Dog(): Animal
{

}
public class Cat(): Animal
{

}

Dans cet exemple, faut-il supprimer Cat et Dog, donc ne conserver que Animal pour l'architecture finale de la solution? Ces classes vides sont-elles un mauvais signe ou une mauvaise conception? ?

Une chose que je crains de supprimer est que mes tests seraient moins représentatifs du système que j'implémente. Après tout, mes exigences parlent de chiens et de chats, pas d'animaux. Mais en même temps, les classes vides sont généralement une odeur.

4
Albuquerque

Après tout, mes exigences parlent de chiens et de chats, pas d'animaux. Mais en même temps, les classes vides sont généralement une odeur.

Discutez avec vos experts du domaine de la raison pour laquelle ils utilisent spécifiquement les mots "chien" et "chat" au lieu d'un mot plus générique comme "animal" ou "animal". Je peux penser à quelques résultats possibles:

  • Les chiens et les chats sont traités exactement de la même manière dans votre domaine: Simplifiez votre vocabulaire et utilisez un seul terme (et donc une seule classe).
  • Certaines règles commerciales dépendent du type d'animal: Utilisez une seule classe et modélisez le type d'animal comme une énumération. L'héritage n'est pas la meilleure solution ici car il n'est probablement pas de la responsabilité de Animal de répondre à toutes les règles commerciales actuelles et futures.
  • Il y a quelque chose de fondamentalement différent dans la structure des chiens et des chats: Allez-y et modélisez-les comme deux entités distinctes (éventuellement avec une superclasse commune si vous avez besoin d'une structure partagée).
14
casablanca

Non, les cours vides ne sont pas une odeur. C'est un signe que vous connaissez la valeur d'un bon nom.

C'est exactement ce que ces classes vides ont pour eux. Ce sont de bons noms. Je crée des classes vides pour donner des exceptions aux bons noms tout le temps.

Chien et chat pourraient un jour avoir une logique de toilettage différente, mais pour l'instant ils ne le font pas. C'est bon. Il serait également bon de pousser cette logique vers le bas si vous sentiez que le toilettage était plus susceptible de changer de manière indépendante. Ce n'est pas parce que c'est la même chose en ce moment que vous devez le faire au même endroit. Il est correct d'avoir à la fois x et y même lorsqu'ils sont tous deux définis sur 1.

Là où vous aurez des ennuis, c'est si vous commencez à imaginer d'autres animaux qui pourraient un jour être soignés et à les ajouter `` au cas où ''. Cela vous fera battre du bâton YAGNI.

7
candied_orange

Oui, c'est une odeur de code.

Si vous avez des classes vides sans logique, cela suggère que quelque part dans votre code, vous avez une logique conditionnelle qui vérifie quelle classe est une instance.

par exemple.

if(x is Dog) then y

Cela peut également suggérer que vous avez un nombre toujours croissant de classes vides, chat, chien, vache, mouton, etc., etc.

Ce genre de choses devrait normalement être remplacé par une propriété enum ou string "Type" sur l'objet parent.

Bien sûr, je dois ajouter la mise en garde habituelle. "odeur de code"! = mauvais code. C'est juste quelque chose qui est souvent vu en conjonction avec un mauvais code.

Cela me suggère également que vous appliquez une approche OOP aux règles métier, ce qui n'est pas toujours une bonne idée.

Votre exemple d'entreprise de toilettage pour animaux de compagnie ne devrait pas avoir d'objets pour chiens et chats avec une méthode de toilettage. Je m'attendrais à ce qu'il ait une classe Service sans méthodes.

Service
{
    Id;
    CustomerId
    Type;
    Price;
    Date;
    Status;
}

Un client réserverait un type de service = "Dog SuperGroom" et vous auriez des cours tels que LateServiceEmailer où votre OOP entrerait en vigueur.

Aucune entreprise ne veut entendre "Oh, nous ne pouvons pas faire ce changement parce qu'un Fish n'est pas un animal.

2
Ewan

Il y a une perspective intéressante inhérente au code source (la forme lisible par l'homme, bien sûr), une que vous devez garder à l'esprit!

C'est l'abstraction d'un processus mental! Votre processus mental. Le processus mental qui tente de construire une représentation conceptuelle, votre représentation conceptuelle. Votre interprétation d'un système, d'une partie du monde (ceci, ou un monde imaginaire).

Une classe vide avec un nom n'est pas nécessairement un signe de mauvaise architecture. Cela ressemble plus à un "sceau" de votre architecture. C'est ce que vous a vu en regardant votre domaine.

Pourquoi toutes les voitures ne sont-elles pas blanches dans le monde réel? Ou noir ... ou beige? Avoir une propriété (c'est-à-dire la couleur) pour un objet, alors que cela ne fait absolument aucune différence pour la fonctionnalité/l'utilisation la plupart du temps semble assez redondant. Mais les clients sont souvent prêts à en faire même des histoires, à en être fiers, à en profiter (OK, pas toujours, bien sûr, mais vous comprenez mon point de vue). Cela améliore leur vie, sans ajouter vraie valeur d'ingénierie (encore une fois, dans la plupart cas). Garder une propriété Color supplémentaire dans une base de données de voiture est juste une autre douleur pour le développeur, cela augmente la taille d'une base de données, ça va et vient, cela n'introduit aucune validation supplémentaire, etc.

Lors de l'écriture de code, vous devez garder à l'esprit la distinction entre ce qui a implications de conception et ce qui est simplement là pour "l'exhaustivité".

Oh, et autre chose. Vous dites:

Dans cet exemple, faut-il supprimer Cat and Dog, ne restant ainsi qu'avec Animal pour l'architecture finale de la solution? Ces classes vides sont-elles un mauvais signe ou une mauvaise conception?

Oui. Rappelez-vous, c'est toujours aujourd'hui! Ne prenez jamais l'architecture d'aujourd'hui pour la finale! Vous devez vous demander quel est le coût de l'introduction de ces classes en quelques mois, en particulier après avoir construit une base de code relativement étendue, réparti les dépendances et potentiellement passé par de nombreuses hypothèses? Que se passe-t-il si les exigences changent, comme elles le font souvent, sans parler de de manière "cassante"?

Donc, vous pouvez y garder les classes aujourd'hui, pour être suffisamment flexible pour s'adapter aux changements demain. Prévoir le changement et s'y préparer est l'une des choses qui font un bon développeur. Et n'oubliez pas, bien sûr, qu'il existe un art dans le maintien d'un bon équilibre entre la suringénierie et la sous-ingénierie.

0
Vector Zita

Prenez un exemple différent pour illustrer le point avec une réponse plus simple.

J'ai 2 mesures - HeightFromGroundInMeters et NumberOfPotatoes. Ils sont tous les deux des entiers comportementaux (disons), mais sémantiquement ils sont totalement indépendants et certainement pas interchangeables logiquement.

Je dirais que c'était un exemple de classes qui existent utilement (dans le sens où il est assez clair qu'elles représentent clairement deux choses différentes et que l'équivalence logique est probablement fausse) mais elles ont toutes les deux les mêmes propriétés comportementales.

TLDR: Parfaitement légitime.

0
LoztInSpace