Masquer / désactiver des fonctionnalités pour certains utilisateurs

10

Disons que j'ai une version gratuite et payante de l'application. La version payante est un sur-ensemble de la version gratuite concernant les fonctionnalités disponibles pour les utilisateurs, ce qui signifie que la version payante aura toutes les fonctionnalités de l'application gratuite en plus.

Existe-t-il un modèle pour basculer la disponibilité des fonctionnalités en fonction d'un indicateur qui se charge au démarrage (par exemple, gratuit / payant)?

Je n'aime pas l'idée d'avoir les blocs de code suivants partout:

if(isFreeVersion){
    // ...
} else {
    // ...
}

Avoir 2 branches git distinctes pour chaque version n'est pas une option car cela signifierait la maintenance de 2 (ou plus) sources de code, semble peu pratique en général et est discuté plus ici: Maintenir deux versions logicielles distinctes à partir de la même base de code dans le contrôle de version .

Y a-t-il un moyen de le faire, tout en ayant une base de code unique et sans joncher le code avec des instructions conditionnelles qui vérifient l'indicateur gratuit / payant?

Je suis sûr que cela a été discuté à plusieurs reprises auparavant et je suis sûr qu'il existe des modèles pour aborder ce problème, mais je ne le trouve pas.

Nous utilisons Android / Java.

Tadija Bagarić
la source
@gnat tnx, belle trouvaille. Mais je voudrais discuter des options qui ne nécessitent pas de branches distinctes et maintenir plusieurs bases de code
Tadija Bagarić
2
Cela ressemble à avoir différents niveaux d'autorisation. Vous pourriez voir comment ce problème est généralement traité, où une fonctionnalité n'est disponible que pour certains utilisateurs / rôles.
Bart van Ingen Schenau
@BartvanIngenSchenau Je trouve qu'il s'agit principalement de ifvérifications pour masquer les contrôles des fonctionnalités interdites ou d'avoir une boîte de dialogue contextuelle lorsque l'utilisateur essaie de faire ce qu'il n'est pas autorisé à faire. J'espère trouver un moyen d'éviter de nombreuses conditions dans le code
Tadija Bagarić
2
Utilisez le polyphormisme. Vous n'aurez plus jamais à vous poser de questions sur cette déclaration if, et ce sera BEAUCOUP plus facile à maintenir!
Steve Chamaillard

Réponses:

14

Un conditionnel if(isFreeVersion)doit se produire une seule fois dans le code. Ce n'est pas un modèle, mais je suis sûr que vous en connaissez déjà le nom: il s'appelle le principe DRY . Avoir du code comme " if(isFreeVersion)" à plusieurs endroits dans votre code signifie que vous avez répété cette ligne / la logique qu'elle contient, ce qui signifie qu'elle doit être refactorisée pour éviter la répétition.

" if(isFreeVersion)" doit être utilisé pour définir une liste d'options de configuration interne pour différentes fonctionnalités. Le code résultant pourrait alors ressembler à ceci:

 if(isFreeVersion)
 {
      feature1Enabled=false;
      feature2Enabled=false;
      maxNoOfItems=5;
      advertisingStrategy=new ShowLotsOfAdvertisementsStrategy();
      // ...
 } 
 else
 {
      feature1Enabled=true;
      feature2Enabled=true;
      maxNoOfItems=int.MaxValue; // virtually unlimited
      advertisingStrategy=new ShowMinimalAdvertisementsStrategy();
 }

Cela met en correspondance votre indicateur "isFreeVersion" unique avec différentes fonctionnalités . Notez que vous pouvez décider ici si vous préférez utiliser des indicateurs booléens individuels pour des fonctionnalités individuelles, ou utiliser une sorte d'autres paramètres, par exemple différents objets de stratégie avec une interface commune, si le contrôle des fonctionnalités nécessite une paramétrisation plus complexe.

Maintenant, vous avez le contrôle de ce qui se trouve dans la version gratuite et de la version payante en un seul endroit, ce qui rend la maintenance de cette logique assez simple. Vous devrez toujours faire attention à ne pas avoir votre code encombré de nombreuses if(feature1Enabled)instructions (en suivant le principe DRY), mais maintenant la maintenance de ces vérifications n'est plus si douloureuse. Par exemple, vous avez un bien meilleur contrôle sur ce que vous devez changer lorsque vous souhaitez rendre une fonctionnalité payante existante gratuite (ou vice versa).

Enfin, jetons un coup d'œil à l'article de blog de Fowler sur les basculements de fonctionnalités , où il parle de points d'entrée / de basculement de fonctionnalités. Permettez-moi de citer un point central:

N'essayez pas de protéger chaque chemin de code dans le nouveau code de fonctionnalité avec une bascule, concentrez-vous uniquement sur les points d'entrée qui y mèneraient les utilisateurs et basculez ces points d'entrée.

