9

Lets say I have a free and paid version of the app. Paid version is a superset of the free version regarding features available to users, meaning paid version will have all the features of the free app plus extra.

Is there a pattern to toggle feature availability based on a flag that loads at startup (e.g. free/paid)?

I don't like the idea of having following code blocks everywhere:

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

Having 2 separate git branches for every version is not an option because it would mean maintaining 2 (or more) code sources, seems impractical in general and is discussed more here: Maintaining Two Separate Software Versions From the Same Codebase in Version Control.

Is there a way to do this, while still having a single code base and not littering the code with conditional statements that check the free/paid flag?

I'm sure this was discussed many times before and I'm sure there are some patterns to approaching this problem, but I just can't find it.

We use Android/Java.

  • @gnat tnx, nice find. But I would like to discuss options that don't require separate branches and maintaining multiple codebases – Tadija Bagarić May 14 '18 at 10:17
  • 2
    This looks similar to having different authorization levels. You might look into how that problem is typically tackled, where a feature is only available for certain users/roles. – Bart van Ingen Schenau May 14 '18 at 10:21
  • @BartvanIngenSchenau I find it to mostly be if checks to hide controls for forbidden features or having popup dialog for when user tries to do what he is not allowed to. I am hoping to find a way to avoid many conditionals in code – Tadija Bagarić May 14 '18 at 10:34
  • 2
    Use polyphormism. You won't ever have to ask yourself about this if statement again, and it'll be A LOT easier to maintain ! – Steve Chamaillard May 14 '18 at 12:06
13

A conditional like if(isFreeVersion) should occur just once in the code. This is not a pattern, but I am sure you already know the name for it: it is called the DRY principle. Having code like "if(isFreeVersion)" in more than one place in your code means you repeated this line / the logic in it, which means it should be refactored to avoid the repetition.

"if(isFreeVersion)" should be used to set up a list of internal configuration options for different features. The resulting code then could look like this:

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

This maps your single "isFreeVersion" flag to different features. Note you can decide here if you prefer to use individual boolean flags for individual features, or use some kind of other parameters, for example different strategy objects with a common interface, if the feature control requires a more complex parametrization.

Now you have your the control of what is in the free version and what in the paid version in one place, which makes maintenance of this logic quite simple. You will still have to be careful for not having your code cluttered with lots of if(feature1Enabled) statements (by following the DRY principle), but now maintenance of this checks is not that painful any more. For example, you have much better control of what you need to change when you want to make an existing paid feature free (or vice versa).

Finally let us have a look into Fowler's blog article about feature toggles, where he speaks about feature entry points / toggle points. Let me cite one central point:

Don't try to protect every code path in the new feature code with a toggle, focus on just the entry points that would lead users there and toggle those entry points.

So as an overall strategy, focus on the user interface and restrict your checks to the minimal number of points there required to make a certain feature appear or disappear. That should keep your code base clean, without any unnecessary clutter.

  • 5
    You have basically replaced IsFreeVersion with FeaturexEnabled, you haven't reduced the number of calls. While I haven't had exactly that case I've always handled similar stuff by upon menu creation disabling those options that the user shouldn't see. Mostly this is at form creation but I've sometimes had to do it when preparing a pop-up menu. – Loren Pechtel May 16 '18 at 3:17
  • 1
    @LorenPechtel: you should read my answer again, more carefully. I actually mentioned two things to reduce the number of conditional tests, one of them the DRY principle, one of them focussing on tests in UI. More important, mapping an unspecific flag like isFreeVersionto specific feature parameters removes most of the pain of those tests - they will actually start to make sense and don't produce a maintenance mess any more. – Doc Brown May 16 '18 at 5:48
9

If you don't like if/else blocks, then you can refactor them to use inheritance (see Replace conditional with polymorphism from Marin Fowler's Refactoring book). This would:

  • Make it slightly simpler to reason about your code.

  • Make it possible to have two classes, one for the free version and the other one for paid version, which would in turn dispatch the calls to other classes, ensuring that the distinction between free and paid versions is limited to two classes (three counting the base class).

  • Make it easy, later on, to add other forms of your software, such as a cheap variant or a premium version. You'll just add another class, and declare it once in your code, and you'll know the whole code base would still work as expected.

  • 3
    I think you might want to be more clear that inheritance of implementation is not required for this. Another benefit is that the free app can be delivered without the premium features. Modifying Java byte code to make an if condition always true is not terribly difficult. – JimmyJames May 14 '18 at 14:31
6

It seems to me your question could be solved pretty well applying the Feature Toggle Pattern.

As is often the case, Pete Hodgson explained in one article all the scenarios you could face applying this pattern, much better that I could do.

There are also some libraries that supports this pattern. I had experience working with FF4J in Java but I would guess if you type:

feature toggle <whatever language you prefer>

...in any search engine you'll get several solutions.

1

There's more than one way to accomplish this. The simple and straight forward way is to use the Feature Toggle Pattern that was provided in so many articles. The next approach has to do with designing for plug-able features. Both Android and IOS have in-app payments. Along with that payment is the potential for a download.

When you look at Servlets, JAMES Mailets, and even IDE plugins, they all use the concept of a plug-in architecture:

  • Define an interface that your app knows how to use. That interface needs to provide a way to inject itself into your application's navigation and any other app to plugin touchpoints.
  • Set up a path that your app will read on start up (run-time plugin management is a lot more difficult)
  • If a plugin exists (like a Java Jar file), the app either reads the manifest to find the implementation of the plugin interface, or scans for a class that implements the interface.
  • Once that class is found, it is instantiated and the appropriate methods called to integrate the new features.

What this also allows you is the opportunity to have different classes of features available to different audiences. The users only have the features they paid for.

Your application code is maintained as one code base, and your plug-in is a separate code base--but only includes the parts that are relevant to the plugin. The app knows how to deal with plugins when they are present, and the plugin only knows how to interact with the interface.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.