Ocultar / deshabilitar funciones para algunos usuarios

10

Digamos que tengo una versión gratuita y de pago de la aplicación. La versión paga es un superconjunto de la versión gratuita con respecto a las características disponibles para los usuarios, lo que significa que la versión paga tendrá todas las características de la aplicación gratuita más un extra.

¿Existe un patrón para alternar la disponibilidad de funciones en función de un indicador que se carga al inicio (por ejemplo, gratis / pago)?

No me gusta la idea de tener los siguientes bloques de código en todas partes:

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

Tener 2 ramas git separadas para cada versión no es una opción porque significaría mantener 2 (o más) fuentes de código, parece poco práctico en general y se discute más aquí: Mantener dos versiones de software separadas de la misma base de código en Control de versiones .

¿Hay alguna manera de hacer esto, sin dejar de tener una sola base de código y sin ensuciar el código con declaraciones condicionales que verifican el indicador de pago / gratis?

Estoy seguro de que esto se discutió muchas veces antes y estoy seguro de que hay algunos patrones para abordar este problema, pero simplemente no puedo encontrarlo.

Usamos Android / Java.

Tadija Bagarić
fuente
@gnat tnx, buen hallazgo. Pero me gustaría discutir opciones que no requieren ramas separadas y mantener múltiples bases de código
Tadija Bagarić
2
Esto se parece a tener diferentes niveles de autorización. Puede investigar cómo se aborda ese problema, donde una función solo está disponible para ciertos usuarios / roles.
Bart van Ingen Schenau
@BartvanIngenSchenau Creo que son principalmente ifcontroles para ocultar los controles de las características prohibidas o tener un cuadro de diálogo emergente para cuando el usuario intenta hacer lo que no tiene permitido. Espero encontrar una manera de evitar muchos condicionales en el código
Tadija Bagarić
2
Usa poliformismo. ¡Nunca más tendrá que preguntarse sobre esta declaración if, y será MUCHO más fácil de mantener!
Steve Chamaillard

Respuestas:

14

Un like condicional if(isFreeVersion)debería ocurrir solo una vez en el código. Este no es un patrón, pero estoy seguro de que ya conoce su nombre: se llama el principio DRY . Tener un código como " if(isFreeVersion)" en más de un lugar en su código significa que repitió esta línea / la lógica en ella, lo que significa que debe ser refactorizada para evitar la repetición.

" if(isFreeVersion)" debe usarse para configurar una lista de opciones de configuración interna para diferentes funciones. El código resultante podría verse así:

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

Esto asigna su único indicador "isFreeVersion" a diferentes características . Tenga en cuenta que puede decidir aquí si prefiere utilizar indicadores booleanos individuales para características individuales, o usar algún otro tipo de parámetros, por ejemplo, diferentes objetos de estrategia con una interfaz común, si el control de características requiere una parametrización más compleja.

Ahora tiene el control de lo que está en la versión gratuita y de la versión paga en un solo lugar, lo que hace que el mantenimiento de esta lógica sea bastante simple. Todavía tendrá que tener cuidado para no tener su código abarrotado de muchas if(feature1Enabled)declaraciones (siguiendo el principio DRY), pero ahora el mantenimiento de estas comprobaciones ya no es tan doloroso. Por ejemplo, tiene un control mucho mejor de lo que necesita cambiar cuando desea que una función paga existente sea gratuita (o viceversa).

Finalmente, echemos un vistazo al artículo del blog de Fowler sobre las funciones de alternancia , donde habla sobre los puntos de entrada / función de funciones. Permítanme citar un punto central:

No intente proteger cada ruta de código en el nuevo código de función con una palanca, concéntrese solo en los puntos de entrada que llevarían a los usuarios allí y active esos puntos de entrada.

Por lo tanto, como estrategia general, concéntrese en la interfaz de usuario y restrinja sus controles al número mínimo de puntos necesarios para que una determinada característica aparezca o desaparezca. Eso debería mantener limpia su base de código, sin ningún desorden innecesario.