Donc, en tant que stratégie globale, concentrez-vous sur l' interface utilisateur et limitez vos vérifications au nombre minimal de points requis pour faire apparaître ou disparaître une certaine fonctionnalité. Cela devrait garder votre base de code propre, sans encombrement inutile.

Doc Brown
la source
5
Vous avez essentiellement remplacé IsFreeVersion par FeaturexEnabled, vous n'avez pas réduit le nombre d'appels. Bien que je n'ai pas eu exactement ce cas, j'ai toujours géré des choses similaires en désactivant les options que l'utilisateur ne devrait pas voir lors de la création du menu. La plupart du temps, il s'agit de la création d'un formulaire, mais j'ai parfois dû le faire lors de la préparation d'un menu contextuel.
Loren Pechtel
1
@ LorenPechtel: vous devriez relire ma réponse, plus attentivement. J'ai en fait mentionné deux choses pour réduire le nombre de tests conditionnels, l'un d'eux le principe DRY, l'un d'eux se concentrant sur les tests dans l'interface utilisateur. Plus important encore , la cartographie d' un drapeau non spécifique comme isFreeVersionà spécifiques paramètres de fonction supprime la plupart de la douleur de ces tests - ils réellement commencer à faire sens et ne produisent pas un gâchis d'entretien plus.
Doc Brown
9

Si vous n'aimez pas les if/elseblocs, vous pouvez les refactoriser pour utiliser l'héritage (voir Remplacer le conditionnel par le polymorphisme du livre de refactorisation de Marin Fowler ). Cela:

  • Faites un peu plus simple de raisonner sur votre code.

  • Permettre d'avoir deux classes, une pour la version gratuite et l'autre pour la version payante, qui à leur tour répartiraient les appels vers d'autres classes, en veillant à ce que la distinction entre versions gratuite et payante soit limitée à deux classes (trois comptant le classe de base).

  • Rendez-vous facile, plus tard, pour ajouter d'autres formes de votre logiciel, comme une variante bon marché ou une version premium. Vous allez simplement ajouter une autre classe et la déclarer une fois dans votre code, et vous saurez que la base de code entière fonctionnerait toujours comme prévu.

Arseni Mourzenko
la source
3
Je pense que vous voudrez peut-être être plus clair sur le fait que l'héritage de la mise en œuvre n'est pas nécessaire pour cela. Un autre avantage est que l'application gratuite peut être livrée sans les fonctionnalités premium. Modifier le code d'octet Java pour que la condition if soit toujours vraie n'est pas très difficile.
JimmyJames
6

Il me semble que votre question pourrait être assez bien résolue en appliquant le modèle de bascule de fonction .

Comme c'est souvent le cas, Pete Hodgson a expliqué dans un article tous les scénarios auxquels vous pourriez faire face en appliquant ce modèle, bien mieux que je pourrais le faire.

Certaines bibliothèques prennent également en charge ce modèle. J'avais de l'expérience avec FF4J en Java mais je suppose que si vous tapez:

feature toggle <whatever language you prefer>

... dans n'importe quel moteur de recherche, vous obtiendrez plusieurs solutions.

danidemi
la source
1

Il y a plus d'une façon d'y parvenir. Le moyen simple et direct consiste à utiliser le modèle de bascule de fonctionnalité fourni dans de nombreux articles. La prochaine approche concerne la conception de fonctionnalités enfichables. Android et IOS ont tous deux des paiements intégrés. Parallèlement à ce paiement, il existe un potentiel de téléchargement.

Lorsque vous regardez les servlets, les messageries JAMES et même les plugins IDE, ils utilisent tous le concept d'une architecture de plug-in:

  • Définissez une interface que votre application sait utiliser. Cette interface doit fournir un moyen de s'injecter dans la navigation de votre application et dans toute autre application pour connecter des points de contact.
  • Configurez un chemin que votre application lira au démarrage (la gestion des plug-ins au moment de l'exécution est beaucoup plus difficile)
  • Si un plugin existe (comme un fichier Java Jar), ​​l'application lit le manifeste pour trouver l'implémentation de l'interface du plugin ou recherche une classe qui implémente l'interface.
  • Une fois que cette classe est trouvée, elle est instanciée et les méthodes appropriées sont appelées pour intégrer les nouvelles fonctionnalités.

Cela vous permet également d'avoir la possibilité de disposer de différentes classes de fonctionnalités pour différents publics. Les utilisateurs n'ont que les fonctionnalités pour lesquelles ils ont payé.

Le code de votre application est conservé comme une seule base de code, et votre plug-in est une base de code distincte - mais inclut uniquement les parties pertinentes pour le plug-in. L'application sait comment gérer les plugins lorsqu'ils sont présents, et le plugin ne sait que comment interagir avec l'interface.

Berin Loritsch
la source