Etiquetas

C (31) Cpp (28) Linux (14) asm (8) Telegram (5) bot (5) libreria (5) Algoritmo (3) Errores comunes (3) python (3) Opengl (2) kali (2) Android (1) Snippet (1) nano (1) recursividad (1)

lunes, 25 de marzo de 2024

conversion explicita (casting)

C++, conversión explícita o cast Cuando necesitamos convertir una variable perteneciente a un tipo de dato (cadena, numérico, fecha, etc.) a otro tipo diferente del suyo original, debemos decirle al programa explícitamente que tipo de conversión deseamos.

La conversión explícita en vez de realizarla el compilador automáticamente se indica de forma explícita, en C++ utilizamos la forma (nombre_de_tipo)expresión. No es exclusiva de C++ pues también se puede emplear en otros programas aunque cambie ligeramente la implementación.

Notación cast Si tenemos una función previamente definida que espera un tipo determinado, por ejemplo la función raíz cuadrada (sqrt) espera como argumento un tipo double, para evitar una salida inesperada en el caso de pasar un argumento de otro tipo, podemos escribir: sqrt ( ( double ) ( n + 2) ); De este modo forzamos para que el resultado de ( n + 2 ) sea siempre un tipo double y se lo pase a la función sqrt.

En C++ es posible expresar también una construcción cast de la forma siguiente: nombre_de_tipo(expresión) esta recibe el nombre de notación funcional, y no se puede utilizar con tipos que tengan un nombre simple.

Notación funcional Para convertir un valor a un tipo puntero utilizando la notación cast escribiríamos: int *p = ( int *)0x1F5; Pero utilizando la notación funcional escribiremo: typedef int *pint; int *p = pint(0x1F5);

