Quelle conception OO utiliser (existe-t-il un modèle de conception)?

11

J'ai deux objets qui représentent un «Bar / Club» (un endroit où vous buvez / socialisez).

Dans un scénario, j'ai besoin du nom de la barre, de l'adresse, de la distance, du slogon

Dans un autre scénario, j'ai besoin du nom de la barre, de l'adresse, de l'URL du site Web, du logo

J'ai donc deux objets représentant la même chose mais avec des champs différents.

J'aime utiliser des objets immuables, donc tous les champs sont définis à partir du constructeur .

Une option consiste à avoir deux constructeurs et à annuler les autres champs, à savoir:

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

Je n'aime pas ça car il faudrait vérifier la valeur nulle lorsque vous utilisez les getters

Dans mon exemple réel, le premier scénario a 3 champs et le deuxième scénario a environ 10, donc ce serait vraiment difficile d'avoir deux constructeurs , la quantité de champs que je devrais déclarer nulle et puis quand l'objet est en cours d'utilisation, vous ne le feriez pas. BarJe ne sais pas où vous utilisez et quels champs seraient nuls et lesquels ne le seraient pas.

Quelles autres options ai-je?

Deux classes appelées BarPreviewet Bar?

Un type d'héritage / interface?

Quelque chose d'autre qui est génial?

Blundell
la source
29
Wow, vous avez en fait trouvé une utilisation légitime de Barcomme identifiant!
Mason Wheeler
1
si vous partagez certaines propriétés, une option serait d'implémenter une classe de base.
Yusubov
1
Je n'ai jamais pensé à ça. Écrire n'importe quel type de code pour mon salon de bar Bar / Foo pourrait devenir très déroutant.
Erik Reppen
4
@gnat Comment les gens pensent-ils? De votre citation de lien: You should only ask practical, answerable questions based on actual problems that you face.et c'est exactement ce qui se passe ici
Blundell

Réponses:

9

Mes pensées:

