3 votes

Motif de conception Factory abstrait @Service dans SpringBoot

Je me demande comment utiliser la classe d'implémentation de l'usine abstraite en tant que @Service dans mon application. J'ai plusieurs fournisseurs de ma logique souhaitée et je les ai injectés à travers le constructeur. Le problème est que je ne suis pas sûr que mon usine devrait avoir le constructeur et s'il le devrait, devrait-il être privé.

J'ai annoté cette classe en tant que @Service mais je me demandais si tout cela est bon et quelles sont les meilleures pratiques. Merci et désolé si ce qui précède est un désordre, c'est en fait ma première question et publication.

Voici un extrait de mon code :

    private final FirstClient first;
    private final SecondClient second;

    private SentimentFactoryImpl(FirstClient first, SecondClient second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public SentimentClient get(String clientName) {
        if ("First".equalsIgnoreCase(clientName)) {
            return this.first;
        } else if ("Second".equalsIgnoreCase(clientName)) {
            return this.second;
        } 
        throw new UnknownClientException("Le service ne prend pas en charge le client : " + clientName);
    }

3voto

Alexey Usharovski Points 1256

En fait, le cœur du framework Spring est une sorte de fabrique abstraite super flexible et puissante. Si vous utilisez Spring, vous devriez déléguer toute fonctionnalité de création de classe au cœur de Spring. Dans votre cas, je préfère utiliser les Configurations java de Spring pour la création de classe. L'équivalent de votre fabrique en Spring ressemblera à ceci.

@Configuration
public class ServiceConfig {

    @Bean
    public SentimentClient firstClient() {
        return new FirstClient();
    }

    @Bean
    public SentimentClient secondClient() {
        return new SecondClient();
    }
}

Après que cette classe a été reconnue par le cœur de Spring, celui-ci l'utilisera comme une fabrique pour obtenir l'instance du service à chaque fois qu'un autre bean Spring en aura besoin. Vous n'avez pas besoin d'appeler une sorte de méthode get comme dans votre fabrique. Spring le fait automatiquement.

@Service
public class SomeService {

    private final SentimentClient firstClient;

    @Autowired
    private SomeService(SentimentClient firstClient) {
        this.firstClient = firstClient;
    }

    // Une logique métier ici 
}

Il est bon de mentionner que le nom de la méthode de fabrique dans la configuration Spring est le nom du bean (service) créé par cette méthode, et ce nom avec le type de classe est utilisé pour trouver le bean nécessaire pour l'Autowiring.

1voto

Vinay Prajapati Points 3092

Vous devriez envisager de suivre les pratiques suivantes pour appliquer les meilleures pratiques :

  1. Ne pas coupler étroitement les champs d'objet, mais les injecter. Cela permettra de changer la classe de champ d'objet selon les besoins.

  2. Pour remplir le 1er point, travailler toujours par contrat c'est-à-dire utiliser des interfaces.

  3. Décider si vous voulez des singletons ou un nouvel objet à chaque appel de factory.

  4. Maintenabilité de l'usine abstraite.

Dans votre cas, vous avez déjà adhéré au #2. Pour le reste, a) créer des beans pour FirstClient et SecondClient.

b) Si ceux-ci doivent être des singletons comme le laisse entendre le mot-clé final mais ce n'est pas évident par la classe de factory, rendre les deux classes client singleton.

c) Au lieu d'avoir un clientName de type chaîne, créer une énumération car c'est plus maintenable et sécurisé car vous recherchez toujours des valeurs bien définies.

d) Se débarrasser des if-else et plutôt utiliser une Map qui est remplie après la construction de votre factory. Cela accélérera l'obtention des beans.

e) Vous pouvez également penser à l'initialisation paresseuse :).

classe SentimentFactoryImpl{
    private Map factoryMap; 
    @Autowired
    private final FirstClient first;
    @Autowired
    private final SecondClient second;

    @Override
    public SentimentClient get(String clientName) {
       Client Client.getByName(clientName);
     if(!factoryMap.contains(client)){
         throw new UnknownClientException("Le service ne prend pas en charge le client : " 
         + clientName);
      }
       return factoryMap.get(Clients.getByName(clientName));
    }

1voto

Bustanil Arifin Points 31

Je sais que vous avez accepté une réponse mais je voulais donner mon propre avis. La première chose est que je ne pense pas que vous vouliez utiliser le design pattern Abstract Factory, il semble que vous ne créez pas les objets mais que vous voulez simplement localiser des objets clients en fonction de leur nom. C'est le travail du Locator, appelons-le ClientLocator (faute de mieux).

L'idée est de créer une classe appelée ClientLocator qui implémente ApplicationContextAware. En implémentant cette interface, Spring injectera un objet ApplicationContext via une méthode setter. En utilisant l'objet ApplicationContext, nous pouvons rechercher un bean par son nom, puis nous pouvons vérifier le type du bean pour nous assurer que nous obtenons le bon, et déclencher une erreur lorsque ce n'est pas le cas.

Voici à quoi ressemble le code.

Clients

// SentimentClient.java
public interface SentimentClient {

    void call();

}

// FirstClient.java
@Service
public class FirstClient implements SentimentClient {

    @Override
    public void call() {
        System.out.println("Bonjour du premier client");
    }
}

// SecondClient.java
@Service
public class SecondClient implements SentimentClient {

    @Override
    public void call() {
        System.out.println("Bonjour du deuxième client");
    }
}

ClientLocator.java

@Component
public class ClientLocator implements ApplicationContextAware {

    private ApplicationContext ctx;

    public SentimentClient get(String name) {
        return ctx.getBean(name + "Client", SentimentClient.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
}

Voici comment nous pouvons utiliser le localisateur.

ClientLocatorDemo.java

@Component
public class ClientLocatorDemo implements CommandLineRunner {

    @Autowired
    private ClientLocator clientLocator;

    @Override
    public void run(String... args) {
        SentimentClient firstClient = clientLocator.get("first");
        SentimentClient secondClient = clientLocator.get("second");

        firstClient.call();
        secondClient.call();

        // sortie:
        // Bonjour du premier client
        // Bonjour du deuxième client
    }
}

De cette manière, à chaque fois qu'un code souhaite obtenir un client par son nom, il suffit d'autocâbler le ClientLocator et d'appeler la méthode get(String name).

Et le plus important est que vous pouvez créer autant d'implémentations client que vous le souhaitez et les obtenir par leur nom (en supposant que chacun a un nom unique et se termine par 'Client') sans avoir à les autocâbler un par un.

0voto

Nir Levy Points 8423

Étant donné que vous dites que cette classe est annotée comme @Service, je suppose que vous aimeriez que les deux clients soient câblés automatiquement dessus et qu'ils sont tous deux également des beans.

Dans ce cas, vous pouvez les câbler automatiquement dans le constructeur (dans ce cas, le constructeur doit être public). Alternativement, vous pouvez vous passer du constructeur et câbler directement les champs (ou à travers des méthodes setter) - ceci est moins recommandé, la meilleure pratique est de câbler en utilisant un constructeur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X