Una variable de un determinado tipo no siempre puede ser convertida explícitamente a otro tipo. En este caso: struct { unsigned int a : 3; // bits 0 a 2 unsigned int b : 1; // bit 3 unsigned int c : 3; // bits 4 a 6 unsigned int d : 1; // bit 7 } atributo; La variable atributo tiene una longitud de ocho bits. Pero si intentamnos copiar la variable atributo a una variable atrib de tipo char y escribimos. char atrib = char atributo; // error Da un error, ya que C++ no permite convertir una estructura a un tipo como char aunque las longitudes de ambos sean iguales.

Conversión de Punteros Utilizando conversiones explícitas de tipo sobre punteros, es posible convertir el valor de una variable de un determinado tipo a otro cualquiera. El formato general para hacer esto es: cualquier_tipo *p = ( cualquier_tipo *)&variable. Aplicando esto al caso anterior, obtendríamos: char * atrib = ( char *)&atributo; Con lo que char a = * atrib; define la variable a de tipo char, cuyo contenido es el mismo que el de la estructura atributo. Constructores y operadores de conversión Cuando trabajamos con clases, nosotros mismos tenemos que construir las conversiones que deseamos que realice el compilador cuando utilice un objeto de una clase. Estas conversiones pueden ser o entre una clase y un tipo predefinido. Para ello podemos utilizar dos mecanismos; constructores y operadores de conversión. Constructores Podemos definir una conversión a través de un constructor que tome un argumento de un determinado tipo como entrada y lo convierta en un objeto de una determinada clase. En la definición de una clase podemos definir el siguiente constructor: nombre_de_clase (int r) { real = ( double )r; imag = 0; } Que sirve para construir un número complejo. Este constructor además de inicializar un objeto complejo utilizando solamente un valor también permite asignar directamente un entero int a un objeto complejo como se muestra a continuación. complejo c(3); // construye el complejo (3,0) c = 6; // equivale a c = complejo(6) Operador de conversión Ahora se desea que se realice también de una forma implícita o explícita, si existe ambigüedad, la conversión de un tipo definido por el usuario a un tipo básico: double d; CRacional r(1,2); d = r ; // r tiene que convertirse a double para que la instrucción d = r se ejecute correctamente, es necesario realizar una conversión implícita de CRacional a double. Este tipo de conversión no está permitido con un constructor, ya que no podemos definir un constructor de un tipo base. Cuando necesitamos convertir objetos de un tipo de clase a otro tipo, tenemos que utilizar un operador de conversión. La sintaxis para este operador es: C::operator T(); Donde T es el nombre de un tipo. La conversión que se realiza es de C a T. Para convertir de CRacional a double. class CRacional { //....... operador double(); }; inline CRacional::operator double() { return ( double )numerador/( double )denominador; } Un operador de conversión no puede tener argumentos ni tipo del valor que se retorna. Un operador de conversión se puede llamar de a través de las formas siguientes: double d; CRacional r(1,2); d = r.operator double();/llamada explícita a la función d = double(r);//conversión explícita (notación funcional ) d = ( double )r; //conversión explícita cast d = r ; // conversión implícita Un operador de conversión puede llamarse explícitamente pero su principal utilidad es que sea llamado automáticamente por el compilador cuando la evaluación de una expresión requiere el tipo de conversión realizado por él. También puede definirse un operador de conversión que convierta un objeto de una clase a otro objeto de otra clase. Conversión del tipo void* El tipo void se puede utilizar para definir un puntero a un elemento genérico. Como podemos ver la función C malloc se define como: void *malloc(size_t n); En C, la conversión del tipo void* a otro tipo podía realizarse de forma implícita de este modo. char *p = malloc(longitud + 1); Pero en C++ esta conversión tiene que realizarse de forma explícita. Esto es. char *p = ( char * ) malloc ( longitud + 1 );

punteros,templates(plantillas)

Operaciones con punteros. Tipos genéricos. Plantillas. ( Templates ). Typedef. El operador sizeof. Constructores. Objetos de la clase. Operaciones con punteros El lenguaje C++ ofrece cinco operaciones básicas con punteros. 1- Asignación: Consiste en asignar una dirección a un puntero. Normalmente se empleará el nombre de un array o con el operador dirección (&). En el siguiente ejemplo, se asigna a punt1 la dirección del inicio de un array llamado arr. En la variable punt2 colocamos la dirección del tercer y último elemento, arr[2]. static int arr[] = { 10,20,30}; int *punt1, *punt2; punt1 = arr; // asigna una dirección al puntero punt2 = & arr[2];
2- Valor guardado en una dirección: El operador * nos da el valor almacenado en la posición de memoria apuntada. *punt1 =arr[1]; //obtenemos en *punt1 el valor que hay en arr[1]. 3- Dirección de un puntero: Como cualquier variable, los punteros tienen una dirección y un valor. El operador & nos dice dónde está almacenado el puntero. int direcc; direcc = &punt1; // en direc metemos la dirección en la que se encuentra punt1. 4- Incremento de un puntero: Es posible realizar esta tarea como una suma normal o a través del operador de incremento. Si incrementamos un puntero, éste apuntará al siguiente elemento del array. De este modo, punt1++ incrementa el valor numérico de punt1 en 2 ( un int ocupa 2 bytes ) y hace que punt1 señale a arr[1]. Hay que tener en cuenta que la dirección de punt1 sigue siendo la misma. Pues una variable no se mueve de su sitio sólo por el hecho de cambiar su valor. También es posible decrementar el puntero pero hay que tener en cuenta algunos puntos peligrosos. El ordenador no le sigue la pista a un puntero, por tanto no puede saber si está apuntando dentro de un array o producirá un error. La operación ++punt1 hace que punt1 se mueva otros dos bytes. Otra cuestión a tener en cuenta es que sólo se pueden incrementar variables, y no constantes, así que una constante que sea puntero no puede cambiar su valor ( esto puede parecer una perogrullada, pero ++arr es una expresión muy atractiva). Sin embargo, sí puede utilizarse en esta suma normal. Válido No válido punt1++; arr++; x++; 3++; punt2 = punt1 + 4; punt2 = arr++; punt2 = arr + 1; x = y + 3++; 5- Resta: Dos punteros pueden restarse. Se utilizará con dos punteros que señalen elementos dentro de un mismo array, para saber cuántos elementos les separan. El resultado no son bytes, sino las mismas unidades del tamaño del array. Punteros y arrays En el siguiente ejemplo veremos cómo se relacionan los puntero y los arrays, vamos a ver primero la notación de un array y luego la notación de un puntero. Versión de array: // imprime los valores del array main( ) { static int nums[ ] = { 90, 70, 69, 58 }; int indice; for (indice = 0; indice < 4; indice ++) cout << nums[indice]; } Esta forma de programar es más directa, pues utiliza la notación de array para acceder a los elementos del array mediante la expresión nums[indice]. Versión de punteros: // utiliza punteros para imprimir los valores del array main( ) { static int nums[ ] = { 90, 70, 69, 58 }; int indice; for (indice = 0; indice < 5; indice ++) cout << *(nums + indice); } Esta versión es idéntica a la primera, excepto por la expresión *(nums + indice). Cuyo efecto es exactamente el mismo que el de nums[indice] en el programa anterior, es decir: *( array + indice) es lo mismo que array[indice] Una tira de caracteres es una cadena de bytes, finalizada por el carácter especial '\0'; En este caso para inicializar la cadena, emplearemos: char *saludo = "Hola "; En vez de static char saludo[ ] = "Hola "; Estos dos formatos parecen que tienen el mismo efecto en el programa. Pero hay una pequeña diferencia. La segunda línea reserva espacio para un array con el suficiente número de bytes ( En este caso 10 ) para almacenar la palabra más un byte correspondiente al carácter '\0' ( nulo ). La dirección del primer carácter del array se obtiene dando el nombre del array, saludo. En la versión puntero (la primera línea) se reserva espacio para el array de la misma forma, pero también se reserva espacio para una variable de puntero, este puntero es el que recibe el nombre saludo. Tipos genéricos. Plantillas. ( Templates ) Los tipos genéricos o tipos parametrizados, también denominados plantillas, permiten construir una familia de funciones o clases relacionadas. Las funciones o clases se diseñan para trabajar con un tipo específico de datos. Esta característica tiene poco interés para el diseñador de la función o de la clase, pero tiene importancia para el usuario de la función o clase, ya que le permitiría elegir el tipo de datos que necesite en cada momento. Las funciones genéricas o plantillas de función admiten argumentos de cualquier tipo. Es decir podemos definir una plantilla que admita el tipo como un parámetro más. #include template < class Plantilla > Plantilla min(Plantilla a, Plantilla b) { return ( a < b ) ? a : b; } void main( ) { int m = 11, n = 28; float x = 17.5, y = 36.2; cout << min ( m, n) << endl; cout << min (x, y) << endl; } Una plantilla no es una definición de función, sino un patrón a partir del cual el compilador puede generar una función. El prefijo template < class Plantilla > indicará al compilador que Plantilla se proporcionará en el momento de la expansión de la plantilla a una definición real de la función, en ese momento un tipo definido sustituirá a Plantilla. Si necesitamos que a sea de un tipo cualquiera y b de otro tipo cualquiera. Entonces escribimos: #include < iostream.h> template < class Plantilla1, class Plantilla2 > Plantilla2 min (Plantilla 2 a, Plantilla1 b) { return ( a < b ) ? a : b; } void main( ) { int m = 11, n = 28; float x = 17.5, y = 36.2; cout << min ( m, n) << endl; cout << min ( x, n) << endl; cout << min ( m, y) << endl; cout << min ( x, y) << endl; } El prefijo template < class Plantilla1, class Plantilla2 > indica que la plantilla en el momento de la expansión sustituirá Plantilla1 y Plantilla2 por los argumentos correspondientes a los tipos en la llamada a min, El compilador expandirá la plantilla automáticamente cuando sea necesario. Por ejemplo, si detecta una llamada min con dos argumentos enteros, automáticamente amplía una versión int de la plantilla. Clases genéricas Aunque las plantillas son útiles, son aún más importantes las plantillas de clase. Las clases genéricas o plantillas de clase permitirán definir un patrón para definiciones de clases. Las clases contenedor o clases de contenido; esto es, una clase que contiene objetos del mismo tipo (listas, arrays, etc). Son buenos ejemplos de plantillas. Si tenemos un vector de enteros o de cualquier tipo, las operaciones básicas que podremos realizar con él, serán las mismas (insertar, borrar, leer, etc.). Lo que permitirá al usuario elegir el tipo de elementos del array, para ellos, definiremos una clase genérica. Una plantilla de clase se diferencia de una plantilla de función por que se expande, dando lugar a una definición completa de clase, cuyos tipos internos son proporcionados por el usuario. Las funciones que son miembros de la plantilla de clase, también son plantillas de función, y por tanto, también tendrán que expandirse cada vez que se expanda la clase asociada. La solución a este problema, y por tanto la forma correcta de utilizar las plantillas, consiste en reunir en una sola clase base común todo el código que podría ser idéntico en las diversas ampliaciones de la plantilla, y derivar de ella la plantilla de clase. Typedef La palabra clave typedef crea un tipo al que se le asigna un nombre arbitrario dado por el programador. Se parece a #define, pero tiene tres diferencias: 1. typedef está limitado a asignar nombres simbólicos a tipos de datos únicamente, al contrario de como hace #define 2. La función typedef se ejecuta desde el compilador y desde el preprocesador. 3. Dentro de sus límites, typedef es más flexible que #define. typedef float real; ó typedef float REAL; permite declaraciones del tipo: real x; ó REAL *numero; También es posible el uso de typedef con estructuras: typedef struct DAT_PER{ char nombre[25]; union datos_conf{ char matricula[20]; int cuenta; char nif; } conf; DAT_PER *sig; }personal[MAX_NUM_PERSONAS]; lo cual permite declaraciones más intelegibles: DAT_PER *sig; o DAT_PER personal[MAX_NUM_PERSONAS] Operador sizeof El operador sizeof no se representa mediante un símbolo como el resto de operadores. Es un operador unario, no una función. Devuelve el tamaño en bytes de un objeto determinado. La forma de utilizar sizeof es: sizeof expresión; Donde expresión es el nombre de una variable o de un tipo de datos. El objetivo de sizeof es determinar el tamaño de varios objetos dependientes de la máquina. Es muy útil para escribir programas que puedan ejecutarse en varios tipos de ordenadores. Pues estos pueden tener tamaños de almacenamiento diferentes. //muestra el tamaño en bytes de varios tipos de datos main( ) { cout << sizeof(char); cout << sizeof(short); cout << sizeof(int); cout << sizeof(long); cout << sizeof(float); cout << sizeof(double); } Este ejemplo devuelve la memoria se se usa para cada dato de tipo primario. Si se ejecuta en un sistema de 16 bits. El programa le dirá que un entero ocupa 4 bytes. En uno de 64 bits le dirá que ocupa 8 bytes. Un programa que necesite este tipo de información se puede obtener fácilmente utilizando sizeof. Constructores. Objetos de la clase En primer lugar definimos una clase: class Punto{ int x; // miembros dato int y; char ch; public: void mostrar( ); void ocultar( ); }; void Punto::mostrar( ) { gotoxy( x, y); cout << ch; } void Punto::ocultar( ) { gotoxy( x, y); cout << " "; } Una vez definida la clase, para usarla se ha de definir un objeto. Se define una variable de la clase Punto, exactamente igual que se define una variable de un tipo predefinido ( int, float, etc,) o cualquier otro tipo definido por el usuario. Los objetos usan la misma notación que cualquier tipo de variable, y su alcance se extiende desde la línea donde se ha declarado hasta el final del bloque. Para crear un objeto de una clase se llama a una función denominada constructor. El constructor se llama de forma automática cuando se crea un objeto. El constructor tiene el mismo nombre que la clase. Lo específico del constructor es que no tiene tipo de retorno. Punto::Punto(argumentos) -Declaración del constructor class Punto { //... public: Punto ( char ch1, int x1, int y1); }; - El constructor inicializa los miembros dato en la definición del constructor. Punto::Punto ( char ch1, int x1, int y1) { ch = ch1; x = x1; y = y1; } -Llamamos al constructor. Para crear un objeto pt1 de la clase Punto. Punto pt1('*',20,33); En el objeto, el miembro dato ch guardará el carácter *, el miembro dato x, el número entero 20, y el miembro y, el entero 33. -Se llama a las funciones miembro desde el objeto pt1 pt1.mostrar( ); pt1.ocultar( ); Es posible tener más de un constructor, A continuación definimos uno que fije el carácter pero que permita cambiar las coordenadas del punto. -Declaración de constructor class Punto{ //... public: Punto( int x1, int y1); //... }; -Definición del constructor: se fija el carácter, y se le pasan las coordenadas del punto Punto::Punto( int x1, int y1 ) { x = x1; y = y1; ch = '*'; } Se llama al constructor para crear un objeto pt2 Punto pt2( 20, 33 ); En el objeto, el miembro dato ch almacenará el carácter *, el miembro dato x, el número entero 20, y el miembro y el entero 33. -Se llama a las funciones miembro desde el objeto pt2 pt2.mostrar( ); pt2.ocultar( ); Una clase puede tener más de un constructor. Si un constructor no tiene argumentos, es el constructor por defecto. -Declaración del constructor por defecto de la clase class Punto{ //... public: Punto( ); //... }; -Definición del constructor por defecto: los miembros dato se inicializan en el bloque de dicho constructor Punto::Punto( ) { ch = '*'; x = 20; y = 33; } Para llamar al constructor por defecto escribimos: Punto def; Si escribimos Punto def( ); // error Da un error. Para llamar a las funciones miembro desde el objeto def. def.mostrar( ); def.ocultar( );

puntero implicito this, keyword friend

El puntero implícito this Saltar el sistema de protección. ( Friend ) Miembros static de una clase Funciones virtuales El puntero implícito this Cada objeto de una determinada clase mantiene su propia copia de los datos miembro de la clase, pero no de las funciones miembro, de estas sólo existe una copia para todos los objetos de la clase; es decir, cada objeto tiene su propia estructura de datos, pero todos comparten el mismo código para cada función miembro. De este modo si necesitamos que una función miembro conozca la identidad de cada objeto para el que ha sido llamada, en C++ debemos invocar un puntero al objeto denominado this. Así por ejemplo, si declaramos un objeto. Empleado.set_empleado( ); C++ define el puntero this para apuntar al objeto fecha1 de la forma: CEmpleado *const this = &Empleado1; y si realizamos la misma operación con otro objeto empleado2, empleado2.set_empleado( ); C++ define el puntero this, para apuntar al objeto empleado2, de la forma: CEmpleado *const this = &Empleado2; Según lo expuesto, la función set_empleado puede ser definida de la forma: void CEmpleado::set_empleado( ) { cout << "nombre, ## : "; cstr >> this->nombre; cout << "apellido1, ## : "; cstr >> this ->apellido1; cout << "apellido2, #### :"; cstr >> this -> apellido2; } Un ejemplo de esto. do empleado.set_empleado( ); while ( !empleado.ok_empleado( )); En este caso, la función miembro ok_empleado conoce con exactitud a que objeto ha de ser aplicada, puesto que se ha expresado explícitamente. Pero ¿Que pasa con la función miembro Jefe_de, que se encuentra sin referencia directa alguna en el cuerpo de la función miembro ok_empleado? int Cempleado::ok_empleado( ) { //... if (Jefe_de (apellido1)) //... } En este otro caso la llamada no es explícita como en el anterior. Lo que sucede realmente, es que todas las llamadas a los datos y funciones miembro son referenciadas con un puntero implícito this, que contiene, como ya se ha dicho, la dirección del objeto por medio del cual se ha producido la llamada. Según esto también podríamos escribir. if ( this -> Jefe_de(apellido1 )); Normalmente no será necesario utilizar este puntero para acceder a los miembros de la clase, pero es útil cuando si se trabaja con estructuras dinámicas, también se puede utilizar la expresión (*this) miembro. Saltar el sistema de protección. (Friend) Los datos miembro de una clase declarados en la parte privada pueden manipularse únicamente por las funciones miembro de la clase, este es el modo de garantizar su integridad. El resultado es como una caja negra que realiza cierta función y que es reutilizable en cualquier otro programa. En el caso de que una función no miembro de una determinada clase necesite acceder a los datos miembro privados de la misma, hay que declarar a dicha función amiga de la clase. Para esto se declara la función anteponiendo al nombre de la misma la palabra reservada friend. Una función declarada friend de una clase CClase es una función no miembro que puede acceder a los miembros privados de la clase CClase. Por ejemplo la función VisualizarVector de CClase no puede acceder a la clase CVector. Según lo expuesto este problema puede resolverse haciendo que la función VisualizarVector sea friend de la clase CVector esto es: class CClase { void VisualizarVector( CVector &objvector); }; class CVector { friend void VisualizarVector( CVector &objvector); //... }; De este modo la definición de la función VisualizarVector puede utilizar datos miembro declarados como private en la clase CVector. Una función friend se puede declarar en cualquier sección de la clase. class C1; // declaración class C2 { public: int GetDato ( C1 &obj); //... }; class C1 { friend int C2::GetDato( C1 &); //... }; Algunas veces puede ser también necesario que una clase C1 tenga acceso directo a los datos miembro privados de otra clase C2. class C1 { friend class C2; //... }; Jerarquía de clases con Friend Una función friend de una clase base puede acceder solamente a los miembros de la clase derivada que fueron heredados de la clase base. Es decir, la amistad no se hereda. Si la función es friend de la clase derivada, esta puede acceder a los miembros públicos y protegidos de la clase base. Una clase friend de una clase base tiene acceso, además de a los miembros públicos, a los miembros privados y protegidos de la clase base, pero no tiene acceso a los miembros privados y protegidos de la clase derivada. Sin embargo, una clase friend de una clase derivada de una clase base, no tiene acceso a los miembros privados y protegidos de dicha clase base. Esto indica que no hay una relación entre ambas clases friend. Miembros static de una clase Una clase es un tipo, no un objeto (Un objeto es un ejemplar de una clase determinada) cada objeto de una determinada clase tiene su propia copia de los datos miembro de la clase a la que pertenece, pero no de las funciones miembro. Si declaramos un dato miembro de una clase como static, sólo existirá una copia de ese miembro, la cual será compartida por todos los objetos de esa clase y existirá aunque no existan objetos de esa clase. Un dato miembro de una clase declarado como static es una variable asociada con la clase, no con el objeto. Un dato miembro static existirá aunque no se hayan declarado objetos de la clase. Un dato miembro static debe ser declarado a nivel global. No se debe colocar la inicialización en un lugar donde se pueda producir más de una vez; por ejemplo, en un fichero de cabecera, ya que este puede ser cargado más de una vez. La inicialización de un dato static normalmente se colocará en el fichero fuente que contiene las definiciones de las funciones miembros de la clase. class CCuenta { //... static float Saldo; }; // inicializar variables estáticas float CCuenta::Saldo = 15,34; //... Una función miembro declarada como static podrá acceder solamente a miembros static de su propia clase. Este tipo de funciones no estarán asociadas con un objeto específico. Por lo que no tiene puntero this, y esto impide que puedan acceder a un miembro normal de su clase. Estas funciones se utilizan normalmente para actuar globalmente sobre todos los objetos de una clase. class CCuenta { //... static void SetSaldo ( float NuevoSaldo) { Saldo = NuevoSaldo;} //... static float Saldo; }; // inicializar variables estáticas float CCuenta::Saldo = 15,34; void main ( ) { //... CCuenta::SetSaldo (20); //... } Una función miembro static también puede llamarse utilizando la siguiente sintaxis: nombre_objeto.SetSaldo(20); Pero resulta engañosa ya que su acción no se ejecutará sobre un objeto en particular, sino sobre todos los objetos de la clase. En definitiva, un miembro static de una clase no está asociado con un objeto individual de la clase, sino que lo está con la propia clase. Funciones virtuales Si se llama a una función miembro que está definida en la clase base y también está definida en la clase derivada, la función que realmente es invocada dependerá del tipo de objeto o del puntero que se utilice para invocar a la misma. Si llamamos a una función que se define con el mismo nombre en varias clases, necesitamos que la función llamada pertenezca a la misma clase que el objeto referenciado. Cuando se llama a una función miembro que está definida en la clase base y en la clase derivada, la función invocada depende del tipo de puntero. El propio sistema será el que se encargue de identificar en tiempo de ejecución la clase de los objetos apuntados. El mecanismo que utiliza C++ para esto es la función virtual. Una función virtual es pues una función miembro pública o protegida de una clase base que puede ser redefinida en cada una de las clases derivadas. Una vez redefinida se accede a ella a través de un puntero o una referencia a su clase base. Para declarar una función virtual hay que colocar la palabra clave virtual antes de la declaración de la función miembro de la clase base. Las redefiniciones que realicemos de esta función en las clases derivadas no necesitan incorporar en su declaración la palabra clave virtual; serán declaradas implícitamente. class Albaran { //... virtual void Visualizar( ); //... }; La declaración de la función visualizar en la clase base le precede la palabra clave virtual. De este modo, tanto la función Visualizar de la clase base como la de las clases derivadas serán funciones virtuales. Una función global o static no podrá ser declarada virtual, ya que una función virtual sólo será llamada para objetos de su clase. Pero, una función virtual si podrá ser declarada friend de otra clase. Llamar a una función virtual La sintaxis para llamar a una función virtual es la misma que se utiliza para llamar a una función miembro normal. Una función virtual será invocada mediante una referencia o puntero a su clase base. La llamada a través de un puntero será automáticamente manipulada dependiendo del tipo del objeto apuntado. Si la llamada a la función virtual se hace a través de un objeto de una clase, el compilador resolverá la función a invocar basándose en el tipo de objeto. Como regla general una llamada a una función virtual se resolverá en función del tipo de objeto para el que es invocada, mientras que una llamada a una función no virtual se resolverá en base al tipo de puntero o de la referencia. Redefinición de una función virtual Una función virtual en una clase base continúa siendo virtual cuando es heredada. Una clase derivada puede disponer de su propia versión de la función virtual o asumirla tal cual. Cuando la clase derivada provee de su propia versión, no será necesario volver a especificar la palabra clave virtual. Una clase derivada también puede contener sus propias funciones virtuales; es decir, funciones virtuales propias y no heredadas de sus clases base. Para redefinir una función virtual en una clase derivada, dicha función debe tener el mismo nombre, número de tipos de argumentos y mismo tipo del valor retornado que la definida en la clase base. Si la redefinición difiere en el tipo del valor retornado, se producirá un error. Por otra parte, si difiere en los argumentos, la función será tratada como una función sobrecargada. Cuando en una clase base derivada se redefine una función de una clase base, se oculta la función de la clase base y todas las sobrecargadas de la misma en la clase base. Constructores y destructores virtuales C++ no admite constructores virtuales por tanto si un constructor llama a una función virtual, ésta corresponderá a la clase base, ya que todavía no se ha creado el objeto de la clase derivada. Si una clase base y sus derivadas tienen definidos sus destructores. Cuando durante la ejecución se sale fuera del ámbito del objeto de una clase derivada, se llamará primero al destructor de esa clase derivada y después al destructor de su clase base. Pero si el objeto ha sido creado dinámicamente y está referenciado por un puntero a la clase base, surge un problema, pues cuando se aplique el operador delete, el compilador llamará al destructor de la base aunque el objeto referenciado sea de una clase derivada. Para solucionar esto se debe declarar un destructor virtual, pero esto hace que todos los destructores de todas las clases derivadas sean virtuales, aunque no compartan el mismo nombre que el destructor de la clase base. De esta forma, cuando se aplica delete a un puntero de la clase base, este llamará al destructor perteneciente a la clase del objeto apuntado. Una buena práctica será colocar un destructor virtual a una clase que posea funciones virtuales, aunque este destructor no haga nada. El motivo de esto es que una clase derivada puede requerir un destructor; por ejemplo, si se deriva una clase de una clase base que define un destructor para ella. Colocando un destructor virtual en la clase base, nos aseguramos de que el destructor de la clase derivada será llamado cuando se necesite. Cómo se implementan las funciones virtuales El compilador no puede identificar durante la compilación la función que va a ser llamada por una sentencia como volumen[i]->Visualizar( ), ya que podría ser cualquiera de varias funciones diferentes. Esto pasa si la función Visualizar se declara virtual en la clase, esto hace que también sea virtual en todas las clases derivadas donde esté definida. El compilador evalúa la sentencia en tiempo de ejecución; es decir, cuando puede indicarle a qué tipo de objeto apunta Volumen[i]. Esto se conoce como asociación dinámica o asociación retrasada. En OOP decimos que un mensaje dirigido a un objeto se asocia con un método. Cuando la asociación se hace durante la compilación, se denomina asociación estática. Y cuando se hace durante la ejecución se llama asociación dinámica. La asociación dinámica sucede si el método que se asocia está definido como virtual. El compilador utilizará la asociación dinámica cuando no se puede utilizar una asociación estática, como ocurre a continuación: void CBiblioteca::VisualizarVol( ) { if (NVols > 0) for ( int i = 0; i < NVols; i ++) { Volumen[i] -> Visualizar( ); cout << endl; } } La asociación dinámica en C++ se implementa a través de una tabla de funciones virtuales o v-table. Dicha tabla está formada por un array de punteros a funciones que el compilador asocia a cada una de las clases que contienen una o más funciones virtuales. La v-table contiene un puntero a una función por cada una de las funciones virtuales de la clase. Clases abstractas y funciones virtuales puras En este blog ya se habló de clases abstractas en VB.Net como se dijo, una clase abstracta es una clase que puede utilizarse solamente como clase base para otras clases. Se trata de disponer de un mecanismo que soporte la implementación de un concepto general, como figura, del cual utilizaremos variantes concretas, cómo círculo, cuadrado y triángulo o como se explicó en .net con las piezas de ajedrez. Pensando en estas ideas,abstractas no se pueden crear objetos de una clase abstracta; lo que crearemos serán objetos de sus clase derivadas. Una clase para ser abstracta, necesita tener al menos una función virtual pura. Por ejemplo. class CPieza // clase abstracta { //... virtual void Visualizar( ) const = 0; //función virtual pura //... }; Una función virtual pura no requiere definición; es decir, no es necesario escribir el cuerpo de la función. El propósito de una declaración en la clase base es proveer a las clases derivadas de una interfaz polimórfica. Una clase derivada debe proveer una redefinición de la función virtual. Si no se hace, heredará la función virtual pura y se convertirá en una clase abstracta, pero esto no permitirá declarar objetos de la misma. Aunque visualizar se ha declarado constante (const) esto no quiere decir que sea necesario. Si se añade const, a las declaraciones y definiciones en las clases derivadas se tiene que añadir también const. No se pueden crear objetos de una clase abstracta. Si se intenta, el compilador muestra un error. Esta restricción es para prevenir cualquier llamada de un objeto a la función virtual pura. CPieza obj; // error, clase abstracta. Es posible declarar punteros y referencias a una clase abstracta, pero una clase abstracta no se puede utilizar como tipo para un argumento ni como tipo retornado por una función por una función, ni en una conversión cast. Por ejemplo. CPieza *p; // correcto CPieza &fn( CPieza &); // correcto Clases virtuales La jerarquía de clases ocasiona un problema por la derivación múltiple, la clase derivada heredará dos veces los miembros de la clase base. Por tanto cuando se crea un objeto de la clase derivada, éste contendrá dos sub objetos de la clase base. Tener dos sub objetos además de un derroche de espacio, hace que el compilador no sepa cual utilizar y genera un error. Para asegurarnos de que sólo se utiliza un sub objeto de la "clase base indirecta". Es necesario declarar la clase base común virtual cuando se está derivando en las clases base directas. class CBase // clase base { // lista de miembros }; class CDerivada1 : virtual public CBase { // lista de miembros }; class CDerivara2 : virtual public CBase { // lista de miembros }; class CDerivada12 : public CDerivada1, public CDerivada2 { // lista de miembros }; La palabra clave virtual, cuando se están derivando las clases bases directas CDerivada1 y CDerivada2, asegura que el compilador solamente pasará a la clase CDerivada12 una copia de CBase. Los constructores de una clase base virtual serán siempre llamados antes que los constructores de las clases no virtuales, y los destructores son siempre serán llamados en el orden inverso.

funciones sobrecargadas, referencias

/*Este texto no es mío. Viene de analisisyprogramacionoop.blogspot.com*/ Funciones sobrecargadasPaso de parámetros por referenciaReferencia como valor retornadoClases con miembros que son punterosArrays de objetos y de punteros a objetosPunteros a miembros de una clasePunteros como argumentos de funciones Funciones sobrecargadas Se dice que una función está sobrecargada cuando se declara una función previamente declarada con distinto número y/o tipo de parámetros pero con el mismo nombre. Es un concepto de programación orientada a objetos llamado polimorfismo. (Muchas declaraciones de una misma función). Cada función suele tener un nombre que la distingue de las demás. Pero se pueden presentar casos en los que varias funciones ejecuten la misma tarea sobre objetos de diferentes tipos, y puede ser interesante que dichas funciones tengan el mismo nombre. Por ejemplo podemos definir una función Mover para cada tipo de movimiento de una pieza distinta de ajedrez y llamar a todas las funciones con el nombre mover pero distinguirlas por el número y/o el tipo de los parámetros sean diferentes. Otro ejemplo: una función pot que calcule xy siendo x e y enteros o reales podemos hacer. int pot (int, int); double pot (double, double); double pot (int, double); double pot (double, int); El compilador buscará la función adecuada en función del tipo de parámetros que le enviamos, en caso de no encontrar una función con los mismos tipos de argumentos, realizaría las conversiones permitidas sobre los parámetros actuales, buscando así una función. No se pueden declarar dos funciones que difieran solamente en el resultado de salida. En el siguiente código se utilizan una u otra de estas funciones para calcular xy en función de los argumentos pasados en la llamada. #include #include "pot.h" void main( ) { double ad = 2.5 , bd = 1.5; int ai = 3, bi = 2; cout << pot ( ai, bi ) << endl; cout << pot ( ad, bd ) << endl; cout << pot (ai, bd ) << endl; cout << pot (ad, bi) << endl; } Paso de parámetros por referencia Cuando pasamos parámetros por valor se hace una copia de los parámetros en sus correspondientes parámetros formales. Esta operación se hace automáticamente cuando se llama a una función, esto impide que se modifiquen los parámetros actuales. Pasar parámetros por referencia, significa que no se transfieren los valores sino las direcciones de las variables donde están contenidos esos valores, de este modo los parámetros actuales se verán modificados en el mismo valor que lo hagan sus correspondientes parámetros formales. Cuando se llama a una función, los argumentos especificados en la llamada son pasados por valor, excepto si se trata de arrays, donde se pasan por referencia, ya que el nombre del array es un puntero a dicho array. Por valor se pueden traspasar constantes, variables y expresiones, y utilizando la forma de pasar parámetros por referencia solamente se permite transferir las direcciones de variables de cualquier tipo, arrays y funciones. Para pasar una variable por referencia, se pueden utilizar las dos formas explicadas a continuación: 1. Pasar la dirección del parámetro actual a su correspondiente parámetro el cual necesariamente tiene que ser un puntero. void permutar(int *, int *); void main( ) { int a = 10, b = 15; permutar(&a, &b);// se pasan las direcciones de //a y b cout << "a = "<< a << "b = " << b << endl; } // utilizando punteros void permutar( int *x, int *y) { int z = *x; *x = *y; *y = z; } 2. Definir el parámetro como una referencia al parámetro actual que se quiere pasar por referencia. Para ello, es necesario anteponer el operador & al nombre del parámetro. #include void permutar (int &, int &); void main( ) { int a = 10, b = 15; permutar (a, b); cout << " a = " << a << " b= " << b << endl; } // Utilizando referencias x e y son referencias a sus // correspondientes parámetros actuales a y b. void permutar(int &x, int &y) { int z = x; x = y; y = z; } Los resultados son los mismos que en el programa anterior. En el programa con punteros, cualquier asignación que se haga a *x afecta a la variable a. En el programa con referencias, la referencia x también tiene esa propiedad, pero sin necesitar el operador de indirección *. Hay que tener en cuenta que las referencias no pueden manipularse igual que los punteros. Con un puntero se puede distinguir el puntero de la variable apuntada utilizando el operador de indirección *. Es decir, x describe el puntero, mientras que *x describe el dato apuntado. Pero con una referencia sólo podemos referirnos al dato. Cualquier operación sobre la propia referencia se realizará sobre el dato referenciado pero nunca sobre la referencia. Referencia como valor retornado También es posible definir una función en la que el valor devuelto venga dado por una referencia a ese valor. #include int &fnx( ); int n; void main( ) { int c; fnx( ) = 25; // n = 15 c = fnx( ); // c = 15 fnx( )++ ; //n = 16 fnx( ) = fnx( ) + 3; // n =19 } //El tipo del resultado es una referencia a un int int &fnx( ) { // otro código return n; } En este código, el valor retornado por la función fnx es una referencia inicializada con la variable global n. Lo que sucede es que fnx actúa como un nombre alternativo para n. Por tanto una llamada a la función puede aparecer a la izquierda o a la derecha de un operador de asignación. Clases con miembros que son punteros Los operadores new y delete se pueden utilizar en la definición de las funciones miembro de una clase. En el código mostrado debajo, nuestro objetivo es crear un array de enteros con un número cualquiera de elementos. En este caso es inapropiado definir como miembro privado de la clase CVector un array con un número fijo de elementos. Para ello es mejor definir un puntero, pVector, que apunte a un entero para después reservar dinámicamente la cantidad de memoria necesaria para el array. class CVector { public: CVector( ); //crea un array con un nº de elementos //por defecto CVector(int ne); // crea un array de ne elementos CVector(CVector &); // inicialización con un //vector v CVector(int [ ], int ); // inicialización con un //array a // otras funciones miembro private: int *pVector; // puntero base int n_elementos; // numero de elementos }; Una clase cuyos miembros sean de tipo puntero como es CVector puede tener problemas. En el caso de que en el programa anterior no se defina un constructor copia y que la función main sea como esta: void main( ) { CVector vector1(x,7); fnEscribir(vector1); // escribe 1 2 3 4 5 6 7 // el siguiente bloque define vector2 { CVector vector2 = vector1; fnEscribir(vector2); //escribe1 2 3 4 5 6 7 } // vector2 ha sido destruido int *pi = new int[5]; for (int i = 0; i < 5; i++) pi[i] = i * 10; // pi apunta a un array de valores 0 10 20 30 40 fnEscribir(vector1); //escribe 0 10 20 30 40 50 } En el caso de que en el programa anterior no definamos un constructor copia, el constructor copia definido por defecto será de la forma. CVector::CVector(CVector &v) // constructor copia { n_elementos = v.n_elementos; pVector = v.pVector; } Ahora v.pVector es un puntero. El resultado de esta asignación es que pVector y v.pVector apuntan a la misma dirección de memoria. Por tanto cuando ejecutemos el código: CVector vector2 = vector1; // llama al constructor copia El miembro pVector de vector1 y el miembro pVector de vector2 apuntarán al mismo array de enteros. Por lo que cualquier modificación en uno de los objetos afecta a ambos. Si salimos fuera del ámbito de vector2 se llama al destructor de la clase CVector y se liberan los dos bloques de memoria correspondientes a ese objeto ( su estructura interna y el array de objetos ). De este modo cualquier operación posterior con el objeto vector1 podría dar resultados inesperados, ya que pVector de vector1 apuntaba a la misma localización de memoria y ésta ha sido liberada. Por otra parte cuando se sale fuera del ámbito de vector1 se invoca de nuevo al destructor, y este intentará liberar de nuevo el bloque de memoria ocupado por el array de enteros, esto provocará resultados inesperados. En la función main anterior al destruir el vector2, la siguiente instrucción asigna memoria para un array de enteros irreferenciado por pi. Se da la circunstancia de que la memoria asignada ha coincidido con la liberada. Pero en el vector1, además de no tener memoria asignada para su array de enteros, tampoco devuelve los resultados esperados. Esto pone de manifiesto que la memoria se libera, pero los punteros conservan sus valores. Si nuestra intención no fuera esta, sino crear un nuevo array para cada objeto, el constructor debería ser como el que sigue a continuación. CVector::CVector(CVector &v) //constructor copia { n_elementos = v.n_elementos; pVector = new int [n_elementos]; for ( int i = 0; i*. El operador .* liga su segundo operando a su primer operando, el cual debe ser un puntero a un miembro, el cual que debe ser un objeto. En este caso tenemos: objeto.*puntero_a_miembro Es decir. CCalificacion Alumno; float n = Alumno.*pmCalificacion; Si el primer operando es un puntero a un objeto, entonces utilizaremos el operador ->*. La sintaxis será: puntero_a_objeto ->* puntero_a_miembro De este modo temenos. CCalificacion *pAlumno; //... (pAlumno ->*pmfnSetCalificacion)(n); La precedencia de los paréntesis ( ) es mayor que la de .* y ->*, por eso son necesarios. No es posible definir un puntero a un miembro declarado como static, ya que este pertenece a la clase misma y no a un objeto en particular de la clase. Punteros como argumentos de funciones Un buen motivo para pasar un puntero a una función en vez de pasarle una copia del valor de la variable es ahorrar tiempo y espacio. Al pasar una copia de una variable grande, como un array o una estructura de datos grande, requiere una gran cantidad de espacio en la pila y de tiempo. Se puede mejorar el rendimiento de un programa pasando la dirección de esa variable y permitir que la función acceda a sus elementos a través de un puntero. Otro motivo para usar punteros como parámetros es para que una función devuelva más de un sólo valor. Pues una función sólo puede devolver un valor por medio de su sentencia return. Pero si se utilizan parámetros de tipo puntero, se puede dar a la función acceso (y de este modo poder cambiar) los valores de una variable. Para ello, la función llamada tendrá que parar argumentos como una dirección en lugar de como un valor.

const,volatile,inline,new,delete

/*Este texto no es de mi autoría. Fue sacado de analisisyprogramacionoop*/ Declaración de Constantes, const y volatile.Funciones inline.Operadores New y Delete. Declaración de constantes Una constante es como una variable pero como su nombre indica, no varía si no que es constante. Se usa fundamentalmente para definir valores que serán constantes a lo largo de todo el programa pero en vez de poner el mismo valor una y otra vez a lo largo del código, lo definimos sólo una vez al principio, de este modo si hubiera que cambiar dicho valor no es necesario rastrear todo el código buscando donde cambiarlo, basta con cambiar el valor de la constante definida al principio. Esto es válido para cualquier lenguaje aunque los ejemplos se pongan para C++. Calificador const Se puede anteponer el especificador const a la declaración de cualquier objeto o tipo de datos, con el fin de hacer que el objeto sea en lugar de una variable, una constante. const int k = 12; const int v[v] = { 1,2,3,4 }; A un objeto declarado como constante no se le puede asignar un valor más que al principio en su declaración. Al declararlo si una constante k ha sido declarada como constante las siguientes sentencias darían error: k = 100; // error k++; // error Si declaramos un puntero precedido por const, esto hace que el objeto apuntado sea una constante. Pero no pasa lo mismo con el puntero. const char *pc = "abcd"; pc[0] = 'z' ; // error pc = "efg"; // correcto Si en lugar de esto queremos declarar un puntero como una constante, procederíamos así. char *const pc = "abcd"; pc[0] = 'z'; // correcto pc = " efg"; // error Para hacer que tanto el puntero como el objeto apuntado sean constantes, procederemos como se indica a continuación: const char *const pc = "abcd"; pc[0] = 'z'; //error pc = "efg"; // error Una variable global declarada como const se considera static. Calificador volatile Si anteponemos el calificador volatile a la declaración de un objeto, hacemos que dicho objeto pueda ser modificado por otros procesos diferentes al programa actual. Una utilidad del calificador volatile es proveer acceso a las posiciones de memoria utilizadas por procesos asíncronos, tal como manejadores de interrupciones. Los calificadores const y volatile pueden utilizarse conjuntamente o individualmente. volatile int v; Los objetos declarados volátiles no se pueden utilizar en optimizaciones porque sus valores pueden cambiar en cualquier momento. Cada vez que accedemos a un objeto volatile, el sistema lee su valor actual. Además en una asignación, su valor es escrito inmediatamente. Cuando se declara explícitamente un objeto volatile, es un error que la función miembro invocada por este objeto no sea también volatile. Funciones en línea C++ provee el especificador de función llamado inline que cuando precede al nombre de la función hace que el compilador reemplace cualquier llamada a la función por el cuerpo actual de la función. Para utilizar esta funcionalidad, la función debe estar definida antes de ser invocada, en caso contrario, el compilador no la expandirá. Debido a esta restricción, las funciones inline son normalmente definidas en ficheros de cabecera .h. Una función como inline solamente es una recomendación para el compilador. Es decir, el compilador puede tomar la iniciativa de expandir o no la función, por ejemplo, por ser demasiado larga. Las funciones inline son similares a las macros declaradas con #define. Pero es mejor utilizar funciones inline que macros, ya que sus parámetros son chequeados automáticamente y no presentan los problemas de las macros parametrizadas. //Comparación de una función en línea con una macro. #include #define MENOR (X, Y) ((X) < (Y)) ? (X) : (Y) inline int menor ( int x, int y) { return ((x < y) ? x : y); } void main() { int m, a = 20, b = 30; m = MENOR ( a--, b--); // efecto colateral. El valor menor //se decrementa dos veces cout << "menor = " << m; cout << " a = " << a << " b = " << b << endl; a = 20; b = 30; m = menor(a--, b--); cout << " menor = " << m; cout << "a = " << a << " b = " << b << endl; } Este ejemplo da lugar al siguiente resultado: menor = 9 a = 8 b = 19 menor = 10 a = 9 b = 19 Las funciones inline se ejecutan más rápidamente, ya que se evitan las llamadas a cada una de estas funciones. Sin embargo, el abuso de inline en ocasiones puede no ser bueno. Ya que la modificación de una función inline obligaría a recompilar todos los módulos en los que ésta apareciera. Además el tamaño del código puede aumentar extraordinariamente. Se recomienda utilizar solamente el especificador inline cuando la función es muy pequeña o si se llama desde pocos lugares. Operadores new y delete Para manejar la memoria libre, C++ dispone de los operadores new y delete. Operador new El operador new asigna recursos de memoria para un objeto del tipo y tamaño especificados. En el caso de arrays, el tamaño se especifica explícitamente. En otros casos el tamaño viene definido por el propio tipo. El operador new devuelve un puntero a un objeto del tipo especificado que referencia el espacio reservado. Si no hay espacio para crear un objeto del tipo especificado, el operador new devuelve un puntero nulo ( 0 o NULL ). int n_elementos; cin >> n_elementos; int *a = new int [n_elementos]; // creación //dinámica del array a long *pl = new long; // asignación para un //long, notación sin paréntesis float *pf = new(float); //asignación para //un float, notación funcional struct complejo { float re, im; }; complejo *pc = new complejo; // asignación //para una estructura Si el tipo especificado es un array, el operador new devolverá un puntero al primer elemento de dicho array. Las dimensiones para crear un array pueden ser expresiones. int cols = 10; int filas = 5; double **puntArr = new double[filas * 2][cols]; En este ejemplo declara un puntero puntArr a un array de dos dimensiones dadas por las por las expresiones filas * 2 y cols. Cuando en la expresión que sigue al operador new para especificar el tipo aparece entre paréntesis, es obligatorio utilizar la versión con paréntesis del operador new (notación funcional). int (**puntArr fn)( ); puntArr fn = new(int (*[20] )( )); En este ejemplo, new asigna memoria para un array de punteros de 20 elementos a funciones que no requieren argumentos y que devuelven un entero. Si en la anterior instrucción no se utiliza la notación funcional, el compilador muestra un error. Es decir. new int (*[30] )( ); // error Es un error que se analiza como: ( new int )(*[30] )( ); // error El operador global ::new aparece originalmente declarado como void *operador new ( size_t tamaño_en_bytes_del_objeto); Según esto en los ejemplos anteriores no se ha declarado explícitamente el tamaño en bytes. Esto se debe a que es el sistema el que realiza automáticamente el cálculo a partir del tipo definido. El operador new asigna memoria de un área de memoria del programa conocida como "free store". Sobrecargando new Una clase también puede gestionar por si misma el espacio de memoria libre que se necesita para la creación de objetos. Para ello es necesario definir los operadores new y delete como funciones miembro de la clase. El operador new llamará a la función operator new, y el operador delete llamará a la función operator delete. Cuando se sobrecarguen estos operadores, la definición de estas funciones necesitan unos requerimientos que se comentan a continuación. Asignar memoria: operator new Si en un programa encontramos una instrucción como la siguiente, se invoca la función operator new para asignar un espacio del mismo tamaño especificado en bytes. Una llamada al operador new invoca una llamada al constructor de la clase. int *pint = new int [TMAX]; Si no hubiera suficiente espacio de memoria para la asignación requerida, esta función devolverá NULL. Cuando el operador new se utiliza para asignar memoria para un objeto de una clase que no tiene definido el operador new, o para un array de cualquier tipo se invoca a la función global ::new. Este operador se define de la forma. void *operator new (new_t tamaño); En el caso de que operador new se utilice para asignar memoria de un objeto de una clase con un operador new definido, entonces se invoca a la función operator new de esa clase: nombre_clase::operador new; La función operator new definida en una clase es necesario que sea una función miembro estática por lo tanto, no puede ser virtual, pues debe devolver void* y tener un primer argumento del tipo size_t. Este argumento recibirá automáticamente un valor en bytes igual al tamaño a asignar. El tipo size_t se encuentra definido en el fichero de cabecera stddef.h. Al ejecutar new, el compilador busca una definición para este operador en la clase del objeto a crear; si no la encuentra ejecuta el operador global ::new Operador delete El operador delete destruye un objeto previamente creado por el operador new, de este modo libera la memoria que ocupaba dicho objeto. El operador delete sólo se puede aplicar a punteros si estos han sido retornados a través del operador new. Un puntero a una constante no se puede borrar. Sin embargo, si se puede aplicar delete a un puntero nulo ( un puntero con valor cero ). int *p, *parray; ... p = new int [n]; parray = new int [n]; ... delete p; delete [ ] parray; En las líneas de arriba, la primera intrucción delete libera la memoria apuntada por p y asignada a un entero. La segunda línea delete libera la memoria apuntada por parray y asignada a un array. La sintaxis varía en el caso de tener que liberar la memoria ocupada por un array. En este caso, el compilador deberá invocar al destructor apropiado para cada elemento del array, desde parray[0] a parray[n-1]. El operador global ::delete aparece originalmente declarado de la forma siguiente: void *operator delete(void *puntero_al_objeto); void *operator delete(void *puntero_al_objeto, size_t tamaño); Se puede ver que no se ha declarado explícitamente el tamaño en bytes. Esto es así porque es el sistema el que realiza automáticamente el cálculo. Liberar memoria: operator delete Cuando en un programa se encuentra una sentencia como alguna de las siguientes, la función operator delete se invoca para liberar la memoria asignada por new. Una llamada al operator delete ejecuta una llamada del destructor de la clase. delete c1; delete []c3; Se puede escribir la función miembro operator delete de dos formas: void operator delete (void *); void operator delete (void *, size_t ); La función operator delete puede ser de ámbito global, o de la clase. La función global, en caso de ser definida, toma un único argumento de tipo void*. La función operator delete con dos parámetros es muy útil cuando la utilizamos desde su clase base para eliminar un objeto de una clase derivada. Es necesario que una función operator delete definida en una clase tiene que ser una función miembro estática ( por lo tanto, no puede ser virtual ), esta debe tener como tipo del resultado void y un primer argumento de tipo void*. También se puede añadir un segundo argumento de tipo size_t, el cual debemos inicializar con el compilador con el tamaño en bytes del objeto direccionado por el primer argumento. El tipo size_t se encuentra definido en el archivo de cabecera stddef.h.

Clases intercambiables usando Polimorfismo II

Crear la clase EditorDibujos Este artículo es la continuación del anterior Añadiremos un evento a EditorDibujos llamado Salvar que genere un interfaz gráfico que sea un control de usuario con interfaz gráfico y pinte en una matriz de 60x60 pixels esos puntos se salvarán en la instancia CLineaPieza cuando se dispare el evento salvar. 1. Añadir un UserControl al proyecto, llamado EditorDibujos, al crear la clase como UserControl, Visual Studio nos permite generar todo el código sobreescrito que necesitamos para un control de usuario. En último lugar, podemos cambiar la declaración de la clase base para indicar que la clase base es CEditorPieza 2. Abrir el editor de la clase EditorDibujos en el diseñador de formularios y poner la propiedad Size en la ventana de propiedades a 175, 150. 3. Añadir los controles de la tabla que se muestra a continuación y poner sus propiedades con los valores indicados.
Añadir un campo a EditorDibujos que defina los puntos del dibujo. EditorDibujos el cual mantiene un array de puntos separado, copiado como backup de la instancia CLineaPieza cuando el usuario pulsa el botón salvar. Private m_puntos() As Point = New Point() {} Añadir el siguiente campo para referirse a la instancia de CLineaPieza reeditando EditorDibujos manteniendo esta referencia y así pudiendo copiar el nuevo array de puntos después de hacer click en el botón Salvar. Private m_tipodibujo As CLineaPieza Añadir el siguiente constructor para tomar un parámetro del objeto CLineaPieza. El constructor debe copiar los puntos del objeto CLineaPieza al objeto EditorDibujos, salvando la referencia al objeto CLineaPieza y asignando el método de dibujo al control PictureBox. Public Sub New(ByVal tipodibujo As CLineaPieza) MyBase.New() InitializeComponent() ReDim Me.m_puntos(tipodibujo.Point.Length - 1) tipodibujo.Punto.CopyTo(Me.m_puntos, 0) AddHandler Me.PictureBox1.Paint, AddressOf Me.Dibujar m_tipodibujo = tipodibujo End Sub
Añadir el método Dibujar al control PictureBox . Public Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) e.Graphics.DrawRectangle(New Pen(Brushes.Black, 1), 0, 0, 60, 60) Dim punto As Integer For punto = 0 To m_puntos.Length - 2 Dim one As Point = m_puntos(punto) Dim two As Point = m_puntos(punto + 1) e.Graphics.DrawLine(Pens.Black, one, two) Next End Sub Crear el manejador de eventos para el evento mousemove del PictureBox y añadir el siguiente código para mostrar las coordenadas en la etiqueta. Private Sub pictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove Me.Label1.Text = String.Format("({0}, {1})", e.X, e.Y) End Sub Crear el manejador de evento para el evento MouseDown y añadir el siguiente código para añadir un Nuevo punto al dibujo de la pieza y redibujar el PictureBox. Private Sub pictureBox1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown ReDim Preserve m_puntos(m_puntos.Length) m_puntos(m_puntos.Length - 1) = New Point(e.X, e.Y) Me.Refresh() End Sub Hacer doble click sobre el botón salvar para crear el evento del botón y añadir en siguiente código para salvar los puntos de la instancia CLineaPieza y disparar el evento Salvar. El evento RaiseSaved no debe aparecer en IntelliSense por que la clase base de este punto es UserControl y no CEditarPieza. Private Sub Salvar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Salvar.Click m_tipodibujo.Puntos = m_puntos MyBase.RaiseSaved(Me, New System.EventArgs()) End Sub Modificar la declaración de la clase para indicar que la clase deriva de la Clase CEditarPieza en lugar de la clase UserControl. Public Class EditorDibujos Inherits CEditorPieza End Class Crear la clase CBitmapPieza Para crear la clase CBitmapPieza se debe implementar otra vez un par de clases que derivan de las clases CPieza and CEditarPieza. La clase CBitmapPieza mantiene el nombre del archivo bitmap para la pieza. CBitmapPiezaEditor mantiene una referencia a la instancia CBitmapPieza y una copia del archivo bitmap. Después de usar, seleccionar un nuevo fichero bitmap y pulsar sobre el botón Salvar, el nuevo nombre de fichero se guarda en una instancia de CBitmapPieza. Añadir una nueva clase al proyecto y llamarla CBitmapPieza. Modificar la declaración de la clase para indicar que la clase deriva de la clase CPieza. Public Class CBitmapPieza Inherits CPieza Public Overrides Function Clone() As CPieza End Function Public Overrides Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) End Sub Public Overrides Function ObtenerEditor() As CEditorPieza End Function End Class Añadir el siguiente campo y propiedad para almacenar el nombre de fichero del bitmap: Private m_piezabitmap As String = "" Public Property BitmapFile() As String Get Return (m_piezabitmap) End Get Set(ByVal Value As String) m_piezabitmap = Value End Set End Property. Definir el método Dibujar, exactamente como el de la clase CLineaPieza, la codificación del interfaz de usuario debe usar este método para mostrar la pieza. Public Overrides Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) e.Graphics.DrawImage(New _ System.Drawing.Bitmap(m_piezabitmap), 0, 0) End Sub Definir el método ObtenerEditor. Saldrá un error en este punto porque aún no se he implementado la clase CBitmapPiezaEditor. Public Overrides Function ObtenerEditor() As CEditorPieza Return New CBitmapPiezaEditor(Me) End Function Definir el método Clone. Public Overrides Function Clone() As CPieza Dim newTipodibujo As New CBitmapPieza() newTipodibujo.BitmapFile = Me.BitmapFile Return (newTipodibujo) End Function Crear la claseCBitmapPiezaEditor La clase CBitmapPiezaEditor necesita solo buscar y guardar. Botones salvar y un picturebox para mostrar los archivos bitmap. Añadir una nueva clase UserControl al proyecto. Llamarla CBitmapPiezaEditor. Abrir CBitmapPiezaEditor en el diseñador y poner la propiedad Size a 175, 150 en la ventana de propiedades. Añadir los siguientes controles y poner sus propiedades como las mostradas en la tabla.
Abrir la clase CBitmapPiezaEditor con el editor de código y añadir un campo al archivo bitmap. La clase CBitmapPiezaEditor mantiene una referencia separada al nombre de archivo que se copió en la instancia de CBitmapPieza cuando el usuario pulsa el botón Salvar. Private m_ficheroBitmap As String Añadir un campo para referirse a la instancia CBitmapPieza. CBitmapPiezaEditor mantiene esta referencia así que copia el nombre del archivo bitmap en CBitmapPieza cuando el usuario pulsa el botón salvar. Private m_tipodibujo As CBitmapPieza Añadir el siguiente constructor, el cual toma un parámetro, una instancia de CBitmapPieza. El constructor copiará el nombre de archivo de CBitmapPieza a CBitmapPiezaEditor, salvando la referencia a CLineaPieza y asignando un método de dibujo al control PictureBox. Public Sub New(ByVal tipodibujo As CBitmapPieza) MyBase.New() InitializeComponent() m_tipodibujo = tipodibujo m_piezabitmap = tipodibujo.BitmapFile AddHandler Me.PictureBox1.Paint, AddressOf Me.Dibujar End Sub
Añadir el método dibujar. Public Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) e.Graphics.DrawImage(New Bitmap(m_piezabitmap), 0, 0) End Sub Crear el manejador de evento para el botón buscar y añadir este código para mostrar la caja de dialogo. Private Sub Buscar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Buscar.Click OpenFileDialog1.ShowDialog() If (Me.OpenFileDialog1.FileName.Length <> 0) Then m_piezabitmap = Me.OpenFileDialog1.FileName Me.PictureBox1.Refresh() End If End Sub Crear el manejador del evento Click del botón salvar y añadirle este código para salvar en fichero y guardarlo en una instancia de CBitmapPieza al ejecutar el evento salvar. Private Sub Salvar_Click(ByVal sender As System.Object,ByVal e As System.Eve ntArgs) Handles Salvar.Click m_tipodibujo.BitmapFile = m_piezabitmap MyBase.RaiseSaved(Me, New System.EventArgs()) End Sub El Interfaz de usuario La implementación para dibujar la pieza como bitmap es diferente, sin embargo el código del interfaz de usuario, es muy simple y no revela las diferencias entre los dos tipos de piezas dibujadas. Crear los elementos del interfaz de usuario El interfaz de usuario contiene paneles para plantillas y para piezas editadas y un área para editar piezas. Abrir el diseñador del formulario form1, en la ventana de propiedades, cambiar la propiedad Size del form1 y ponerla a 344,392. Añadir los siguientes controles al formulario.
Con lo que el interfaz de usuario queda así:
La clase CPieza muestra cada pieza gracias al método Dibujar, y no contiene ningún tipo de elemento que pueda mostrar en un formulario, tal y como un control panel con una imagen o un PictureBox. Añadir la siguiente mini clase, BotonPieza, después del final la clase Form1. Este control de usuario creado por el cliente se usa para mostrar las piezas en plantillas y en paneles de piezas. Public Class BotonPieza Inherits UserControl Private m_pieza As CPieza Public Sub New(ByVal nuevaPieza As CPieza) Me.Size = New Size(61, 61) m_pieza = nuevaPieza AddHandler Me.Paint, AddressOf nuevaPieza.Dibujar End Sub Public Property Pieza() As CPieza Get Return (m_pieza) End Get Set(ByVal Value As CPieza) m_pieza = Value End Set End Property End Class Hay que notar que le uso del método Dibujar de CPieza es como el método Paint del control. En definitiva consiste en añadir una pieza a la instancia como una propiedad del control. Crear las instancias de la plantilla Las plantillas de las piezas son instancias de cada clase CLineaPieza y CBitmapPieza que se muestran en el control de usuario BotonPieza. Las instancias de BotonPieza se añaden al panel de plantillas. Esta es sólo la parte del código de la interfaz de usuario que necesita para crear la clase CPieza. No hay necesidad de agregar más de una instancia de CBitmapPieza en el panel de plantillas. Es mejor añadir múltiples instancias de CLineaPieza, ya que el usuario puede guardarlas para cuando tenga que volver a crear dibujos con una base común. Si se extiende la aplicación para añadir más tipos de piezas, se necesitaría este código para modificarlo. El resto de la aplicación se ocupará de las instancias CLineaPieza y CBitmapPieza usando las referencias de la clase base CPieza. Hacer doble click en el diseñador del formulario para crear el evento Form_Load y añadir el siguiente código al evento para añadir instancias de CPieza al panel de plantilla. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Dibujo1 As New CLineaPieza() Dibujo1.Puntos = New Point() {New Point(0, 30), New Point(60, 30), New Point(60, 0), New Point(30, 0), New Point(30, 60)} Dim Dibujo2 As New CLineaPieza() Dibujo2.Puntos = New Point() {New Point(30, 0), New Point(60, 30), New Point(30, 60), New Point(0, 30), New Point(30, 0), New Point(0, 0)} Dim bitmap1 As New CBitmapPieza() bitmap1.BitmapFile = "CarpetaProyecto\CaballoBlanco.bmp" Dim tipodibujos() As CPieza = {Dibujo1, bitmap1, Dibujo2} Dim pt As Integer For pt = 0 To tipodibujos.Length - 1 Dim button As New BotonPieza(tipodibujos(pt)) button.Top = 70 * pt button.Left = 5 AddHandler button.Click, AddressOf Me.Plantilla_Click Me.Plantillas.Controls.Add(button) Next End Sub Editar y salvar las nuevas piezas El uso de las instancias de CLineaPieza y CBitmapPieza se hace a través de las variables de referencia de CPieza. Como se ha dicho antes se simplifica la programación gracias al uso de polimorfismo, reduciendo el número de nombres de clase con las que hay que trabajar Si hay que añadir otros tipos de pieza, no hay que cambiar el código. A continuación se añade el siguiente código al formulario al método Pieza_Click y se añade un campo para referirse a la nueva instancia CPieza. Hay que tener en cuenta que da igual en qué plantilla se ha hecho click, o si el tipo de esa instancia es CLineaPieza o CBitmapPieza. Porque CEditarPieza deriva de UserControl, no importa si la instancia, devuelve una instancia de EditorDibujos o CBitmapPiezaEditor. Private m_nuevaPieza As CPieza = Nothing Private Sub Pieza_Click(ByVal sender As Object, ByVal e As System.EventArgs) Dim button As BotonPieza = CType(sender, BotonPieza) m_nuevaPieza = button.Pieza.Clone() Dim designer As CEditorPieza = m_nuevaPieza.ObtenerEditor() designer.Location = New Point(10, 10) Me.Editor.Controls.Add(designer) AddHandler designer.Saved, AddressOf Me.PiezaSalvada End Sub Finalmente añadiremos el siguiente código para el método PiezaSalvada. Esto agrega una pieza en el panel de piezas. Una vez que la pieza se guarda, el control de CEditarPieza no tiene ningún propósito. Con lo que se desecha de modo que no utiliza más recursos del sistema. Debido a que controlamos el código, sabemos que el parámetro enviado en el evento PiezaSalvada es una instancia de CEditarPieza. Esto envía un parámetro que se puede controlar con un cast. Private Sub PiezaSalvada(ByVal sender As Object, ByVal e As EventArgs) Me.Controls.Remove(CType(sender, Control)) CType(sender, Control).Dispose() Dim pb As BotonPieza = New BotonPieza(m_nuevaPieza) pb.Left = Me.Piezas.Controls.Count * 70 pb.Top = 5 pb.Enabled = False Me.Piezas.Controls.Add(pb) End Sub Todo lo que necesita el código del interfaz de usuario es una pequeña clase y tres controladores de eventos. Gran parte del trabajo se mete en las clases CPieza y CEditarPieza. Para probar la aplicación presionar F5 para ejecutar. El siguiente gráfico muestra los resultados después de añadir nuevos modelos de piezas.

