web-dev-qa-db-fra.com

Comment initialiser des classes avec beaucoup de champs de manière élégante?

Dans mon application, je dois instancier de nombreux types d'objets. Chaque type contient des champs et doit être ajouté à un type contenant. Comment puis-je faire cela d'une manière élégante?

Mon étape d'initialisation actuelle ressemble à ceci:

public void testRequest() {

        //All these below used classes are generated classes from xsd schema file.

        CheckRequest checkRequest = new CheckRequest();

        Offers offers = new Offers();
        Offer offer = new Offer();
        HotelOnly hotelOnly = new HotelOnly();
        Hotel hotel = new Hotel();
        Hotels hotels = new Hotels();
        Touroperator touroperator = new Touroperator();
        Provider provider = new Provider();
        Rooms rooms = new Rooms();
        Room room = new Room();
        PersonAssignments personAssignments = new PersonAssignments();
        PersonAssignment personAssignment = new PersonAssignment(); 
        Persons persons = new Persons();
        Person person = new Person();
        Amounts amounts = new Amounts();

        offers.getOffer().add(offer);
        offer.setHotelOnly(hotelOnly);

        room.setRoomCode("roomcode");
        rooms.getRoom().add(room);

        hotels.getHotel().add(hotel);
        hotel.setRooms(rooms);

        hotelOnly.setHotels(hotels);

        checkRequest.setOffers(offers);

        // ...and so on and so on
    } 

Je veux vraiment éviter d'écrire un code comme celui-ci, car il est un peu compliqué de devoir instancier chaque objet séparément, puis d'initialiser chaque champ sur plusieurs lignes de code (par exemple, avoir à appeler new Offer(), puis setHotelOnly(hotelOnly), puis add(offer)).

Quelles méthodes élégantes puis-je utiliser à la place de ce que j'ai? Existe-t-il des "Factories" pouvant être utilisés? Avez-vous des références/exemples pour éviter d'écrire un code comme celui-ci?

Je suis vraiment intéressé par la mise en œuvre de code propre.


Le contexte:

Je développe une application RestClient pour envoyer des demandes de publication à un Webservice.

L'API est représentée sous la forme d'un fichier xsd schema et j'ai créé tous les objets avec JAXB

Avant d'envoyer une requête, je dois instancier de nombreux objets car ils ont des dépendances entre eux. (Une offre a des hôtels, un hôtel a des chambres, une chambre a des personnes ... Et ces classes sont celles qui sont générées)

Merci de votre aide.

25
Patrick

Vous pouvez utiliser un constructeur ou un modèle de générateur ou une variante du modèle de générateur pour résoudre le problème de trop nombreux champs dans votre étape d'initialisation.

Je vais développer un peu votre exemple pour prouver ce que je pense de la raison pour laquelle ces options sont utiles.

Comprendre votre exemple:

Disons qu'une Offer est simplement une classe conteneur pour 4 champs:

public class Offer {
    private int price;
    private Date dateOfOffer;
    private double duration;
    private HotelOnly hotelOnly;
    // etc. for as many or as few fields as you need

    public int getPrice() {
        return price;
    }

    public Date getDateOfOffer() {
        return dateOfOffer;
    }

    // etc.
}

Comme dans votre exemple, pour définir des valeurs sur ces champs, vous utilisez des paramètres:

    public void setHotelOnly(HotelOnly hotelOnly) {
        this.hotelOnly = hotelOnly;
    }

Malheureusement, cela signifie que si vous avez besoin d'une offre contenant des valeurs dans tous les domaines, vous devez faire ce que vous avez:

Offers offers = new Offers();
Offer offer = new Offer();
offer.setPrice(price);
offer.setDateOfOffer(date);
offer.setDuration(duration);
offer.setHotelOnly(hotelOnly);
offers.add(offer);

Voyons maintenant comment améliorer cela.

Option 1: Constructeurs!

Un constructeur autre que le constructeur par défaut (le constructeur par défaut est actuellement Offer()) est utile pour initialiser les valeurs des champs de votre classe.

Une version de Offer utilisant des constructeurs ressemblerait à ceci:

public class Offer {
    private int price;
    private Date dateOfOffer;
    //etc.

