c++ - pattern - Pros y contras de la inversión de control
inversion of control vs dependency injection (5)
Supongamos que tengo una secuencia de objetos [acme] que quiero exponer a través de una API. Tengo dos opciones, devoluciones de llamada e iteradores.
API # 1: devoluciones de llamada
// API #1
// This function takes a user-defined callback
// and invokes it for each object in the stream.
template<typename CallbackFunctor>
void ProcessAcmeStream(CallbackFunctor &callback);
API # 2: Iteradores
// API #2
// Provides the iterator class AcmeStreamIterator.
AcmeStreamIterator my_stream_begin = AcmeStreamIterator::begin();
AcmeStreamIterator my_stream_end = AcmeStreamIterator::end();
API # 1 toma el flujo de control del programa de la mano del usuario y no regresará hasta que se consuma toda la secuencia (olvidando las excepciones por el momento).
API # 2 retiene el flujo de control en la mano del usuario, lo que permite al usuario avanzar la secuencia por su cuenta.
La API # 1 se siente a un nivel más alto , lo que permite a los usuarios saltar a la lógica de negocios (el functor de devolución de llamada) de inmediato. Por otro lado, API # 2 se siente más flexible, permitiendo a los usuarios un nivel de control más bajo.
Desde una perspectiva de diseño, ¿con cuál debo ir? ¿Hay más pros y contras que no haya visto todavía? ¿Cuáles son algunos problemas de soporte / mantenimiento en el futuro?
Desde una perspectiva de diseño, diría que el método del iterador es mejor, simplemente porque es más fácil y también más flexible; Es realmente molesto hacer funciones de devolución de llamada para sin lambdas. (Ahora que C ++ 0x tendrá expresiones lambda, sin embargo, esto puede convertirse en una preocupación menor, pero aún así, el método del iterador es más genérico).
Otro problema con las devoluciones de llamada es la cancelación. Puede devolver un valor booleano para indicar si desea cancelar la enumeración, pero siempre me siento incómodo cuando el control está fuera de mis manos, ya que no siempre sabe qué podría suceder. Los iteradores no tienen este problema.
Y, por supuesto, siempre existe el problema de que los iteradores pueden tener acceso aleatorio, mientras que las devoluciones de llamadas no lo son, por lo que también son más extensibles.
El enfoque del iterador es más flexible, ya que la versión de devolución de llamada se implementa fácilmente en términos de la primera por medio de algoritmos existentes:
std::for_each( MyStream::begin(), MyStream::end(), callback );
El lenguaje de la biblioteca estándar de C ++ es proporcionar iteradores. Si proporciona iteradores, ProcessAcmeStream
es un envoltorio simple alrededor de std::for_each
. Tal vez valga la pena escribir, o tal vez no, pero no es exactamente impulsar a su interlocutor a un nuevo mundo radical de usabilidad, es un nuevo nombre para una aplicación de una función de biblioteca estándar para su par de iteradores.
En C ++ 0x, si también hace que el par de iteradores esté disponible a través de std::begin
y std::end
then caller puede usar el rango basado en, lo que los lleva a la lógica empresarial tan rápido como ProcessAcmeStream
hace ProcessAcmeStream
, quizás más rápido.
Así que diría, si es posible proporcionar un iterador y luego hacerlo, el estándar de C ++ hace la inversión de control para usted si la persona que llama quiere programar de esa manera. Al menos, para un caso donde el control es tan simple como este lo hace.
En mi opinión, el segundo es claramente superior. Aunque puedo (más o menos) entender su sensación de que es un nivel inferior, creo que eso es incorrecto. El primero define su propia idea específica de "nivel superior", pero es una que no encaja bien con el resto de la biblioteca estándar de C ++ y termina siendo relativamente difícil de usar. En particular, requiere que si el usuario desea algo equivalente a un algoritmo estándar, debe volver a implementarse desde cero en lugar de utilizar el código existente.
El segundo encaja perfectamente con el resto de la biblioteca (asumiendo que implementa sus iteradores correctamente) y le da al usuario la oportunidad de tratar sus datos a un nivel mucho más alto a través de algoritmos estándar (y / o algoritmos nuevos, no estándar que siguen a patrones estándar).
Una de las ventajas de las devoluciones de llamada sobre los iteradores es que los usuarios de su API no pueden desordenar la iteración. Es fácil comparar los iteradores incorrectos, usar la operación de comparación incorrecta o fallar de alguna otra manera. La API de devolución de llamada impide que.
La cancelación de la enumeración se realiza fácilmente mediante una devolución de llamada, BTW: Simplemente deje que la devolución de llamada devuelva un bool
y continúe solo mientras sea true
.