Clases intercambiables usando Polimorfismo I

En este artículo aprenderemos a: § Usar clases derivadas polimórficamente. § Crear una clase que deriva de una clase UserControl. A continuación utilizaremos el polimorfismo, para resolver una tarea de programación. El polimorfismo hace referencia a una instancia de una clase derivada a través de una variable de referencia a la clase base. (Ver Clases base y abstractas) Cuando llamemos a un método o utilicemos una propiedad, serán definidos en la clase derivada. De este modo, las clases derivadas podrán responder de diferentes maneras a la llamada al mismo método. El polimorfismo simplifica la programación y hace que el diseño más fácilmente extensible. Un creador de imágenes de piezas de ajedrez Crearemos una aplicación que permite a un usuario crear las piezas del tablero de ajedrez mediante la importación de un archivo de mapa de bits creado en otra aplicación, como Paint. O dibujándolo directamente desde la aplicación. El usuario selecciona una pieza haciendo click en una de las piezas en el panel. Cuando hace click en él, la pieza se muestra en un editor que permite al usuario modificarla y guardar el modelo de pieza modificado se guarda en el panel de piezas. Diseño del creador de imágenes de las piezas En este caso en vez de referirnos a las piezas de ajedrez conceptualmente, las crearemos “físicamente” para su representación en pantalla, el programa permitirá elegir entre bitmaps o dibujos conceptuales diseñados a partir de líneas. Ejecutaremos mediante polimorfismo dos tareas de programación. Se tratará de mostrar el editor correcto basándose en el patrón seleccionado (dibujo de Paint o del editor de líneas de la aplicación). Posteriormente crearemos nuevas instancias de piezas dibujadas. Cómo diseñar las clases del editor de piezas Para los dos tipos diseñadores usaremos la misma clase base para hacer un creador de piezas que manejará los dos diferentes diseñadores: un tipo dibujado y un tipo bitmap. Para ello escribiremos código para trabajar con ambos tipos. Posteriormente podríamos añadir más modos de creación de piezas sin la necesidad de reescribir dicho código. Este modo de trabajar tiene como ventajas que el código es menos repetitivo pues prescindiendo del polimorfismo nos veríamos obligados a crear un bloque de código que crease una nueva pieza y mostrase un editor para ella. Luego sería necesario crear otro bloque de código idéntico para hacer lo mismo con el editor de bitmap. Con el polimorfismo podemos escribir, depurar y mostrar una pieza, una sola vez. Y las diferencias de código pueden ir en la clase derivada. Además se puede añadir fácilmente un nuevo tipo de pieza, pues las instancias de las clases derivadas se suministran en tiempo de ejecución con lo que es posible extender la aplicación para implementar clases derivadas adicionales. Finalmente la aplicación necesitará menos clases, pues crearemos una clase CPieza para la aplicación que tendrá las clases derivadas CLineaPieza y CCBitmapPieza. Limitaremos las referencias a CLineaPieza y CCBitmapPieza a un método del código de cliente. El resto del código de cliente utilizará únicamente las referencias a instancias de piezas. De este modo podemos reducir el número de clases, simplificando así la tarea de programación. A continuación vamos a diseñar una clase base de clase-pieza-que contiene la funcionalidad para las dos operativas, una pieza dibujada y una pieza de mapa de bits. Posteriormente se puede ampliar con más tipos de piezas, sin tener que reescribir la clase base existente. La clase CPieza que implementaremos: § Será capaz de hacer copias de sí misma con el método Clone Si se hace click en una pieza particular en el panel de piezas, la instancia de la pieza hará una copia de sí misma. El código de cliente no necesita conocer el tipo de la clase derivada. Solamente tiene que pedir una copia, y a continuación, utilizar la copia para su edición. § Añadir un método al editor a través de ObtenerEditor para que devuelva un control de usuario personalizado. Como el editor se deriva de UserControl si queremos modificar una pieza, el código de cliente necesita pedir a una instancia de pieza una instancia del editor, Para mostrarlo en tiempo de ejecución es necesario añadirlo a la Colección Controls del formulario. El editor de esta aplicación también se representa por una clase base, la clase CEditorPieza. La clase CEditorPieza a implementar será: § Implementar un evento guardado. La interfaz de usuario responde al evento salvado moviendo la pieza editada en el panel de piezas y elimina el editor del formulario. La eliminación del editor es tan sencilla como eliminar de este un UserControl de la colección de controles del formulario. § Derivar de la clase UserControl. Añadiendo una instancia a la colección de controles del formulario y mostrando el formulario en tiempo de ejecución, seremos capaces de desplegar el editor como una unidad.
Hay que implementar por derivación cada tipo de pieza que pertenezca a ambos editores. Una clase CPieza y un Editor de Piezas. La relación entre las clases base y las clases que dibujan las piezas se muestra aquí en UML:
Es importante entender que CPieza y CEditorPieza nunca serán instanciados. Sólo las clases derivadas CLineaPiezas y EditorDibujos, CBitmapPieza (no se muestra en el diagrama anterior y crean instancias de Bitmap_Editor_Pieza). Asimismo, recuerda que CEditorPieza sólo crea instancias Dibujar_CEditorPieza y CBitmapPieza crea sólo instancias de Bitmap_CEditorPieza. Usando estas clases, el control básico de flujo en el formulario aparece como esto: 1. En el inicio, la aplicación carga unas piezas en el panel de piezas. La clase CPieza implementa un método de dibujo para facilitar esto. Este código de inicio no utiliza polimorfismo, porque las clases derivadas deben ser instanciadas específicamente. 2. El usuario hace click en una de las plantillas, que es una instancia de cualquiera de la clase CLineaPieza o la clase CBitmapPieza. El controlador de eventos para el evento click no determina el tipo de método de dibujo derivado al hacer click, sino que simplemente accede a la instancia a través de una referencia de CPieza. 3. Se crea una copia de la instancia llamando al método Clone. Esta llamada se comporta de forma polimórfica. 4. Mediante una llamada al método ObtenerEditor se crea una instancia de CEditorPieza de la instancia seleccionada. Esta llamada también se comporta Polimórficamente. 5. Se deriva la instancia CEditorPieza de UserControl y se agrega a la colección de controles del formulario. Luego se muestra en el formulario. 6. El usuario modifica la pieza mediante el CEditorPieza. 7. Si el usuario hace clic en el botón Guardar, que forma parte del control CEditorPieza. El controlador de eventos Click para el botón Guardar guarda los cambios en la pieza y dispara el evento de guardado del formulario. 8. En respuesta al evento guardar, la instancia del dibujo de la pieza se añade al panel de dibujos de piezas de la clase CEditorPieza -un control de usuario- y la colección de controles del formulario la recoge y la almacena. Las clases Base CPieza y CEditorPieza son las dos clases base del proyecto por eso tienen muy pocas funciones miembro. Tienen las funciones necesarias para crear, dibujar, editar y salvar las instancias de los dibujos de las piezas. Estas funciones crean la interfaz que utilizará todo el código. El resto del comportamiento será implementado en las clases derivadas. Crear la clase CPieza La clase CPieza sólo tiene tres miembros y es una clase abstracta, es decir, que no puede ser instanciada, por tanto hay que derivar otra clase de ella. Esto deja a la totalidad de la implementación a las clases derivadas, lo cual es apropiado teniendo en cuenta lo variadas que la pueden ser las clases derivadas. Crearemos un nuevo proyecto de aplicación Windows de nombre DibujadorDePiezas. Agregaremos una nueva clase al proyecto de nombre CPieza y modificaremos la declaración de la clase para incluir la palabra clave abstract, se muestra en negrita: Public MustInherit Class CPieza End Class Añadir los siguientes miembros abstractos a la clase: Public MustOverride Sub Dibujar ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Public MustOverride Function ObtenerEditor() As CEditorPieza Public MustOverride Function Clone() As CPieza Hay que tener en cuenta que las propiedades y métodos de la clase CPieza se refieren sólo a las clases CPieza y CEditorPieza. En las clases derivadas, el método devuelve una instancia ObtenerEditor de cualquiera de las clases Dibujar_Pieza y BitMap_Pieza. El tipo que retorna ObtenerEditor es CEditorPieza, que permite a las clases derivadas devolver cualquier tipo que derive de CEditorPieza. El método Dibujar es muy similar al método Paint de los controles de formulario de Windows. Se puede añadir este método como un controlador de eventos con el método Paint de cualquier control. Esto será bueno para crear la parte de interfaz de usuario del proyecto. También es posible añadir la nueva instancia a la colección Controls del formulario, porque la clase CEditorPieza deriva de UserControl. El método clone devuelve una copia de la instancia CPieza. En las clases derivadas, la instancia devuelta puede ser de cualquiera de las clases o Dibujar_Pieza o BitMap_Pieza Crear la clase CEditorPieza CEditorPieza es una clase derivada de la clase UserControl que implementa un evento guardado. En este caso, la clase no se declara como clase abstracta porque se quieren diseñar las clases derivadas en el Diseñador de Windows Forms y para ello, la clase debe heredar de una clase concreta (no abstracta). Añadir un UserControl al proyecto y llamarlo CEditorPieza. Añadir la declaración para el evento Guardar de la clase CEditorPieza Public Class CEditorPieza Public Event Salvar(ByVal sender As Object, ByVal e As EventArgs) End Class Añadir el siguiente método al disparar el evento Salvar de la clase CEditorPieza Los eventos en las clases base no pueden ser disparados en las clases derivadas, este método debe ser accesible para las clases derivadas, para disparar el evento Salvar no se puede implementar un evento Salvar en cada clase derivada, para que el evento se comporte polimórficamente, debe ser declarado en la clase base. Public Sub DisparoSalvar(ByVal sender As Object, ByVal e As EventArgs) RaiseEvent Salvar(sender, e) End Sub Las Clases Derivadas Para utilizar las clases de forma polimórfica no se pueden añadir miembros públicos de la clase, pero debido a que se tienen que crear instancias de las clases derivadas, cada clase derivada de CEditorPieza necesita un constructor personalizado que acepte una instancia de la clase derivada de CPieza y un miembro para almacenarla. Para cada tipo de dibujo, se implementará un par de clases que se derivarán de CPieza y CEditorPieza. Las clases que derivarán de CEditorPieza implementarán los eventos de salvado. En las clases derivadas de CPieza se implementarán los miembros abstractos y se añadirán los miembros para la creación de nuevas instancias. Para acceder a los miembros de las clases derivadas será necesario usar una referencia a la clase derivada Crear la clase CLineaPieza La imagen de abajo es un dibujo creado por el usuario de 60x60 píxel. El usuario creará la pieza dentro de este espacio. El siguiente gráfico muestra el UML para la clase base y el editor de piezas
Para construir la clase que maneja esta funcionalidad hay que agregar una nueva clase al proyecto de nombre CLineaPieza. Después agregaremos una instrucción Imports al principio del fichero fuente de CLineaPieza para incluir el espacio de nombres System.Drawing. Los puntos se almacenarán como una matriz de tipo System.Drawing.Point. Agregar la instrucción imports permite usar el nombre no calificado Point, en el código. Imports System.Drawing Modificar la declaración de la clase para indicar que la clase deriva de la clase CPieza. Public Class CLineaPieza Inherits CPieza End Class Añadir el siguiente array y la propiedad para almacenar los puntos. Private m_puntos() As Point = New Point() {} Public Property Puntos() As Point() Get Return (m_puntos) End Get Set(ByVal Value As Point()) m_puntos = Value End Set End Property Para definir el método Dibujar el código de cliente se puede asignar como un manejador de eventos a cualquier control que dispare el evento Dibujar. Public Overrides Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) e.Graphics.DrawRectangle(Pens.Black, 0, 0, 60, 60) Dim point As Integer For point = 0 To m_puntos.Length - 2 Dim puntoInicio As Point = m_puntos(point) Dim puntoFin As Point = m_puntos(point + 1) e.Graphics.DrawLine(System.Drawing.Pens.Black, puntoInicio, puntoFin) Next End Sub Al definir el método ObtenerEditor se producirá un error de compilación en este punto porque no hemos implementado aún la clase EditorDibujos. Public Overrides Function ObtenerEditor() As CEditorPieza Return (New EditorDibujos(Me)) End Function Vamos a definir el método Clone. Este método reserva nueva memoria para todos los objetos contenidos en la nueva instancia y copia el valor de la instancia me en una nueva instancia. Public Overrides Function Clone() As CPieza Dim nuevaPieza As New Dibujar_Pieza nuevaPieza.m_puntos = CType(m_puntos.Clone(), Point()) Return (nuevaPieza) End Function