Doc Brown
fuente
55
Básicamente ha reemplazado IsFreeVersion con FeaturexEnabled, no ha reducido la cantidad de llamadas. Si bien no he tenido exactamente ese caso, siempre he manejado cosas similares al crear el menú deshabilitando esas opciones que el usuario no debería ver. Principalmente esto es en la creación de formularios, pero a veces tuve que hacerlo al preparar un menú emergente.
Loren Pechtel
1
@LorenPechtel: deberías leer mi respuesta nuevamente, con más cuidado. De hecho, mencioné dos cosas para reducir el número de pruebas condicionales, una de ellas el principio DRY, una de ellas centrada en las pruebas en la interfaz de usuario. Más importante aún, la cartografía de una bandera inespecíficos como isFreeVersiona determinados parámetros de la operación elimina la mayor parte del dolor de esas pruebas - que realmente empezará a tener sentido y no producen un lío de mantenimiento más.
Doc Brown
9

Si no le gustan los if/elsebloques, puede refactorizarlos para usar la herencia (consulte Reemplazar condicional por polimorfismo del libro Refactorización de Marin Fowler ). Esto sería:

  • Haz que sea un poco más simple razonar sobre tu código.

  • Permitir tener dos clases, una para la versión gratuita y la otra para la versión paga, que a su vez enviaría las llamadas a otras clases, asegurando que la distinción entre versiones gratuitas y pagas se limita a dos clases (tres contando el clase base).

  • Facilite, más adelante, agregar otras formas de su software, como una variante barata o una versión premium. Simplemente agregará otra clase y la declarará una vez en su código, y sabrá que toda la base de código seguirá funcionando como se esperaba.

Arseni Mourzenko
fuente
3
Creo que es posible que desee tener más claro que la herencia de la implementación no es necesaria para esto. Otro beneficio es que la aplicación gratuita se puede entregar sin las características premium. Modificar el código de bytes de Java para que una condición if siempre sea verdadera no es terriblemente difícil.
JimmyJames
6

Me parece que su pregunta podría resolverse bastante bien aplicando el Patrón de alternancia de funciones .

Como suele ser el caso, Pete Hodgson explicó en un artículo todos los escenarios que podría enfrentar al aplicar este patrón, mucho mejor de lo que yo podría hacer.

También hay algunas bibliotecas que admiten este patrón. Tenía experiencia trabajando con FF4J en Java, pero supongo que si escribe:

feature toggle <whatever language you prefer>

... en cualquier motor de búsqueda obtendrás varias soluciones.

danidemi
fuente
1

Hay más de una forma de lograr esto. La forma simple y directa es utilizar el Patrón de alternancia de funciones que se proporciona en tantos artículos. El siguiente enfoque tiene que ver con el diseño de características conectables. Tanto Android como IOS tienen pagos en la aplicación. Junto con ese pago, existe la posibilidad de una descarga.

Cuando observa Servlets, JAMES Mailets e incluso complementos IDE, todos utilizan el concepto de una arquitectura de complemento:

  • Defina una interfaz que su aplicación sepa cómo usar. Esa interfaz debe proporcionar una forma de inyectarse en la navegación de su aplicación y en cualquier otra aplicación para agregar puntos de contacto.
  • Configure una ruta que su aplicación leerá al inicio (la administración de complementos en tiempo de ejecución es mucho más difícil)
  • Si existe un complemento (como un archivo Java Jar), ​​la aplicación lee el manifiesto para encontrar la implementación de la interfaz del complemento o busca una clase que implemente la interfaz.
  • Una vez que se encuentra esa clase, se instancia y se llama a los métodos apropiados para integrar las nuevas características.

Lo que esto también le permite es tener la oportunidad de tener diferentes clases de características disponibles para diferentes audiencias. Los usuarios solo tienen las funciones que pagaron.

El código de su aplicación se mantiene como una base de código, y su complemento es una base de código separada, pero solo incluye las partes que son relevantes para el complemento. La aplicación sabe cómo manejar los complementos cuando están presentes, y el complemento solo sabe cómo interactuar con la interfaz.

Berin Loritsch
fuente