Cómo imponer la secuencia en llamadas a funciones.

Digamos que quiero diseñar una clase cuyos clientes necesitarían llamar a funciones en una secuencia particular, por ejemplo,

hasNext(); next(); 

o, como ejemplo muy genérico, una clase de CookFood con métodos:

 class CookFood { getListOfItems(); mixAllItems(); heat(); } 

En el segundo ejemplo, quiero exigir que la mezcla se haga solo después de obtener los artículos, y que el calentamiento se haga solo después de la mezcla. ¿Existen patrones conocidos o buenas prácticas que impongan la secuencia de llamadas a funciones?

Usted podría estar interesado en el patrón Step Builder . No es necesariamente una buena combinación para todos los casos que ha presentado, pero la idea es que cada operación devuelva algo implementando una interfaz que le permita realizar la siguiente operación. Dado que solo puede obtener los objetos realizando las operaciones en el orden correcto, se verá obligado a realizarlos en el orden correcto.

Si bien se sentiría un poco forzado en la situación de iteración (siguiente / hasSiguiente), se puede imaginar para el

  • ponte los calcetines,
  • luego ponte los zapatos

patrón. De alguna manera obtienes una instancia de la interfaz de CanWearSocks , que tiene solo el siguiente método.

 CanWearShoes putOnSocks() 

Cuando llama a putOnSocks() , obtiene su instancia de CanWearShoes , que tiene solo el siguiente método.

 SockAndShoeWearer putOnShoes() 

Cuando llamas a putOnShoes() , ahora tienes algo con calcetines y zapatos, y te obligaron a hacerlo en el orden correcto.

Lo que es particularmente bueno es que realmente puede usar el mismo objeto en ambos casos, pero debido a que las firmas del método solo devuelven el tipo de interfaz, el código solo podrá usar los métodos de la interfaz (a menos que el código sea engañoso, y convierta el objeto en un diferente tipo).

Ejemplo

Aquí hay un ejemplo bastante artificial que implementa el patrón de iteración, es decir, que se asegura de usar un NextChecker antes de un NextGetter.

 public class StepBuilderIteration { interface NextChecker { NextGetter hasNext(); } interface NextGetter { Object next(); NextChecker more(); } static class ArrayExample { final static Integer[] ints = new Integer[] { 1, 2, 3, 4 }; public static NextChecker iterate() { return iterate( 0 ); } private static NextChecker iterate( final int i ) { return new NextChecker() { public NextGetter hasNext() { if ( i < ints.length ) { return new NextGetter() { public Object next() { return ints[i]; } public NextChecker more() { return iterate( i+1 ); } }; } else { return null; } } }; } } public static void main(String[] args) { NextChecker nc = ArrayExample.iterate(); while (nc != null) { NextGetter ng = nc.hasNext(); if (ng != null) { System.out.println(ng.next()); nc = ng.more(); } } } } 

La salida es:

 1 2 3 4 

Si tiene acceso total al código fuente y puede modificarlo, entonces lo que le impide usar una combinación de Patrón de método de fábrica con Patrón de método de plantilla . Un ejemplo simple:

 public class CookFood { public Food MakeFood() { PrepareFood(); HeatFood(); ServeFood(); } protected abstract void PrepareFood(); protected abstract void HeatFood(); protected abstract ServeFood(); } 

Ahora los clientes del código pueden llamar a MakeFood que impondrá el orden de los pasos y, si desea personalizar cualquier paso, puede CookFood subclase de CookFood e implementar ese paso determinado. Por supuesto, los pasos PrepareFood(), HeatFood(), ServeFood() no necesitan ser abstractos, puede tener una implementación predeterminada que puede reemplazar en la subclase para la personalización.

Una forma de hacer lo que desea es establecer indicadores cada vez que se llama a una función, y luego verificar si ese indicador se establece cuando se llama a una función dependiente.

Por ejemplo:

 public void getListOfItems() { funcGetListOfItemsCalled = true; .... } public void mixAllItems() { if(funcGetListOfItemsCalled) { funcMixAllItemsCalled = true; ... } } public void mixAllItems() { if(funcMixAllItemsCalled ) { ... } } 

Puede haber diferentes enfoques, uno se enumera aquí. Aunque este enfoque considera que necesita llamar a la otra función una vez antes de llamar a la otra pero no siempre. Puedes editarlo según lo necesites:

  1. Crear variables para comprobar el estado de las llamadas a funciones. Cada vez que alguien llama listOfItems, entonces puede establecer la variable isListed en true. Luego verifique el valor de isListed en mixAllItems para asegurarse de que se haya llamado a getListOfItems anteriormente.

     class CookFood { boolean isListed; boolean isMixed; boolean isHeated; public String getListOfItems() { // do listing and mark as listed isListed = true; return "something"; } public void mixAllItems() { // check if listed first if (isListed) { // do mixing // mark as mixed isMixed = true; } else { System.out.println("You need to call getListOfItems before mixing"); return; } } public void heat() { if (isMixed) { // do heating // mark as mixed isHeated = true; } else { System.out.println("You need to call isMixed before heating"); return; } } }