El fin de este documento es fijar los criterios de diseño elegidos para la arquitectura y código.Estos criterios fueron el resultado de un extenso análisis de los requerimientos que nuestra biblioteca debía cumplir, entre los que pesamos: claridad, flexibilidad, performance, etc. tal como se explicaron en la introducción a la documentación.
Se presentarán como resultados acompañado de breves ejemplos que muestran la bondad de las elecciones, pero no como un estudio exhaustivo como el que de hecho realizamos durante el desarrollo, principalmente porque la mayoría de las decisiones fueron tomadas en forma concurrente con este.
Nuestro proyecto no contó con una precisa especificación de requerimientos inicial, entre otras causas: porque la experiencia anterior en el área no había dado buenos frutos y no se pretendía influenciarnos en decisiones previamente tomadas y porque a esto hay que sumar nuestra prácticamente nula experiencia en las áreas tanto de "Tratamiento de Imágenes" como de "Diseño Orientado a Objetos".Como consecuencia de lo anterior se eligió un diseño en espiral de la biblioteca, tratando de lograr en cada ciclo una versión lo más adecuada posible e incorporando elementos nuevos en forma gradual. Entre algunas de las virtudes de esta estrategia que queremos destacar están: la posibilidad de en forma paralela adquirir experiencia en dos tema que nos eran desconocidos (Tratamiento de Imágenes y Orientación a Objetos) al mismo tiempo que pudimos en base al uso por parte de grupos de trabajo en el área, chequear las cualidades y carencias del software. Hay que sumar el importante hecho de ir testeando la conveniencia del diseño al incorporar funcionalidad nueva, quizás la cualidad más importante que debe tener un FrameWork en cualquier área de aplicación.
Criterios de Arquitectura
Durante el desarrollo encontramos que ciertas estructuras y algoritmos podrían implementarse en forma general sin pérdida notoria de performance y ganando en cambio en transparencia; mientras que en contrapartida en otros era vital la especialización. Por ejemplo en las implementaciones de las imágenes era muy conveniente especializar la dimensión dado que ciertos algoritmos solo tienen sentido en dimensiones específicas. En complemento a esto también es notorio que pueden existir diversas implementaciones para distintos tipos de imágenes. Por ejemplo para representar una curva es más conveniente tener una implementación que guarde solo la información de esta y omita todo el resto asociándolo a un "default pixel", mientras que en el caso más común seguramente sea necesario tener información de todos los puntos en la imagen. En consecuencia es fundamental permitir mantener muchas implementaciones de imágenes para una misma dimensión.
Otro caso de ejemplo es el de interpolar pixeles en una imagen haciendo una aproximación lineal entre todos los vecinos de la posición dada. En este caso concreto implementamos un interpolador general pero encontramos que una especialización en la dimensión, mejoraba el tiempo en un orden 6 veces y obviamente elegimos especializar.En general medimos tiempos en todos los casos y pesando contra la pérdida de generalidad asociada, decidimos que usar en cada problema particular.Encontramos que aquellos algoritmos que no requerían información espacial, podían implementarse eficientemente en forma independiente de la dimensión y por lo tanto sin pérdida de generalidad; frecuentemente a costa de agregar algún objeto que resuelva los problemas de la dimensión. Por ejemplo al sumar (restar, etc.) entre sí imágenes pixel a pixel, no es importante conocer la posición espacial del pixel para hacer la operación en cuestión ( si es importante que las posiciones sean las mismas ), ni el tipo de implementación elegido para las imágenes ( perfectamente podrían ser implementaciones diferentes ) pero es claro que el acceso estándar a los datos ( acceder por columna, fila, etc. ) implicaría especializar inútilmente este tipo de algoritmo en la dimensión. En este caso al igual que otros de características similares, se optó por definir una forma estándar para acceder a la información del los puntos, "El iterador" que es un objeto asociado a cada implementación específica y que permite ver la imagen como una secuencia de pixeles tal como se explicó en el capítulo de Design Patterns. Siguiendo el ejemplo, también es cierto que el tipo de operación entre pixeles ( si estoy sumando o restando pixeles ) podría dejarse parametrizada en un objeto externo al algoritmo, que se usaría solo en el momento de efectuar el cálculo; está técnica conocida como "Estrategia" trae aparejadas múltiples ventajas en cuanto a flexibilidad y re-uso de código.
Queda pendiente evaluar la pérdida de performance en usar el dynamic bindig.Nuestros medidas de performance, nos mostraron que en los peores casos (una operación elemental como la suma de enteros ), el dynamic binding hace caer la performance hasta el orden 7-10 veces. Como consecuencia el uso indiscriminado de esta forma de polimorfismo, puede redundar en una importante pérdida de performance. Sin embargo las estrategias usadas inteligentemente, esto es reducidas solo al uso en las pequeña parte variable de los algoritmos (o sea minimizando la porción dinámica ), logran pérdidas de performance no mayores al 50%, siempre y cuando claro está las operaciones sobre los tipos más básicos (pixeles) se logren en performance equivalente al caso estático. Esto último como veremos en el siguiente punto se solucionó mediante los "templates"."Hemos adoptado como criterio de diseño, el uso en la medida de lo posible tanto de iteradores como de estrategias", ya que estos benefician enormemente las cualidades del diseño permitiendo lo que se conoce como diseño ortogonal ( independiente ) de algoritmos y datos, salvo en casos aislados donde la generalidad ( de la dimensión por ejemplo ) implique un costo extra muy grande ( como en las estrategias de interpolación )."El tipo de polimorfismo elegido para generalizar los algoritmos es siempre mediante el dynamic binding del lenguaje y la especialización en la herencia" esto permite a costa de una razonable pérdida de performance, usar un mismo objeto diversas veces en distintos casos, al tiempo que fija una política de crecimiento ordenada de la biblioteca en una de las áreas donde esta seguramente crecerá más.Hemos intentado minimizar la herencia multiple en la biblioteca debido a las dificultades que esta acarrea, sin embargo en ciertos casos se mostró especialmente útil para resolver elegantemente algunos problemas y la usamos como solucion. Es conveniente de todos modos fijar una politica al respecto y en Bicoti hemos adoptado "Intentar minimizar el uso de herencias multiples mientras prescindir de esta no comprometa el diseño y en cualquier caso usarla solo como herencia de interface". De esta forma siempre una clase tiene lo que podriamos llamar un "arbol de herencia principal" que es del cual hereda: variables, código, etc. pero por sobre todas las cosas su identidad. Cuando la clase comparte ciertas funciones con otra familia de clases virtuales puras sobre las cuales estan definidos algoritmos que puedan ser de utilidad, se permite definir la herencia multiple como herramienta de re-uso. Por ejemplo, existe una clase virtual pura "bicotiFunction", que sencillamente marca la prescencia del "operator()" en la interface. Para esta clase estan implementados algoritmos numéricos varios que pueden ser usados en cualquier otra clase de la biblioteca, simplemente haciendo heredar esta de "bicotiFunction". Observemos que al ser puramente de interface este tipo de herencia múltiple no puede ocasionar inconsistencias y que en complemento, el objeto no pierde en lo absoluto su identidad por usar algoritmos previamente definidos para otra clase, manteniendo así la claridad en la arquitectura. En resumen la herencia múltiple usada con cuidado solo acarrea beneficios y en estos casos no nos hemos privado de usarla.Supongamos que alguien decide definir una nueva implementación para 3D, en este caso el polimorfismo me permite re-uusar todos los algoritmos existentes que sean independientes de la dimensión, así como aquellos específicos para 3D y por lo tanto el usuario puede usar toda las posibilidades existentes previamente en la biblioteca con un esfuerzo mínimo. Otro ejemplo interesante sería agregar una dimensión a la biblioteca digamos 4D; en este caso habría que agregar además alguna especialización ( para los interpoladores por ejemplo ) y nuevamente tendría a su disposición todos los algoritmos disponibles.Siguiendo con el ejemplo anterior, un usuario que necesite una operación pixel a pixel nueva entre imágenes ( por ejemplo el máximo ), solo tendrá que definir un objeto con la operación adecuada y tendrá disponibles los algoritmos para cualquier implementación y dimensión existentes. El siguiente punto tiene que ver con los casos donde no fue conveniente la adopción de la política anterior, el porque y la solución propuesta.Desde el comienzo vimos claramente que el pixel en la imagen iba a ser una de las partes con más variabilidad en la biblioteca. Estaba claro que debíamos poder usar pixeles de tipo simple (e.g. char, int, float, etc.) que de hecho es lo que tenía implementado la biblioteca vieja, pero se debía además poder tratar imágenes en color e incluso dejar la puerta abierta para imágenes no tan convencionales ( un ejemplo podría ser enriquecer una imagen con información de resonancias electromagnéticas y/o conjunto con tomografías computadas por ejemplo ). En resumen era necesario extender el concepto de color de un pixel.También estaba el problema que una misma implementación debía poder usarse con diversos tipos de pixel. Ambos problemas están íntimamente ligados.Para re-usar una misma implementación con diversos pixeles es necesaria alguna forma de polimorfismo, mientras que para manejar imágenes complejas, encontramos que la solución al problema estaba en una de dos familias:
- Permitir representar imágenes tanto con pixeles simples como sofisticados.
- Lograr representar imágenes complejas a través de un arreglo de imágenes simples.
La primera es más conveniente desde el punto de vista de la arquitectura, por tener encapsulado en la clase pixel todos los detalles del caso en cuestión mejorando la claridad del código. El pixel en la jerga es uno de los Hot Spots de la biblioteca y por lo tanto era fundamental centralizar su implementación dejándola separada de los algoritmos y datos. Por ejemplo no es concebible tener muchas clases definidas con la misma implementación para tipos distintos de pixeles en un FrameWork, lo deseable es implementar ambas en forma independiente ( ortogonal ) e instanciar la combinación adecuada en cada caso.La principal limitante de la herencia, parecía ser la pérdida de performance en manejar objetos complejos respecto a tipos básicos. Si quisiéramos usar herencia en los pixeles, tendríamos que definir clases para encapsular los tipos básicos porque no es posible redefinirlos en el lenguaje elegido.Un ejemplo de la pérdida de performance entones podría ser definir una imagen de char's y otra que reciba una clase Char que no tenga otro fin más que permitir definir la herencia para el tipo. Nuestros experimentos demostraron que en la suma de imágenes el caso simple sin herencia era mejor en performance en una relación de 3 a 1 ( sin contar las pérdidas debidas al dynamic binding y las dificultades de trabajar con los operadores básicos ). Se volvió clara la necesidad de quitar el pixel de un árbol de herencia para poder aprovechar las optimizaciones de los compiladores en los tipos predefinidos, así como para usar con más libertad los operadores básicos (+, -, etc. ) que al tener distintos tipos de retorno dificultaban gravemente el uso y encontramos en los templates una solución conveniente para el problema, al permitirnos parametrizar el tipo de pixel en forma estática y por lo tanto lograr de hecho la misma performance al menos para los tipos predefinidos.Esta solución de parametrizar el tipo básico era válida en ambas alternativas, faltaba ver que estrategia usar en los pixeles estructurados ( e.g. RGB ). Para analizar este caso usamos como ejemplo sumas entre imágenes color representadas de dos formas distintas: tres imágenes de char y una de tipo RGB. Las pruebas arrojaron como resultado que la segunda implementación era mejor en un orden del 30% para imágenes pequeñas y la diferencia se disparaba para imágenes grandes, debido a la mayor fragmentación de memoria producida por la primera que por un lado dificultaba el acceso a memoria ( cambios permanentes en los registros de segmento ) y requería más swapeos por parte del sistema operativo.El uso de templates nos permitió que la solución más elegante para el problema, fuera al mismo tiempo la más eficiente a costa de tener el pixel como tipo estático; sin embargo esto no es grave si pensamos que la imagen no cambia su tipo de pixel en tiempo de ejecución.La experiencia anterior en conjunto con nuevas pruebas nos llevó a fijar uno de los criterios básicos de diseño; este es "Usar templates en los casos donde la performance sea más exigente, el uso de los operadores conveniente y la pérdida del tipo dinámico no comprometa la flexibilidad". Como ejemplos de los templates más típicos están: el Pixel y la Coordenada."No existen en la biblioteca objetos que mantengan datos y algoritmos simultáneamente (excluyendo los métodos básicos como el constructor), excepto los más básicos como: Pixel y Coordenada". Las motivaciones de esta restricción son nuevamente la ortogonalidad del diseño, y limitar las modificaciones en clases existentes. Las clases que mantienen datos, solo tienen las funciones básicas de acceso. De esta forma un usuario no tiene porque modificar funciones de las clases que mantienen los datos, para agregar nuevos algoritmos.
Se adoptó la política que "Ningún algoritmo de la biblioteca tiene tipo de retorno". Esto minimiza el número de copias intermedias y mejora la performance de los algoritmos. Por ejemplo: si se permitiera sumar las imágenes: A, B y C de la siguiente forma "A = B + C", el sistema generaría una copia intermedia como resultado de "B + C" y dificultaría la elección del tipo de retorno salvo que se redefiniera el operador de asignación, para las posibles combinaciones de implementaciones violando así criterios de independencia anteriores.
Se fijo que "Es siempre responsabilidad del usuario proporcionar el objeto donde un algortimo debe devolver su resultado". Por ejemplo a un filtro de vecindad es preciso proporcionarle dos implementaciones de imágen: la que tiene la información y aquella donde se desea obtener el resultado, ya que es posible que el usuario desee implementaciones distintas en ambas imágenes. En ciertos casos es posible devolver el resultado en una de las imágenes de entrada, esta en general es la opción más eficiente si solo es importante el resultado y hemos adoptado como política que "En los algoritmos donde es posible devolver el resultado en uno de los parámetros de entrada, se usa esta opción por defecto. Es responsabilidad del usuario mantener una copia de la entrada de ser necesario". El caso del filtro de vecindad es un típico ejemplo donde es preciso devolver el resultado en otra imagen ya que es naturalmente destructivo ( no puedo calcular correctamente el valor de la vecindad de un pixel en la imagen, si previamente modifiqué el pixel anterior ); en este caso el usuario debe proporcionar dos imágenes: una de donde leer la información que se devuelve inalterada y otra donde se escribe el resultado. En contraparte, el resultado de la suma de imágenes puede devolverse en una de las entradas; cuando no es necesario mantener los datos una vez obtenido el resultado, está es la opción más eficiente y la que se adoptó. En la documentación de arquitectura se especifica cual es el comportamiento de cada algoritmo.
"Los algoritmos están limitados a operar con imágenes del mismo tipo de pixel, tanto en las entrada como en la salida". Esta limitante es debida al uso de templates en los algoritmos. Al estar los algoritmos parametrizados en el pixel, habría que multiplicar el número de parámetros creando instancias para todas las posibilidades y todas las combinaciones de "castings", lo que es inaceptable en cuanto a tamaño de código y eficiencia. Lo que se proporciona para salvar esta limitante son funciones para copiar implementaciones de distintos pixeles entre sí.
"Siempre es responsabilidad del usuario liberar la memoria reservada". En Bicoti existen dos formas de reservar memoria: el "new" y el "factory method", el primero es el clásico en C++ mientras que el segundo está explicado en los "Design Patterns".
Existen casos en los que no es posible devolver el resultado en el tipo de pixel elegido para una implementación. Por ejemplo una transformada de Fourier suele retornar número enormes como resultado; si el usuario elige un pixel tipo char (obviemos que además es complejo) para la imagen de retorno, seguramente obtendrá un resultado sin valor alguno. "Es responsabilidad del usuario elegir un tipo de pixel adecuado para representar, tanto sus datos, como los resultados". Distinto es el caso en el que el pixel es adecuado para la representación, pero no para los cálculos intermedios del algoritmo; en este último caso "Es posible definir el tipo de pixel a usar internamente en los cálculos, aunque esto también debe ser fijado en cada caso por el usuario".
A veces es conveniente poder usar objetos de algoritmos muchas veces, con distintos datos; lo anterior nos motivó a fijar que "Los objetos de algoritmos deben poder crearse en forma independiente de los datos y proporcionar además mecanismos para fijar los últimos en forma dinámica".
"Ciertas constantes, tolerancias en algunos métodos numéricos, parámetros de algoritmos, tipos por defecto generales, etc. se han definido mediante macros en un archivo en la raíz de la biblioteca llamado bicoti_basic". Estos macros claro pueden modificarse, pero sugerimos cambiarlos solo de ser muy conscientes de lo que se está haciendo, y solo en para casos particulares ya que los mismos se han calibrado para adecuarse al uso estándar.
Por defecto la biblioteca realiza chequeos totales. Ciertos chequeos básicos se realizan durante la compilación misma pero muchos deben hacerse en tiempo de ejecución. A veces es posible hacer chequeos usando funciones de los datos pero en los algoritmos no especializados es necesario fijar protocolos para corroborar integridad. Por ejemplo tiene sentido preguntarle a una imágen 2D cuantas filas o columnas tiene, pero no a una 1D y por lo tanto no es posible definir este tipo de funciones en imágenes generales. Se ha optado por crear en los casos donde se encontró necesario, un protocolo de reconocimiento, basado en una especie de credencial llamada "descriptor"."Existe la opción mediante un macro del bicoti_basic de eliminar muchos de los chequeos dinámicos con la consecuente mejora de performance".
De los chequeos realizados en tiempo de ejecución algunos no le restan performance a la biblioteca porque son previos a un gran bloque de operaciones (e.g. chequear compatibilidad en las dimensiones y tamaños de un grupo de imágenes antes de sumarlas). En estos casos dejarlos es la mejor opción.
En los casos donde los chequeos se realizan muchas veces (e.g. al setear un punto en una imagen podríamos chequear si las coordenadas del punto caen dentro de esta) quitarlos trae aparejado una ligera mejora de performance que sumada a otras similares optimize la ejecución. Es en estos casos donde los chequeos pueden eliminarse y se logra comentando el macro TOTAL_CHECK de bicoti_basic.
Sugerimos desarrollar usando chequeos totales y quitarlos en la última versión, de evaluarse conveniente.
Normas de Codigo
Establecer normas de codificacion beneficia la claridad y facilita la lectura del programa.
"Los nombres de clases y funciones no se abrevian". Fue siempre nuestro propósito que las aplicaciones de alto nivel que se desarrollaran en Bicoti pudieran leerse naturalmente, en forma parecida al lenguaje natural. Es posible que los nombres completos sean largos en ciertos casos y por ende dificiles de escribir; pero es probable que ese esfuerzo extra se vea recompensado en la claridad del código hacia el resto de los usuarios e incluso para el desarrollador mismo en el futuro. No hay que perder de vista que el re-uso de código por parte de otros usuarios es uno de los objetivos principales del Framework.En Bicoti son contados los casos donde existe herencia multiple (de hecho al momento de escribir este documento solo son un par), de cualquier forma "Los nombres de las clases reflejan su posicion dentro de su arbol de herencia principal al usar como prefijo de su nombre el de su padre". De paso diremos que es probable que bicoti se use en conjunto con otras librerías de imagenes y para evitar confusión, se ha establecido que "Todos los nombres de clases públicas comienzan con la palabra 'bicoti'. Por clases públicas se entienden aquellas que se espera sean usadas por los usuarios". De los criterios anteriores se desprende un problema evidente, ¿ como separar las palabras dentro de un nombre de clase ?. La solución adoptada ha sido "Usar siempre mayúsculas para indicar el comienzo de una palabra dentro de un nombre de clase o función, a excepción del nombre 'bicoti' al comienzo de las clases".Se imponen un par de ejemplos que resuman las normas anteriores. Comenzando por las clases, un nombre adecuado para la raiz del arbol de herencia que tiene por fin mantener implementaciones de las imagenes (como matrices de pixeles) podria ser : 'bicotiImageImplementation' y para una especialización 2D de la anterior 'bicotiImageImplementation2D'. Una nueva especialización ahora de la ultima clase, podria ser aquella optimizada para mantener imagenes dispersas (sparse) y se llamara 'bicotiImageImplementation2DSparse'.En cuanto a los métodos las reglas son similares salvando la falta de 'bicoti' así como la herencia claro está; en cambio se agregan varias convenciones de nombres para los casos comunes que se veran más adelante. El método de la clase 'bicotiImageImplementation2DSparse', que permite leer un pixel de la implementación dadas sus coordenadas se llama 'GetPixel(x,y)' y el que asigna el valor a este 'SetPixel(x,y,valor_pixel)'.Adoptaremos que "Todos los nombres de los métodos y funciones comienzan en mayúsculas y de ser compuestos, se marca el comienzo de una palabra con mayúsculas". Ejemplos pueden ser 'GetPixel' y 'SetPixel'."Siempre que una función devuelva un valor en uno de los parámetros, debe ser sobre el(los) primero(s) de estos"."Los métodos con retornos por referencia (aquellos que permiten acceder o modificar directamente el valor de alguna variable) deben tener un nombre que muestre claramente cual es esta".Existe bastante libertad para fijar los nombres siempre y cuando estos den una idea clara de cual es el propósito de la función. Se han fijado en cambio normas para algunos prenombres usuales como:
Create_ Crea una instancia nueva de una clase y devuelve la referencia a esta. Se usa comunmente cuando no es posible (o práctico) hacer un 'new' explícitamente; esta asociado por lo tanto al 'Factory Method'. En la práctica es equivalente a un 'new' y por lo tanto el usuario tiene la responsabilidad de liberar la memoria entregada cuando ya no necesite el objeto.Un ejemplo tipico es el de los iteradores:'ptr_iterator = imagen.CreateIterator()' le pide al objeto imagen (que es una implementacion cualquiera de imagen) que devuelva un puntero al iterador adecuado para recorrerla.Una vez que este iterador haya completado su labor, debe efectuarse un 'delete ptr_iterator' por parte del usuario que lo creo.
Get_ Devuelve una referencia o copia de un objeto interno a aquel al que se aplica el método.Ejemplos:pixel_value = imagen.GetPixel(x,y) que copia en pixel_value el valor del pixel ubicado en las coordenadas (x, y) del objeto imagen.coord_value = ptr_iterator->GetCurrentCoordinate() que copia en el objeto coord_value la coordenada a la que hace referencia el iterador, dentro de la imagen que lo creo.Es importante cuando el tipo de retorno sea un puntero que el usuario no libere este objeto; no hay que olvidar que se esta entregando una referencia a una variable interna a otro objeto.
Set_ Fija el valor de una variable o referencia, interna a aquel objeto en que se aplica el método.Ejemplos:imagen.SetPixel(x, y, valor_pixel) cambia el valor del pixel ubicado en las coordenadas (x, y) del objeto imagen por valor_pixel.pixel_interpolator->SetImageImplementation( &imagen ) le indica al objeto interpolador de pixeles que vea su información del objeto imagen.En los casos que se pase una referencia, se tiene la garantía que el objeto no libera la memoria en cuestión; como se fijo en las normas de arquitectura esta es simpre responsabilidad del que demanda memoria.
Build_ El objeto al que se invoca el método, crea una instancia nueva de otro objeto pero esta es administrada internamente. El usuario no es responsable de liberar la memoria ya que de hecho lo hace el objeto al que se invoco el Build; sin embargo puede ser útil en términos de eficiencia hacerlo explícitamente cuando ya no sea necesario mantenerlo. En este último caso la liberación no debe hacerse con un 'delete' sino con el siguiente método.
Destroy_ Invocando el método Destroy al mismo objeto al que se hizo previamente build, se libera la memoria reservada. Típicos ejemplos son los objetos de 'fachada' que se detallaran más adelante en la documentación:fachada_imagen.BuildProperty() crea un objeto adecuado para manejar propiedades de la imagen asociada a la fachada en cuestión.En general estos métodos tienen por finalidad facilitar la vida del usuario que desea crear aplicaciones de imágenes en base a la funcionalidad existente en la biblioteca, escondiéndole los detalles de la arquitectura. En el caso anterior automáticamente se crea una instancia adecuada del objeto encargado de administrar propiedades de imágenes. Si luego de hacer el BuildProperty el programador quiere crear un histograma para la imagen(que es una propiedad), solo necesita ejecutar:fachada_imagen.GetProperty()->BuildHistogramUsual()Es importante explicar en detalle la semantica de las instrucciones anteriores. Al hacer 'BuildProperty' el objeto 'fachada_imagen' hace un 'new' de la instancia adecuada de la clase 'PropertyFacade' que es una fachada para las propiedades de imágenes. Este puntero se asocia a una variable interna del objeto fachada_imagen y se setea además una bandera indicándole al destructor que libere este objeto. Al ejecutarse fachada_imagen.GetProperty() se me entrega una copia del puntero anterior al que a su vez puedo pedirle 'BuildHistogramUsual' que nuevamente crea una instancia adecuada ahora de la clase 'PropertyHistogramUsual'. Si quisiéramos acceder al histograma en sí; tendríamos que pedírselo al objeto que administra las propiedades con:fachada_imagen.GetProperty()->GetHistogram()Observemos que si bien es posible ejecutar 'delete fachada_imagen.GetProperty()' esto liberaría un lugar de memoria que volverá a liberarse en el momento que se ejecute el destructor de fachada_imagen con el consecuente fallo de memoria asociado. Sin embargo es claro que una vez el histograma haya cumplido su rol en la aplicación, no es práctico mantenerlo ya que este objeto ocupa recursos del sistema y por lo tanto se ha provisto del metodo 'Destroy':fachada_imagen.GetProperty()->DestroyHistogram() libera el objeto histograma, tambien es posible claro está ejecutar fachada_imagen.DestroyProperty() en el caso que no necesita calcular mas propiedades para la imagen.Load_ Si bien como se estableció en los criterios de arquitectura los algoritmos no crean objetos donde guardar los resultados, en ciertos casos las fachadas crean estos objetos como mecanismo para facilitar el uso de algoritmos. Si el usuario deseara seguir trabajando sobre el resultado de un algoritmo entonces 'Load' es la solución. Ejemplo:fachada_imagen.LoadNeighbourFilterResult(), libera la imagen fuente, crea una nueva imagen destino y usa la anterior como nueva fuente.First_ Hace que un iterador cualquiera se posicione en el primer lugar del objeto al que hace referencia. Ejemplo:ptr_image_iterator->First(), deja al iterador apuntando al primer pixel de la imagen; este es aquel con todas las coordenadas cero.GetCurrent_ Devuelve algún dato relacionado a con la posición actual de un iterador. Ejemplo:ptr_image_iterator->GetCurrentCoordinateReal(), devuelve una coordenada de reales con la posición actual del iterador.SetCurrent_ Permite cambiar el valor de alguna variable relacionada a la posición actual de un iterador. Ejemplo:ptr_image_iterator->SetCurrentPixel( pixel_value ), fija el valor del pixel asociado a la posición del iterador en pixel_value.Next o ++_ Incrementa la posición de un iterador. Ejemplo:ptr_image_iterator->Next() o (*ptr_image_iterator)++IsDone_ Devuelve 'true' solo si el iterador a pasado por todos los elementos.En cuanto a los nombres de las variables, aunque estan sean internas es conveniente mantener ciertas reglas de prolijidad básicas.
"Los nombres de variables son con minusculas y las palabras se separan con un _".
Los punteros suelen ser variables críticas y es importante destacarlas "Las variables puntero comienzan con ptr".
Ademas es útil en los métodos diferenciar las variables volátiles ( locales a las funciones ) y se usa "Prenombrar las variables locales con local_". En los casos que una variable local a una función sea un puntero se usa local_ptr_variable.