2011-10-20 12 views
9

que tienen una clase con dos funciones importantes:¿Cuál es la mejor práctica cuando el orden de las llamadas en clase es importante?

public class Foo { 
    //plenty of properties here 
    void DoSomeThing(){/*code to calculate results*/} 
    void SaveSomething(){/* code to save the results in DB*/} 

} 

SaveSomething() utiliza los resultados calculados en DoSomeThing().

el problema es que no debemos llamar al SaveSomething() antes de DoSomeThing() o si eso sucede, los resultados no son verdaderos. Quiero decir que el orden de las llamadas es importante, este es un problema para mantener el código (cuando se agrega uno nuevo al equipo).

¿hay alguna forma de gestionar esto?

pienso en 3 métodos de la siguiente manera

  1. lanzar una excepción en SaveSomething() si llama antes de DoSomeThing()
  2. tener un bool que se encuentra en DoSomeThing() y SaveSomething() cambios de código a:

    bool resultsAreCalculated = false; 
    void SaveSomething(){ 
        if (!resultsAreCalculated) { 
         DoSomeThing(); 
         // the resultsAreCalculated = true; is set in DoSomeThing(); 
         // can we throw some exception? 
        } 
        /* code to save the results in DB*/ 
    } 
    
  3. implementándolo Fluido como:

    Foo x = new Foo(); 
    x.DoSomeThing().SaveSomething(); 
    

    en este caso, es importante garantizar que esto no sucede:

    x.SaveSomething().DoSomeThing(); 
    

en este momento, yo uso el segundo método. ¿Hay alguna manera mejor o es suficiente?

+2

Esto no es realmente revisión de código, ya que no está completo. Es posible que desee publicar esto en Stack Overflow, ya que es una cuestión de diseño. –

+0