    // CONSTRUCTOR
    public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
        this.price = price;
        this.dateOfOffer = dateOfOffer;
        //etc.
    }

    // Your getters and/or setters
}

Maintenant, nous pouvons l'initialiser en une seule ligne!

Offers offers = new Offers();
Offer offer = new Offer(price, date, duration, hotelOnly);
offers.add(offer);

Mieux encore, si vous n'utilisez jamais offer autre que cette seule ligne: offers.add(offer);, vous n'avez même pas besoin de l'enregistrer dans une variable!

Offers offers = new Offers();
offers.add( new Offer(price, date, duration, hotelOnly) ); // Works the same as above

Option 2: Modèle de générateur

Un modèle de générateur est utile si vous souhaitez avoir la possibilité d'avoir des valeurs par défaut pour n'importe lequel de vos champs.

Le problème qu'un modèle de générateur résout est le code désordonné suivant:

public class Offer {
    private int price;
    private Date dateOfOffer;
    // etc.

    // The original constructor. Sets all the fields to the specified values
    public Offer(int price, Date dateOfOffer, double duration, HotelOnly hotelOnly) {
        this.price = price;
        this.dateOfOffer = dateOfOffer;
        // etc.
    }

    // A constructor that uses default values for all of the fields
    public Offer() {
        // Calls the top constructor with default values
        this(100, new Date("10-13-2015"), 14.5, new HotelOnly());
    }

    // A constructor that uses default values for all of the fields except price
    public Offer(int price) {
        // Calls the top constructor with default values, except price
        this(price, new Date("10-13-2015"), 14.5, new HotelOnly());
    }

    // A constructor that uses default values for all of the fields except Date and HotelOnly
    public Offer(Date date, HotelOnly hotelOnly) {
        this(100, date, 14.5, hotelOnly);
    }

    // A bunch more constructors of different combinations of default and specified values

}

Vous voyez comme ça peut devenir désordonné?

Le modèle de générateur est une autre classe dans laquelle vous insérez dans votre classe.

public class Offer {
    private int price;
    // etc.

    public Offer(int price, ...) {
        // Same from above
    }

    public static class OfferBuilder {
        private int buildPrice = 100;
        private Date buildDate = new Date("10-13-2015");
        // etc. Initialize all these new "build" fields with default values

        public OfferBuilder setPrice(int price) {
            // Overrides the default value
            this.buildPrice = price;

            // Why this is here will become evident later
            return this;
        }

        public OfferBuilder setDateOfOffer(Date date) {
            this.buildDate = date;
            return this;
        }

        // etc. for each field

        public Offer build() {
            // Builds an offer with whatever values are stored
            return new Offer(price, date, duration, hotelOnly);
        }
    }
}

Maintenant, vous ne pouvez pas avoir autant de constructeurs, mais vous pouvez toujours choisir les valeurs que vous souhaitez laisser par défaut et celles que vous souhaitez initialiser.

Offers offers = new Offers();
offers.add(new OfferBuilder().setPrice(20).setHotelOnly(hotelOnly).build());
offers.add(new OfferBuilder().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200).build());
offers.add(new OfferBuilder().build());

Cette dernière offre est simplement une avec toutes les valeurs par défaut. Les autres sont des valeurs par défaut, à l'exception de celles que j'ai définies.

Vous voyez comment cela facilite les choses?

Option 3: Variation du motif de construction

Vous pouvez également utiliser le modèle de générateur simplement en faisant en sorte que vos paramètres actuels renvoient le même objet Offer. C'est exactement la même chose, sauf sans la classe OfferBuilder supplémentaire.

Avertissement: Comme les états WW utilisateur ci-dessous, }, cette option annule JavaBeans - une convention de programmation standard pour les classes de conteneur telles que Offer }. Donc, vous ne devriez pas utiliser ceci à des fins professionnelles, et devriez limiter votre utilisation dans vos propres pratiques.

public class Offer {
    private int price = 100;
    private Date date = new Date("10-13-2015");
    // etc. Initialize with default values

    // Don't make any constructors

    // Have a getter for each field
    public int getPrice() {
        return price;
    }

    // Make your setters return the same object
    public Offer setPrice(int price) {
        // The same structure as in the builder class
        this.price = price;
        return this;
    }

    // etc. for each field

    // No need for OfferBuilder class or build() method
}