Un "Bar", tel que représenté dans votre domaine, a toutes les choses qui peuvent être nécessaires dans l'un ou l'autre endroit: nom, adresse, URL, logo, slogan et "distance" (je suppose de l'emplacement du demandeur). Par conséquent, dans votre domaine, il doit y avoir une classe "Bar" qui est la source de données faisant autorité pour une barre, peu importe où les données seront utilisées ultérieurement. Cette classe doit être modifiable, afin que les modifications des données de la barre puissent être apportées et enregistrées si nécessaire.

Cependant, vous disposez de deux emplacements dans lesquels les données de cet objet Bar sont nécessaires, et les deux n'ont besoin que d'un sous-ensemble (et vous ne voulez pas que ces données soient modifiées). La réponse habituelle est un "objet de transfert de données" ou DTO; un POJO (simple objet Java) contenant les getters de propriété immuables. Ces DTO peuvent être produits en appelant une méthode sur l'objet de domaine Bar principal: "toScenario1DTO ()" et "toScenario2DTO ()"; les résultats étant un DTO hydraté (ce qui signifie que vous n'avez besoin d'utiliser que le constructeur long et compliqué en un seul endroit).

Si vous avez déjà eu besoin de renvoyer des données à la classe de domaine principale (pour les mettre à jour; quel est l'intérêt des données si vous ne pouvez pas les modifier selon les besoins pour refléter l'état actuel du monde réel?), Vous pouvez construire l'un des DTO, ou utilisez un nouveau DTO mutable et remettez-le à la classe Bar à l'aide d'une méthode "updateFromDto ()".

EDIT: pour donner un exemple:

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

Les classes Address, Distance et Url doivent être elles-mêmes immuables ou, lorsqu'elles sont utilisées dans les DTO, elles doivent être remplacées par des homologues immuables.

KeithS
la source
que signifie l'acronyme DTO? Je ne comprends pas très bien ce que vous dites, pourriez-vous le mettre en exemple, s'il vous plaît fyi Les données proviennent d'un serveur, donc une fois que l'une ou l'autre forme de cette classe est «hydratée», les champs n'auront pas besoin d'être modifiés, juste utilisés pour s'afficher sur l'interface utilisateur
Blundell
1
DTO signifie "data transfer object" et fait référence à une classe de données de structure très simple utilisée pour déplacer des données de la couche de domaine "riche" vers des couches supérieures comme l'interface utilisateur, sans exposer la couche de domaine réelle à l'interface utilisateur (permettant des modifications à apporter au domaine sans affecter l'interface utilisateur tant que le DTO n'a pas à changer).
KeithS
la mutabilité n'a rien à voir avec la modification ou la persistance.
4
@JarrodRoberson - vous plaisantez? Si une classe est immuable (ne peut pas être modifiée sur place après l'instanciation), la seule façon d'apporter une modification aux données dans la couche de données est de construire une nouvelle instance représentant le même enregistrement (même PK) avec différents membres. Bien que les méthodes de "mutation" qui produisent une nouvelle instance puissent la rendre plus facile, elles ont toujours une énorme influence sur la modification et la persistance.
KeithS
1
@JarrodRoberson Écoutez la communauté. Vous avez tort. . En fait, la moitié des commentaires dans toute cette réponse montrent que nous avons besoin d'une formation de base en OO autour du tableau - c'est dégoûtant ..
David Cowden
5

Si vous ne vous souciez que d'un sous-ensemble des propriétés et que vous voulez vous assurer qu'elles ne se mélangent pas, créez deux interfaces et utilisez-les pour parler à votre objet de base.

jmoreno
la source
1
Vous dites cela, mais pourriez-vous donner un exemple en utilisant la Barclasse
Blundell
3

Le modèle de générateur (ou quelque chose de proche) pourrait être utile ici.

Avoir des objets immuables est une chose admirable, mais la réalité est qu'avec Reflection en Java, rien n'est vraiment sûr ;-).

Martijn Verburg
la source
Je peux voir comment cela HawaiianPizzaBuilderfonctionne parce que les valeurs dont il a besoin sont codées en dur. Cependant, comment pouvez-vous utiliser ce modèle si les valeurs sont récupérées et transmises à un constructeur? Le HawaiianPizzaBuilderaurait encore tous les getters que l' SpicyPizzaBuildera si nul est possible. Sauf si vous combinez cela avec @Jarrods Null Object Pattern. Un exemple de code avec Barferait passer votre message
Blundell
+1 J'utilise le constructeur dans des cas comme celui-ci, fonctionne comme un charme - y compris, mais sans s'y limiter, la définition de valeurs par défaut raisonnables au lieu de null lorsque je le souhaite
moucher
3

Le point clé ici est la différence entre ce qu'est un "Bar" et comment vous l'utilisez dans l'un ou l'autre contexte.

Le Bar est une entité unique dans le monde réel (ou un monde artificiel, comme un jeu), et une seule instance d'objet devrait le représenter. À tout moment plus tard, lorsque vous ne créerez pas cette instance à partir d'un segment de code, mais la chargerez à partir d'un fichier de configuration ou d'une base de données, cela sera plus évident.

(Pour être encore plus ésotérique: chaque instance Bar a un cycle de vie différent de l'objet qui la représente lorsque votre programme s'exécute. Même si vous avez un code source qui crée cette instance, cela signifie que l'entité Bar telle qu'elle est décrite, "existe "dans un état dormant dans votre code source, et" réveiller "lorsque ce code le crée réellement dans la mémoire ...)

Désolé pour le long début, mais j'espère que cela clarifie mon propos. Vous avez UNE classe Bar ayant tous les attributs dont vous auriez besoin et une instance Bar représentant chaque entité Bar. Ceci est correct dans votre code et indépendant de la façon dont vous souhaitez voir la même instance dans différents contextes .

Ce dernier peut être représenté par deux interfaces différentes , qui contiennent les méthodes d'accès requises (getName (), getURL (), getDistance ()), et la classe Bar doit implémenter les deux. (Et peut-être que la "distance" changera en "emplacement", et le getDistance () devient un calcul à partir d'un autre emplacement :-))