@ S.Lott sí, lo es, pero creo que no es un código completo aquí. Publiqué el código completo, como una nueva pregunta, mencionando 2 o 3 de las respuestas aquí y luego se siente más la revisión del código. Por cierto, esta pregunta es relevante para [link] (http://codereview.stackexchange.com/questions/5201/best-design-pattern-for-refactoring-a-set-of-classes-thhat-do-calculation-base -en). después de 1,5 años del primer lanzamiento cuando todos los miembros del equipo son nuevos (en este proyecto, ¡no en la programación!). –

+0

Revisión de código es para el código completo. Después de todas las decisiones de diseño. Eso es lo que significa "revisión", significa que después de todo el trabajo se hace para crearlo. Una pregunta de diseño sigue Stack Overflow. –

Respuesta

6

Una opción para evitar el error del usuario es dejarlo en claro al pasar una variable. Al hacer esto, se levanta una bandera para el usuario que necesitan obtener los resultados (es decir, DoSomething()) antes de llamar a SaveSomething (...).

results = DoSomething(); // returns the results to be saved 
SaveSomething(results); 
+0

Confundido un poco con esta respuesta, ya que no hay forma de votar solo la segunda parte de la misma. +1 de todos modos – Snowbear

+0

Editar: Se eliminó la 1ª parte. Tienes razón, es más una tangente que una respuesta directa a la pregunta. Buena llamada. – bitsoflogic

+0

no está mal agregar la primera parte como segunda parte en su respuesta. –

13

Idealmente, los métodos que necesitan seguir un cierto orden en ejecución denotan, o implican la necesidad de implementar, un flujo de trabajo de algún tipo.

Hay un par de patrones de diseño que admiten el cumplimiento del orden de ejecución lineal similar al del flujo de trabajo, como Template Method Pattern o Strategy.

tomar el enfoque Template Method, su clase Foo tendrá una base abstracta que define el orden de ejecución de Do() y Save(), algo así como:

public abstract class FooBase 
{ 
    protected abstract void DoSomeThing(); 
    protected abstract void SaveSomething(); 
    public void DoAndSave() 
    { 
     //Enforce Execution order 
     DoSomeThing(); 
     SaveSomething(); 
    } 

} 

public class Foo : FooBase 
{ 
    protected override void DoSomeThing() 
    { 
     /*code to calculate results*/ 
    } 

    protected override void SaveSomething() 
    { 
     /* code to save the results in DB*/ 
    } 
} 

De esta manera los consumidores de clase sólo tendrá acceso a DoAndSave() y no infringirán el orden de ejecución que pretendías.

Hay otros patrones que se ocupan de situaciones de flujo de trabajo/estado de transición. Puede consultar Chain of Command y State Patrones.

En respuesta a tu comentario: Esto sigue la misma idea de la plantilla, se agrega un paso más en su plantilla, imagine que desea validar los resultados antes de guardar, puede ampliar su plantilla para convertirse en:

public abstract class FooBase 
{ 
    protected abstract void DoSomeThing(); 
    protected abstract void SaveSomething(); 
    protected abstract bool AreValidResults(); 
    public void DoAndSave() 
    { 
     //Enforce Execution order 
     DoSomeThing(); 

     if (AreValidResults()) 
      SaveSomething(); 
    } 

} 

Y, por supuesto, para un flujo de trabajo más elaborado, lo referí al patrón de estado al final de mi respuesta original, puede tener un control más detallado de la condición de transición de un estado a otro.

+1

¿Qué pasa si los resultados medios son necesarios por otra parte? por ejemplo, si los resultados 'DoSomeThing()' deben mostrarse al usuario y luego, si están de acuerdo (haga clic en algún lugar), entonces 'SaveSomething()' sucede? –

+1

Consulte mi respuesta editada ... la sección de comentarios no pudo contestar una larga respuesta. –

+0

Solo tenga en cuenta que esto sería tan vago para los mantenedores del método DoAndSave(). En este caso, tenga en cuenta la importancia del comentario @Anas Karkoukli agregado. El comentario se convierte en lo que garantizará que los mantenedores no rompan el código. – bitsoflogic

2

DoSave y métodos son no parece como un par ordenado para mí. Debe solicitarlos solo porque no devuelve el estado del cálculo desde el método Do. Si escribe el método Do como método que devuelve los resultados al código del cliente, puede volver a escribir Save, de modo que reciba los resultados como un parámetro.

Beneficios:

  1. Usted no tiene que pedir métodos más, porque Save método no le importa cómo el cliente tiene el parámetro. Simplemente lo recibe en eso es todo.
  2. Puede probar la unidad Do método más fácilmente, porque los métodos se acortan menos.
  3. Puede mover su método Save a otra clase, si alguna vez necesita escribir una lógica de guardado compleja o implementar un patrón de repositorio.
2

Ampliando la respuesta Levinaris' (1 si tuviera el representante), que podría tener alternativamente un método Save() en los resultados objeto devuelto por el método DoSomthing(). Así se podrían obtener algo como esto:

var obj = new Foo(); 

// Get results 
var results = obj.DoSomething(); 

// Check validity, and user acceptance 
if(this.AreValidResults(results) && this.UserAcceptsResults(results)) 
{ 
    // Save the results 
    results.Save(); 
} 
else 
{ 
    // Ditch the results 
    results.Dispose(); 
} 

Obviamente este enfoque requiere que el devuelto results objeto es un tipo genérico que maneja el guardado/eliminación de los resultados, sino que también contiene los resultados genéricos; o necesitaría ser alguna clase de clase base que los tipos de resultados específicos podrían heredar.

2

Me gusta la respuesta de Anas Karkoukli, pero otra alternativa es una máquina de estado.

public class Foo { 

    private enum State { 
     AwaitingDo, 
     AwaitingValidate, 
     AwaitingSave, 
     Saved 
    } 

    private State mState = State.AwaitingDo; 

    private void Do() { 

     // Do something 
     mState = State.AwaitingValidate; 
    } 

    private void Validate() { 

     // Do something 
     mState = State.AwaitingSave; 
    } 

    private void Save() { 

     // Do something 
     mState = State.Saved; 
    } 

    public void MoveToNextState() { 
     switch (mState) { 
      case State.AwaitingDo: 
       Do(); 
       break; 

      case State.AwaitingValidation: 
       Validate(); 
       break; 

      case State.AwaitingSave: 
       Save(); 
       break; 

      case State.Saved: 
       throw new Exception("Nothing more to do."); 
       break; 
     } 
    } 
} 

Es un poco slap-dash, pero se entiende.

El problema con la respuesta de Anas es que todas las funciones se ejecutan como un solo paso, lo que significa que no puede acceder a las etapas intermedias del objeto. Una máquina de estado obliga a los desarrolladores a seguir el flujo de trabajo, pero cada uno en cada etapa del flujo de trabajo puede examinar las propiedades del objeto antes de pasar al siguiente.

2

El excelente libro de Steve McConnel Código completo pasa todo un capítulo discutiendo esta cuestión. Es el Capítulo 14 en la segunda edición.

Si el orden de las declaraciones es importante, entonces es una muy buena práctica hacer cumplir ese orden con los datos. Así que en lugar de

calculateResults(); 
saveResults(); 

(almacenar los resultados en variables de instancia) escribir

Results r = calculateResults(); 
saveResults(r); 

Es entonces mucho más difícil de tratar de salvar los resultados antes de que se calculan. Hay una indicación clara de cuál es el orden esperado.

+0

sí, y luego los 'Resultados 'se pueden usar de alguna manera, por ejemplo:' UpdateUI (r); ' –

3

¿Qué tal esto?

interface Result { 
    void Save(); 
    SomeData GetData(); 
} 
class Foo { 
    Result DoSomething() { /* ... */ } 
} 

Uso:

myFoo.DoSomething().Save(); 
//or something like: 
var result = myFoo.DoSomething(); 
if (result.GetData().Importance > threshold) result.Save(); 

Desde un punto de vista externo, esto hace mucho sentido. Se produce un Result y proporciona medios para guardarse, si se desea, mientras que la implementación es completamente opaca. No tengo que preocuparme de devolver esto a la instancia correcta Foo. De hecho, puedo pasar el resultado a objetos que ni siquiera conocen la instancia Foo que lo creó (de hecho, el creador debe pasar toda la información necesaria para guardar en el resultado al momento de la creación). El resultado puede tener un método para decirme, si ya se ha guardado, si es necesario. Y así.

Esto es básicamente la aplicación del SRP, aunque principalmente en la interfaz en lugar de la implementación. La interfaz de Foo proporciona medios para producir resultados, Result resúmenes significa manipular los resultados.

Cuestiones relacionadas