S.O.L.I.D en la Programacion Orientada a Objetos

/*Este texto no es de mi autoría. Fue sacado de analisisyprogramacionoop.blogspot.com*/ Solid es un acrónimo para establecer los cinco principios básicos de la programación orientada a objetos y diseño. El objetivo de tener un buen diseño de programación es llegar a la fase de mantenimiento de una forma más legible y sencilla así como conseguir crear nuevas funcionalidades sin tener que modificar en gran medida código antiguo. Los costes de mantenimiento pueden abarcar el 80% de un proyecto de software por lo que hay que valorar un buen diseño. Las reglas SOLID son un conjunto de principios que, aplicados correctamente, ayudan a escribir software de calidad en cualquier lenguaje de programación orientado a objetos. El código será más fácil de leer, testear y mantener. Los procesos de refactorización serán mucho más sencillos si se cumplen estas reglas. Los principios en los que se basa SOLID son los siguientes: Principio de Responsabilidad Única Principio Open/Closed Principio de Sustitución de Liskov Principio de Segregación de Interfaces Principio de Inversión de Dependencias Principio de Responsabilidad Única Un objeto debe realizar una única cosa. Es muy habitual, encontrar clases que tienen varias responsabilidades lógicas a la vez. Cómo detectar el incumplimiento de este principio: Si, en una misma clase están involucradas dos capas de la arquitectura. En toda arquitectura, debería haber una capa de presentación, una de lógica de negocio y otra de persistencia. Si mezclamos responsabilidades de dos capas en una misma clase, será un buen indicio de que algo va mal. El número de métodos públicos Si una clase hace muchas cosas, lo más probable es que tenga muchos métodos públicos, y que tengan poco que ver entre ellos. Hay que tratar de agruparlos para separarlos en distintas clases. Los métodos que usan cada una de las variables de esa clase Si existen dos variables, y una de ellas se utiliza en unos cuantos métodos y otra en otros cuantos, esto puede ser indicio que cada variable con sus correspondientes métodos podrían formar una clase independiente. Normalmente esto estará más difuso y habrá métodos en común, porque seguramente esas dos nuevas clases tendrán que interactuar entre ellas. Por el número de instancias Si necesitamos instanciar demasiadas clases, es posible que estemos haciendo trabajo de más. También ayuda fijarse a qué clases pertenecen esas instancias. Si se agrupan con facilidad, puede que nos esté avisando de que estamos haciendo cosas muy diferentes. Cuesta testear la clase Si no somos capaces testear fácilmente una clase, es momento de plantearse dividir la clase en dos. Cada vez que se escribe una nueva funcionalidad, esa clase se ve afectada Si una clase se modifica a menudo, es porque está involucrada en demasiadas cosas. Por el número de líneas Si una clase es demasiado grande, será posible dividirla en clases más manejables. Ejemplo Un objeto que necesita ser impreso por pantalla. public class Vehiculo { public int getCuentaRuedas() { return 4; } public int getVelocidadMaxima() { return 200; } Override public String ACadena() { return "Cuenta Ruedas=" + getCuentaRuedas() + ", Velocidad Máxima =" + getVelocidadMaxima(); } public void Imprime() { System.out.println(ACadena()); } } Aunque parece una clase de lo más razonable, se detecta que mezcla dos conceptos diferentes: la lógica de negocio y la lógica de presentación. Este código puede dar problemas en muchas situaciones distintas: Si hay que presentar el resultado de distinta forma, es necesario cambiar una clase que especifica la forma que tienen los datos. Está imprimiendo por pantalla, pero si se necesita mostrar en formato HTML. La implementación cambia. Para mostrar el mismo dato de dos formas distintas, no existe esta opción si sólo tenemos un método Imprime(). Una solución simple consiste en crear una clase para imprimir: public class ImprimeVehiculo{ public void Imprime(Vehiculo vehiculo){ System.out.println(vehiculo.toString()); } } Si fueran necesarias distintas variaciones para presentar la misma clase de forma diferente (por ejemplo, texto plano y HTML), siempre se puede crear una interfaz y crear implementaciones específicas. Otro ejemplo el de objetos a los que se les añade el método save(). Una vez más, la capa de lógica y la de persistencia deberían permanecer separadas. El Principio de Responsabilidad Única es indispensable para proteger el código frente a cambios, ya que implica que sólo haya un motivo por el que modificar una clase. Principio Open/Closed Este principio dice que una entidad de software debería estar abierta a extensión pero cerrada a modificación. Es decir, será necesario extender el comportamiento de las clases sin necesidad de modificar su código. Esto ayudará a seguir añadiendo funcionalidad con la seguridad de que no afectará al código existente. Nuevas funcionalidades implicarán añadir nuevas clases y métodos, pero en general no debería suponer modificar lo que ya ha sido escrito. La forma de hacer esto dando a las clases una única responsabilidad, de modo que sea posible añadir nuevas características que no les afecten. Esto no significa que cumpliendo el primer principio se cumpla automáticamente el segundo El principio Open/Closed se suele resolver utilizando polimorfismo. En vez de obligar a la clase principal a saber cómo realizar una operación, delega ésta a los objetos que utiliza, de tal forma que no necesita saber explícitamente cómo llevarla a cabo. Estos objetos tendrán una interfaz común que implementarán de forma específica según sus requerimientos. ¿Cómo detectar que estamos violando el principio Open/Closed? Una de las formas más sencillas para detectarlo es darnos cuenta de qué clases modificamos más a menudo. Si cada vez que hay un nuevo requisito o una modificación de los existentes, las mismas clases se ven afectadas, podemos empezar a entender que estamos violando este principio. Ejemplo Tenemos una clase con un método que se encarga de dibujar un vehículo por pantalla. Cada vehículo tiene su propia forma de ser pintado. Nuestro vehículo tiene la siguiente forma: public class Vehiculo { public TipoVehiculo getType(){ ... } ... } Es una clase que especifica su tipo mediante un enumerado. Podemos tener un enum con un par de tipos: public enum TipoVehiculo { COCHE, MOTO } El método de la clase que se encarga de pintarlos: public void pintar(Vehiculo vehiculo) { switch (vehiculo.getType()) { case COCHE: pintarCoche(vehiculo); break; case MOTORBIKE: pintarMoto(vehiculo); break; } } Mientras no sea necesario pintar más tipos de vehículos ni se vea este switch repetido en varias partes, no sería necesario modificarlo. Incluso por el hecho de que cambie la forma de dibujar un coche o una moto estaría encapsulado en sus propios métodos y no afectaría al resto del código. Pero puede llegar un punto en el que necesitemos dibujar un nuevo tipo de vehículo, y luego otro… Esto implica crear un nuevo enumerado, un nuevo case y un nuevo método para implementar el dibujado. En este caso si hay que aplicar el principio Open/Closed. El paso evidente es sustituir ese enumerado por clases reales, y que cada clase sepa cómo pintarse: public abstract class Vehiculo { ... public abstract void pintar(); } public class Coche extends Vehiculo { Override public void pintar() { // Pinta el coche } } public class Moto extends Vehiculo { Override public void pintar() { // Pinta la moto } } Ahora el método anterior se reduce a: public void pintar(Vehiculo vehiculo) { vehiculo.pintar(); } Añadir nuevos vehículos ahora es tan sencillo como crear la clase correspondiente que extienda de Vehiculo: public class Camion extends Vehiculo { Override public void pintar() { // Pinta el camión } } Este ejemplo choca con el Principio de Responsabilidad Única. Esta clase está guardando la información del objeto y la forma de pintarlo. ¿Implica eso que es incorrecto? No necesariamente. Tendremos que evaluar si el hecho de tener el método pintar en nuestros objetos afecta negativamente la mantenibilidad y testabilidad del código. En ese caso habría que buscar alternativas. Una alternativa para cumplir ambos requisitos sería aplicar este polimorfismo a clases que sólo tengan un método de pintado y que reciban el objeto a pintar por constructor. Tendríamos por tanto un PintarCoche que se encargue de pintar coches o un PintarMoto que dibuje únicamente motos, todos ellos implementando pintar(), que estaría definido en una clase o interfaz padre. ¿Cuándo debemos cumplir con este principio? Como el resto de principios, sólo será aplicable si es realmente necesario. Intentar hacer un código 100% Open/Closed es prácticamente imposible, y puede hacer que sea ilegible e incluso más difícil de mantener. Las reglas SOLID son ideas muy potentes, pero hay que aplicarlas donde corresponda sin obsesionarse con cumplirlas en cada punto del desarrollo. Es más sencillo limitarse a usarlas cuando haya surgido la necesidad real. Principio de sustitución de Liskov El principio de sustitución de Liskov nos dice que si en alguna parte del código se utiliza una clase, y esta clase es extendida, tenemos que ser capaces de utilizar cualquiera de sus clases hijas y que el programa siga siendo válido. Esto obliga a asegurarse de que al extender una clase no se altera el comportamiento de la clase padre. Este principio desmiente la idea de que las clases son una forma directa de modelar la realidad. Esto no siempre es así. ¿Cómo detectar que estamos violando el principio de sustitución de Liskov? Si se crea una clase que se extiende de otra, pero uno de los métodos sobra, y no se sabe qué hacer con él. Las opciones más rápidas son dejarlo vacío, o lanzar una excepción cuando se use, asegurándote de que nadie llama incorrectamente a un método que no se puede utilizar. Si un método sobrescrito no hace nada o lanza una excepción, probablemente no se esté cumpliendo el principio de sustitución de Liskov. Si los tests de la clase padre no funcionan para la hija, también se está violando este principio. Ejemplo Si intentamos modelar un cuadrado como una concreción de un rectángulo. public class Rectangulo { private int ancho; private int alto; public int leerAncho() { return ancho; } public void ponerAncho(int ancho) { this.ancho = ancho; } public int leerAlto() { return alto; } public void ponerAlto(int alto) { this.alto = alto; } public int calcularArea() { return ancho * alto; } } Un test que comprueba el área: public void testeaArea() { Rectangulo r = new Rectangulo(); r.ponerAncho(5); r.ponerAlto(4); assertEquals(20, r.calcularArea()); } La definición del cuadrado sería la siguiente: public class Cuadrado extends Rectangulo { Override public void ponerAncho(int ancho) { super.ponerAncho(ancho); super.ponerAlto(ancho); } Override public void ponerAlto(int alto) { super.ponerAlto(alto); super.ponerAncho(alto); } } Ahora en el test si se cambia el rectángulo por un cuadrado. Este test no se cumple, el resultado sería 16 en lugar de 20. Esta violando el principio de sustitución de Liskov. ¿Cómo se soluciona? Ampliando esta jerarquía de clases. Se pueden extraer a otra clase padre las características comunes y hacer que la antigua clase padre y su hija hereden de ella. Al final lo más probable es que la clase tenga tan poco código que se sustituya por un interfaz. Esto no supone ningún problema. public interface IRectangulo { int leerAncho(); int leerAlto(); int calculaArea(); } public class Rectangulo extends IRectangulo { ... } public class Cuadrado extends IRectangulo { ... } Para este caso en particular, la solución es más sencilla. No se cumple que un cuadrado es un rectángulo porque estamos dando la opción de modificar el ancho y alto después de la creación del objeto. Para solventar esta situación se debe utilizar la inmutabilidad. La inmutabilidad consiste en que una vez que se ha creado un objeto, el estado del mismo no puede volver a modificarse. La inmutabilidad tiene múltiples ventajas, entre ellas un mejor uso de memoria o seguridad en múltiples hilos de ejecución. public class Rectangulo { public final int ancho; public final int alto; public Rectangulo(int ancho, int alto) { this.ancho = ancho; this.alto = alto; } } public class Cuadrado extends Rectangulo { public Cuadrado(int lado) { super(lado, lado); } } Al instanciar el objeto, lo que hagamos con él será válido, ya usemos un rectángulo o un cuadrado. El problema era que la asignación de una parte del estado modificaba otro campo. Pero, con este nuevo enfoque, al no permitir las modificaciones, el funcionamiento de ambas clases es totalmente predecible. El principio de Liskov ayuda a utilizar la herencia de forma correcta, y a tener más cuidado a la hora de extender clases. Ahorrará muchos errores derivados de modelar lo que vemos en la vida real en clases siguiendo la misma lógica. No siempre hay una modelización exacta, por lo que este principio nos ayudará a descubrir la mejor forma de hacerlo. Principio de Segregación de Interfaces Ninguna clase debería depender de métodos que no usa. Cuando se crean interfaces que definan comportamientos, es importante estar seguros de que todas las clases que implementen esas interfaces vayan a necesitar y ser capaces de agregar comportamientos a todos los métodos. En caso contrario, es mejor tener varias interfaces más pequeñas. Las interfaces ayudan a desacoplar módulos entre sí. Siempre es posible crear una clase que lo implemente de modo que cumpla las condiciones. El módulo que describe la interfaz no tiene que saber nada sobre nuestro código y, sin embargo, nosotros podemos trabajar con él sin problemas. Cuando esas interfaces intentan definir más cosas de las debidas, lo que se denominan fat interfaces. Ocurrirá que las clases hijas acabarán por no usar muchos de esos métodos, y habrá que darles una implementación. Muy habitual es lanzar una excepción, o simplemente no hacer nada. Pero, esto es peligroso. Si lanzamos una excepción, el módulo que define esa interfaz utilizará el método en algún momento, y hará fallar el programa. El resto de implementaciones puede generar efectos secundarios que no esperamos, y a los que sólo podemos responder conociendo el código fuente del módulo en cuestión, cosa que tampoco se debe hacer. ¿Cómo detectar que estamos violando el Principio de segregación de interfaces? Si al implementar una interfaz, uno o varios de los métodos no tienen sentido y es necesario dejarlos vacíos o lanzar excepciones, es muy probable que se esté violando este principio. Es mejor divídir la interfaz en varias interfaces que definan comportamientos más específicos. Ejemplo Una tienda de CDs de música, y que tiene modelados los productos así. public interface Producto { String leerNombre(); int leerCantidad(); int leerNumeroDisco(); Date leerFechaPublicacion(); } public class CD implements Producto { ... } El producto tiene una serie de propiedades que la clase CD sobrescribirá. Si se amplía para DVDs. El problema es que para los DVDs se necesita almacenar también la clasificación por edades. Lo más directo sería simplemente añadir la nueva propiedad a la interfaz: public interface Producto { ... int leerEdadRecomendada(); } Ahora los CDs se ven obligados a implementar leerEdadRecomendada(), pero no lo van a utilizar, así que lanzarán una excepción: public class CD implements Producto { ... Override public int leerEdadRecomendada() { throw new UnsupportedOperationException(); } } Se forma una dependencia en la que cada vez que se añade algo a Producto, hay que modificar CD con cosas que no necesita. public interface DVD extends Producto { int leerEdadRecomendada(); } Y hacer que las clases se extiendan de aquí. Esto soluciona el problema a corto plazo, pero otras cosas pueden seguir sin funcionar bien. Si hay otro producto que necesite categorización por edades, necesitaremos repetir parte de esta interfaz. Esto no permitiría realizar operaciones comunes a productos que tengan esta característica. La alternativa es segregar las interfaces, y que cada clase utilice las que necesite. Se crea una nueva interfaz. public interface ClasificacionEdad { int getRecommendedAge(); } Y ahora la clase DVD implementará los dos interfaces: public class CD implements Producto { ... } public class DVD implements Producto, ClasificacionEdad { ... } La ventaja es que ahora es posible tener código ClasificacionEdad, y todas las clases que implementen esta interfaz podrían participar con código común. Si además de productos vendemos actividades, que necesitarían una interfaz diferente. Estas actividades también podrían implementar la interfaz ClasificacionEdad, y podemos tener este código, independientemente del tipo de producto o servicio que vendamos: public void chequeaUsuarioPuedeComprar(Usuario usuario, ClasificacionEdad){ return usuario.leeEdad() >= ClasificacionEdad.leeEdadRecomendada(); } ¿Qué hacer con código antiguo? Si ya tenemos código que utiliza fat interfaces, la solución consiste en utilizar el patrón de diseño “Adapter”. El patrón Adapter permite convertir unas interfaces en otras, por lo que es posible utilizar adaptadores que conviertan la interfaz antigua en la nueva. Principio de inversión de dependencias Este principio será el que más necesario para hacer que el código sea testable y mantenible. Permite que el código no dependa de los detalles de implementación, como pueden ser el framework, la base de datos, cómo se conecte al servidor, etc. Todos estos aspectos se especificarán mediante interfaces, y el núcleo no tendrá que conocer cuál es la implementación real para funcionar. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones. Cuando un módulo depende de otro, se crea una nueva instancia y se utiliza sin más complicaciones. Esta forma de hacer las cosas, que a primera vista parece la más sencilla y natural, traerá bastantes problemas posteriormente, entre ellos: Las parte más genérica del código (el dominio o lógica de negocio) dependerá de los detalles de la implementación. Esto no es bueno, porque no podrá ser reutilizado, ya que estará acoplado al framework que utilizamos, o la forma de persistir los datos, etc. Si se cambia algo de eso, tenemos que rehacer también la parte más importante del programa. No quedan claras las dependencias. Si las instancias se crean dentro del módulo que las usa, es mucho más difícil detectar de qué depende el módulo y, por tanto, es más difícil predecir los efectos de un cambio en uno de estos módulos. También nos costará más tener claro si estamos violando algunos otros principios, como el de responsabilidad única. Es muy complicado hacer tests. Si la clase depende de otras y no hay forma de sustituir el comportamiento de esas otras clases, no se puede testar de forma aislada. Si algo en los tests falla, no hay forma de saber de un primer vistazo qué clase es la culpable. ¿Cómo detectar que estamos violando el Principio de inversión de dependencias? Cualquier instanciación de clases complejas o módulos es una violación de este principio. Si al escribir un test, en cuanto no se pueda probar esa clase con facilidad porque dependa del código de otra clase será un buen indicio. Es necesario utilizar alguna alternativa para suministrarle lass dependencias. Por ejemplo mediante un constructor o mediante setters (propiedades que lo único que hacen es asignar un valor). Utilizar un inyector de dependencias. Un módulo que se encarga de instanciar los objetos que se necesitan y pasárselos a las nuevas instancias de otros objetos. Se puede hacer una inyección muy sencilla a mano, o utilizar alguna de las librerías que existen si necesitamos algo más complejo. Ejemplo Tenemos una cesta de la compra que lo que hace es almacenar la información y llamar al método de pago para que ejecute la operación. El código sería así: public class CestaCompra { public void comprar(Tiendas tiendas) { SqlDatabase db = new SqlDatabase(); db.save(tiendas); TarjetaCredito tarjetacredito = new TarjetaCredito(); tarjetacredito.pagar(tiendas); } } public class SqlDatabase { public void guardar(Tiendas tiendas){ // Guarda los datos en una base de datos SQL } } public class TarjetaCredito { public void pagar(Tiendas tiendas){ // Ejecuta un pago utilizando una tarjeta de crédito } } Aquí se incumplen todas las reglas. Una clase de más alto nivel, como es la cesta de la compra, está dependiendo de otras de alto nivel, como el mecanismo para almacenar la información o para realizar el método de pago. Se encarga de crear instancias de esos objetos y después utilizarlas. Si deseamos añadir métodos de pago, o enviar la información a un servidor en vez de guardarla en una base de datos local. No hay forma de hacer todo esto sin desmontar toda la lógica. El Primer paso consiste en dejar de depender de concreciones. Se crean interfaces que definan el comportamiento que debe dar una clase para poder funcionar como mecanismo de persistencia o como método de pago: public interface Persistencia { void guardar(Tiendas tiendas); } public class SqlDatabase implements Persistencia { Override public void guardar(Tiendas tiendas){ // Guarda los datos en una base de datos SQL } } public interface MetodoPago { void pagar(Tiendas tiendas); } public class TarjetaCredito implements MetodoPago { Override public void pagar(Tiendas tiendas){ // Ejecuta el pago con tarjeta de crédito } } Ahora ya no se depende de la implementación particular. Pero aún hay que seguir instanciándolo en CestaCompra. El segundo paso es invertir las dependencias. Estos objetos se deben pasar por constructor: public class CestaCompra { private final Persistencia persistencia; private final MetodoPago metodoPago; public CestaCompra(Persistencia persistencia, MetodoPago metodoPago) { this.persistencia = persistencia; this.metodoPago = metodoPago; } public void comprar(Tiendas tiendas) { persistencia.guardar(tiendas); metodoPago.pagar(tiendas); } } Si ahora queremos pagar con Paypal y guardar los datos en el servidor, definimos las concreciones específicas y se las pasamos al constructor de la clase CestaCompra. public class Server implements Persistencia { Override public void guardar(Tiendas tiendas) { // Guarda los datos en el servidor } } public class Paypal implements MetodoPago { Override public void pagar(Tiendas tiendas) { // ejecuta el pago utilizando una cuenta de PayPal } } Este mecanismo nos obliga a organizar el código de una forma distinta a como estamos acostumbrados, y en contra de lo que la lógica dicta inicialmente, pero a la larga compensa por la flexibilidad que otorga a la arquitectura de nuestra aplicación.