Mais la création est pour l'entité Bar et non pour la façon dont vous souhaitez utiliser cette entité: un constructeur, tous les champs.

EDITÉ: je peux écrire du code! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}
Lorand Kedves
la source
1

Schéma approprié

Ce que vous recherchez est le plus souvent appelé le Null Object Pattern. Si vous n'aimez pas le nom, vous pouvez l'appeler la Undefined Value Patternmême étiquette sémantique différente. Parfois, ce modèle est appelé Poison Pill Pattern.

Dans tous ces cas, l'objet est un remplacement ou se substituer à un Default Valuelieu de null. It doesn't replace the semantic ofnulle but makes it easier to work with the data model in a more predictable way becausenull` devrait maintenant jamais être un état valide.

Il s'agit d'un modèle dans lequel vous réservez une instance spéciale d'une classe donnée pour représenter une autre nulloption en tant que a Default Value. De cette façon, vous n'avez pas à vérifier null, vous pouvez vérifier l'identité par rapport à votre NullObjectinstance connue . Vous pouvez appeler en toute sécurité des méthodes dessus et similaires sans vous en soucier NullPointerExceptions.

De cette façon, vous remplacez vos nullaffectations par leurs NullObjectinstances représentatives et vous avez terminé.

Analyse orientée objet appropriée

De cette façon, vous pouvez avoir un commun Interfacepour le polymorphisme et toujours avoir une protection contre le fait de devoir vous soucier de l'absence de données dans les implémentations spécifiques de l'interface. Ainsi, certains Barpeuvent ne pas avoir de présence sur le Web et certains peuvent ne pas avoir de données de localisation au moment de la construction. Null Object Pattervous permet de fournir une valeur par défaut pour chacun d'entre eux, c'est-à-dire markerpour ces données qui disent la même chose, rien n'a été fourni ici, sans avoir à gérer la vérification NullPointerExceptionpartout.

Conception orientée objet appropriée

Ayez d'abord une abstractimplémentation qui est un super ensemble de tous les attributs qui partagent Baret Clubpartagent.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

Ensuite, vous pouvez implémenter des sous-classes de cette Establishmentclasse et ajouter uniquement les éléments spécifiques dont vous avez besoin pour chacune des classes Baret Clubqui ne s'appliquent pas à l'autre.

Persistance

Ces objets d'espace réservé, s'ils sont construits correctement, peuvent également être stockés de manière transparente dans une base de données sans aucune manipulation particulière.

Future Proof

Si vous avez décidé de sauter plus tard dans le train d'inversion de contrôle / injection de dépendance, ce modèle facilite également l'injection de ces objets marqueurs.


la source
0

Je pense que le problème est que vous ne modélisez pas une barre dans aucun de ces scénarios (et vous modélisez deux problèmes différents, des objets, etc.). Si je vois un bar de classe, je m'attendrais à certaines fonctionnalités liées aux boissons, aux menus, aux sièges disponibles, et votre objet n'a rien de tout cela. Si je vois le comportement de vos objets, vous modélisez des informations sur un établissement. La barre est ce pour quoi vous les utilisez en ce moment, mais ce n'est pas le comportement intrinsèque qu'ils implémentent. (Dans un autre contexte: si vous modélisez un mariage, vous aurez deux variables d'instance Personne épouse; Personne mari; une femme est le rôle actuel que vous donnez à cet objet à ce moment, mais l'objet est toujours une personne). Je ferais quelque chose comme ça:

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}
user1494736
la source
-1

Il n'y a vraiment pas besoin de trop compliquer cela. Vous avez besoin de deux types d'objets différents? Faites deux classes.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

Si vous devez les exploiter également, envisagez d'ajouter une interface ou d'utiliser la réflexion.

DeadMG
la source
@David: Oh non. Ils ont comme, un membre de données entier en commun. URGENCE!
DeadMG
sans une interface commune, il n'y a pas de polymorphisme dans cette solution, aucune de ces classes ne pourrait être remplacée par l'autre avec cette mauvaise décision de conception. Ce n'est pas qu'ils n'ont pas le même surensemble d'attributs, c'est que par défaut certains de ces attributs le sont null. Rappelez-vous nullsignifie l' absence de données , pas l'absence de l'attribut.
1
@DeadMG Il s'agit très probablement d'un exercice qui consiste à regrouper exactement les valeurs partagées en objets parents. Votre solution ne serait pas pleinement valorisée si vous la proposiez dans ce contexte.
David Cowden
L'OP ne précise aucun besoin de substituabilité. Et comme je l'ai dit, vous pouvez simplement ajouter une interface ou utiliser la réflexion si vous le souhaitez.
DeadMG
@DeadMG Mais la réflexion est comme essayer d'écrire une application mobile multiplateforme en utilisant un environnement - Cela peut fonctionner, mais ce n'est pas correct . La pénalité pour appeler une méthode en utilisant la réflexion est entre 2 et 50 fois plus lente qu'un appel de méthode normale. La réflexion n'est pas une panacée.
David Cowden
-1

Ma réponse à toute personne ayant ce type de problème est de la décomposer en étapes gérables .

  1. Créez d'abord deux classes BarOneet BarTwo(ou appelez-les toutes les deux Barmais dans des packages différents)
  2. Commencez à utiliser vos objets en tant que classes séparées ne vous inquiétez pas de la duplication de code pour l'instant. Vous remarquerez lorsque vous passerez (méthodes en double) de l'une à l'autre
  3. Vous pouvez découvrir qu'ils ne sont en aucun cas liés, et vous devriez donc vous demander s'ils sont vraiment tous les deuxbar sinon renommer la classe incriminée en ce qu'elle représente maintenant
  4. Si vous trouvez des champs ou un comportement communs, vous pouvez ensuite extraire un interfaceou superclassavec le comportement commun
  5. Une fois que vous avez un interfaceou superclassvous pouvez créer un builderou factorypour créer / récupérer vos objets d'implémentation

(4 et 5 correspondent aux autres réponses à cette question)

Blundell
la source
-2

Vous avez besoin d'une classe de base, par exemple, Location qui a un nom et une adresse . Maintenant, vous avez deux classes Bar et BarPreview étendent l' emplacement de la classe de base . Dans chaque classe, vous initialisez les variables communes de la super classe, puis vos variables uniques:

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

Et similaire pour la classe BarPreview ..

Si cela vous fait mieux dormir la nuit, remplacez toutes les instances de Location dans mon code par AnythingYouThinkWouldBeAnAppropriateNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.

David Cowden
la source
l'OP veut que l'instance soit immuable , cela signifie que tout doit être final.
1
Cela pourrait fonctionner, vous avez besoin finaldes champs @David. Barne devrait pas extend Locationpenser que cela n'a pas de sens. Peut Bar extends BaseBar- être et BarPreview extends BaseBarces noms ne sonnent pas vraiment trop bien non plus, j'espérais quelque chose de plus élégant
Blundell
@JarrodRoberson Je suis juste en train de le dessiner pour lui .. il suffit d'ajouter la finale pour qu'elle soit immuable. C'est une évidence. Le problème fondamental avec la question du PO est qu'il ne sait pas comment avoir une classe de base et deux classes distinctes qui étendent une classe de base. Je détaille simplement cela.
David Cowden
@Blundell de quoi parles-tu dans le monde? Un bar est un lieu . Remplacez simplement mon emplacement par votre BaseBar et c'est exactement la même chose. Vous savez que lorsqu'une classe en étend une autre, la classe qu'elle étend n'a pas besoin d'être nommée Base [ClassThatWillExtend], n'est-ce pas?
David Cowden
1
@DavidCowden, pouvez-vous arrêter d'annoncer votre réponse sous forme de commentaire sous toutes les autres réponses, s'il vous plaît?
marktani