Et votre nouveau code d'initialisation est

Offers offers = new Offers();
offers.add(new Offer().setPrice(20).setHotelOnly(hotelOnly));
offers.add(new Offer().setDuration(14.5).setDate(new Date("10-14-2015")).setPrice(200));
offers.add(new Offer());

Cette dernière offre est simplement une avec toutes les valeurs par défaut. Les autres sont des valeurs par défaut, à l'exception de celles que j'ai définies.


Ainsi, même si cela demande beaucoup de travail, si vous souhaitez nettoyer votre étape d'initialisation, vous devez utiliser l'une de ces options pour chacune de vos classes contenant des champs. Ensuite, utilisez les méthodes d’initialisation que j’ai incluses avec chaque méthode.

Bonne chance! Est-ce que cela nécessite une explication supplémentaire?

44
snickers10m

J'ai toujours préféré utiliser builder-pattern-with-a-a-twist car il fournit beaucoup plus que l'approche de base du modèle de générateur.

Mais que se passe-t-il lorsque vous voulez dire à l'utilisateur qu'il doit appeler une méthode de construction ou l'autre, car c'est crucial pour la classe que vous essayez de construire.

Pensez à un générateur pour un composant d'URL. Que penser des méthodes de construction permettant d'encapsuler l'accès aux attributs d'URL, sont-elles d'égale importance, interagissent-elles entre elles, etc.? Bien que les paramètres de requête ou le fragment soient facultatifs, le nom d'hôte ne l'est pas; vous pouvez dire que le protocole est également requis, mais pour cela vous pouvez avoir un défaut significatif, comme http, non? 

Quoi qu’il en soit, je ne sais pas si cela a du sens pour votre problème particulier, mais j’ai pensé qu’il serait intéressant que les autres y jettent un coup d’œil.

10
Filip

Quelques bonnes réponses sont déjà données ici!

Ce qui m’est venu à l’esprit est le suivant: Domain Driven Design . Spécifique les blocs de construction part, avec Entity, Value Object, Aggregate, Factory etc.

Une introduction intéressante est donnée dans Domain Driven Design - Quickly (pdf).

3
Verhagen

Je viens de fournir cette réponse car elle a été mentionnée dans un commentaire et je pense que cela devrait également faire partie de cette énumération de motifs de conception.


Modèle de conception d'objet nul

Intention

Le but d'un objet Null est d'encapsuler l'absence d'un objet en fournissant une alternative substituable qui offre un comportement par défaut ne faisant rien. En bref, un design où "rien ne viendra de rien"

Utilisez le modèle d'objet nul lorsque

  • un objet nécessite un collaborateur. Le modèle Null Object n'introduit pas cette collaboration - il utilise une collaboration déjà existante.
  • certaines instances de collaborateurs ne doivent rien faire
  • vous voulez faire abstraction du traitement de null loin du client

Vous trouverez ici l'intégralité du motif de conception "Objet Null"

1
Patrick

Idéalement, un objet ne devrait pas se préoccuper d'instancier ses dépendances. Il ne devrait s’inquiéter que de ce qu’il est censé faire avec eux… Avez-vous envisagé un cadre d’injection de dépendance? Printemps ou Google Juice sont assez polyvalents et ont une petite empreinte au sol.

L'idée est simple, vous déclarez les dépendances et laissez le cadre décider quand/comment/où les créer et «l'injecter» dans vos classes.

Si vous ne souhaitez utiliser aucun framework, vous pouvez en prendre des notes de conception, essayer de les imiter et les ajuster pour votre cas d'utilisation.

En outre, vous pouvez simplifier les choses dans une certaine mesure en utilisant correctement les collections. Par exemple, quelle fonctionnalité supplémentaire Offers a-t-il autre que le stockage d'une collection de Offer? Je ne sais pas quelles sont vos contraintes, mais si vous pouviez rendre cette partie un peu plus propre, vous auriez des gains énormes à tous les endroits où vous instanciez les objets.

1
Prasoon Joshi

Dozer framework fournit un bon moyen de copier les valeurs de ws object vers votre dto. Voici un autre exemple . De plus, si les noms de getter/setter sont identiques à ceux de la classe, vous n'avez pas besoin d'un convertisseur personnalisé.

0
HRgiger