Question

Je voudrais poser une question sur la manière dont vous aborderiez un problème de conception simple orienté objet. J'ai quelques idées personnelles sur la meilleure façon de traiter ce scénario, mais j'aimerais connaître l'opinion de la communauté Stack Overflow. Les liens vers des articles en ligne pertinents sont également appréciés. J'utilise C #, mais la question n'est pas spécifique à la langue.

Supposons que j'écris une application de stockage vidéo dont la base de données contient une table Person , avec PersonId , Nom , DateOfBirth et Adresse . Il comporte également une table Staff , qui contient un lien vers un PersonId , et une table Client qui contient également des liens vers PersonId .

Une approche orientée objet simple consisterait à dire qu'un client "est un" " Personne et créez donc des classes un peu comme ceci:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer : Person {
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff : Person {
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}

Nous pouvons maintenant écrire une fonction pour envoyer des courriels à tous les clients:

static void SendEmailToCustomers(IEnumerable<Person> everyone) { 
    foreach(Person p in everyone)
        if(p is Customer)
            SendEmail(p);
}

Ce système fonctionne bien jusqu'à ce que nous ayons quelqu'un qui est à la fois un client et un membre du personnel. En supposant que nous ne souhaitons pas vraiment que notre liste tout le monde ait la même personne deux fois, une fois en tant que Client et une fois en tant que Personnel , faisons-nous un choix arbitraire entre:

class StaffCustomer : Customer { ...

et

class StaffCustomer : Staff { ...

Évidemment, seul le premier de ces deux programmes ne casserait pas la fonction SendEmailToCustomers .

Alors que feriez-vous?

  • Attribuez à la classe Person des références facultatives aux classes StaffDetails et CustomerDetails ?
  • Créez une nouvelle classe contenant un Personne , plus éventuellement StaffDetails et CustomerDetails ?
  • Faites de tout une interface (par exemple, IPerson , IStaff , ICustomer ) et créez trois classes mettant en œuvre les interfaces appropriées?
  • Adoptez une autre approche complètement différente?
Était-ce utile?

La solution

Mark, C’est une question intéressante. Vous trouverez autant d'opinions à ce sujet. Je ne crois pas qu'il existe une "bonne" réponse. C’est un bon exemple d’une conception d’objet hiérarchique rigide pouvant réellement poser problème après la construction d’un système.

Par exemple, supposons que vous ayez choisi l'option "Client". et " Personnel " Des classes. Vous déployez votre système et tout est heureux. Quelques semaines plus tard, une personne signale qu’elle est à la fois «membre du personnel» et «client» et qu’elle ne reçoit pas d’e-mails de clients. Dans ce cas, vous devez effectuer beaucoup de modifications de code (reconfigurer, pas re-factoriser).

Je pense que ce serait trop complexe et difficile à maintenir si vous essayez d'avoir un ensemble de classes dérivées qui implémentent toutes les permutations et combinaisons de personnes et leurs rôles. Ceci est d'autant plus vrai que l'exemple ci-dessus est très simple: dans la plupart des applications réelles, les choses seront plus complexes.

Pour votre exemple, je choisirais l'option "Adopter une approche totalement différente". Je mettrais en œuvre la classe Person et j'y inclurais une collection de "rôles". Chaque personne peut avoir un ou plusieurs rôles tels que "Client", "Personnel" et "Vendeur".

Cela facilitera l'ajout de rôles à mesure que de nouvelles exigences sont découvertes. Par exemple, vous pouvez simplement avoir une base " Rôle " classe, et en tirer de nouveaux rôles.

Autres conseils

Vous pouvez envisager d'utiliser les Modèles de fête et de responsabilité

.

De cette façon, Person disposera d'un ensemble de responsabilités pouvant être de type Client ou Personnel.

Le modèle sera également plus simple si vous ajoutez ultérieurement plus de types de relations.

L’approche pure consisterait à: faire de tout une interface. En tant que détails d'implémentation, vous pouvez éventuellement utiliser diverses formes de composition ou d'héritage d'implémentation. Comme il s’agit de détails d’implémentation, votre API publique n’a aucune importance, vous êtes donc libre de choisir celle qui vous simplifie la vie.

Une personne est un être humain, alors qu'un client n'est qu'un rôle qu'une personne peut adopter de temps à autre. Homme et femme seraient candidats à l'héritage de Person, mais le client est un concept différent.

Selon le principe de substitution de Liskov, nous devons pouvoir utiliser des classes dérivées dans lesquelles nous avons des références à une classe de base, sans le savoir. Avoir le client hérité de la personne violerait ceci. Un client pourrait peut-être aussi être un rôle joué par une organisation.

Faites-moi savoir si j'ai bien compris la réponse de Foredecker. Voici mon code (en Python; désolé, je ne connais pas C #). La seule différence est que je ne notifierais pas quelque chose si une personne "est un client", je le ferais si l’un de ses rôles "est intéressé par" cette chose. Est-ce suffisamment flexible?

# --------- PERSON ----------------

class Person:
    def __init__(self, personId, name, dateOfBirth, address):
        self.personId = personId
        self.name = name
        self.dateOfBirth = dateOfBirth
        self.address = address
        self.roles = []

    def addRole(self, role):
        self.roles.append(role)

    def interestedIn(self, subject):
        for role in self.roles:
            if role.interestedIn(subject):
                return True
        return False

    def sendEmail(self, email):
        # send the email
        print "Sent email to", self.name

# --------- ROLE ----------------

NEW_DVDS = 1
NEW_SCHEDULE = 2

class Role:
    def __init__(self):
        self.interests = []

    def interestedIn(self, subject):
        return subject in self.interests

class CustomerRole(Role):
    def __init__(self, customerId, joinedDate):
        self.customerId = customerId
        self.joinedDate = joinedDate
        self.interests.append(NEW_DVDS)

class StaffRole(Role):
    def __init__(self, staffId, jobTitle):
        self.staffId = staffId
        self.jobTitle = jobTitle
        self.interests.append(NEW_SCHEDULE)

# --------- NOTIFY STUFF ----------------

def notifyNewDVDs(emailWithTitles):
    for person in persons:
        if person.interestedIn(NEW_DVDS):
            person.sendEmail(emailWithTitles)

J'éviterais le " est " check ("instance of" en Java). Une solution consiste à utiliser un motif de décorateur . Vous pouvez créer un EmailablePerson qui décore une personne lorsque EmailablePerson utilise la composition pour contenir une instance privée d'une personne et déléguer toutes les méthodes autres que la messagerie à l'objet Person.

Nous avons étudié ce problème au collège l'année dernière, nous apprenions eiffel et nous avons donc utilisé l'héritage multiple. Quoi qu’il en soit, l’alternative aux rôles de Foredecker semble être suffisamment souple.

Qu'est-ce qui ne va pas dans l'envoi d'un courrier électronique à un client membre du personnel? S'il est un client, alors l'e-mail peut lui être envoyé. Ai-je tort de le penser? Et pourquoi devriez-vous prendre "tout le monde"? comme votre liste de courriel? Il serait préférable d’avoir une liste de clients, car nous traitons avec "sendEmailToCustomer". méthode et non "sendEmailToEveryone". méthode? Même si vous voulez utiliser "tout le monde" vous ne pouvez pas autoriser les doublons dans cette liste.

Si rien de tout cela n’est réalisable avec beaucoup de redécoupage, j’irai avec la première réponse de Foredecker et vous devrez peut-être attribuer des rôles à chaque personne.

Vos classes ne sont que des structures de données: aucune d’elles n’a de comportement, mais seulement des accesseurs et des passeurs. L'héritage est inapproprié ici.

Adoptez une approche complètement différente: le problème avec la classe StaffCustomer est que votre membre du personnel peut commencer en tant que membre du personnel et devenir client plus tard. Vous devrez donc les supprimer en tant que personnel et créer une nouvelle instance de la classe StaffCustomer. . Peut-être qu'un simple booléen dans la classe Staff de 'isCustomer' permettrait à notre liste de tout le monde (supposée compiler à partir de tous les clients et du personnel des tables appropriées) de ne pas obtenir le membre du personnel car il saura qu'il a déjà été inclus en tant que client.

Voici quelques conseils supplémentaires: Dans la catégorie «ne pensez même pas faire cela», voici quelques exemples de code rencontrés:

La méthode du Finder retourne Object

Problème: en fonction du nombre d'occurrences trouvées, la méthode de recherche renvoie un nombre représentant le nombre d'occurrences - ou! Si un seul trouvé renvoie l'objet réel.

Ne faites pas ça! C’est l’une des pires pratiques en matière de codage. Elle crée une ambiguïté et perturbe le code de manière à ce que lorsqu’un développeur différent entre en jeu, il ou elle vous hais pour cela.

Solution: Si 2 fonctionnalités sont nécessaires: le comptage et la récupération d’une instance créent 2 méthodes, l'une renvoyant le nombre et l'autre, mais jamais une seule méthode effectuant les deux démarches.

Problème: une mauvaise pratique dérivée est quand une méthode de recherche renvoie soit la seule occurrence trouvée, soit un tableau d'occurrences s'il y en a plus d'une. Ce style de programmation paresseux est fait beaucoup par les programmeurs qui font le précédent en général.

Solution: Si je dispose de cette information, je retournerais un tableau de longueur 1 (un) si une seule occurrence est trouvée et un tableau de longueur> 1 si plusieurs occurrences sont trouvées. De plus, le fait de ne pas trouver d'occurrence renvoie null ou un tableau de longueur 0 selon l'application.

Programmation sur une interface et utilisation de types de retour covariants

Problème: programmation sur une interface, utilisation de types de retour covariants et transposition dans le code appelant.

Solution: utilisez à la place le même supertype que celui défini dans l'interface pour définir la variable devant pointer vers la valeur renvoyée. Ceci garde la programmation à une approche d’interface et votre code propre.

Les classes de plus de 1000 lignes sont un danger permanent Les méthodes comportant plus de 100 lignes constituent également un danger!

Problème: certains développeurs insèrent trop de fonctionnalités dans une classe / méthode, trop paresseux pour les casser - cela conduit à une faible cohésion et peut-être à un couplage élevé - l’inverse d’un principe très important en POO! Solution: évitez d’utiliser trop de classes internes / imbriquées - ces classes doivent être utilisées UNIQUEMENT selon les besoins, vous n’avez pas à prendre l’habitude de les utiliser! Leur utilisation pourrait entraîner plus de problèmes, tels que la limitation de l'héritage. Lookout pour le code en double! Le code identique ou trop similaire pourrait déjà exister dans une implémentation de supertype ou peut-être dans une autre classe. S'il s'agit d'une autre classe qui n'est pas un supertype, vous avez également enfreint la règle de cohésion. Méfiez-vous des méthodes statiques - vous avez peut-être besoin d'une classe d'utilitaires à ajouter!
Plus à: http://centraladvisor.com/it/oop-what -sont-les-meilleures-pratiques-en-oop

Vous ne voulez probablement pas utiliser l'héritage pour cela. Essayez ceci à la place:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer{
    public Person PersonInfo;
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff {
    public Person PersonInfo;
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top