Ir al contenido



Foto

¿Alternativa para los parámetros por referencia?


  • Por favor identifícate para responder
12 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 22 mayo 2012 - 08:07

Hola muchachos,
Tengo unas unidades que me sirve de "API" para otras unidades clientes que la usan.
Estas unidades API contienen tipos, procedimientos y funciones que me son de base y soporte para ciertas operaciones, y está basado en el paradigma estructurado. Es decir, no hay clases... se trabaja como si fuera el Pascal de toda la vida. En la API no hay clases debido a que por naturaleza del problema ponerlo bajo el esquema OO es más engorroso, y resulta más limpio y simple si se utilizara el otro enfoque.

Pues, para algunos de los procedimientos y funciones me resulta útil tener un parámetro por referencia para devolver una variable a modo de control. Algo como:



delphi
  1. procedure HacerAlgo(... var ErrorCode: integer);
  2. procedure HacerMas(... var Status: TStatus);



Entonces en estas variable ErrorCode o Status devuelvo el valor adecuado con el que se necesita hacer ciertas evaluaciones, etc.
Como bien sabemos a estos procedimientos se los podría hacer función y que directamente regresen el valor adecuado al caso, pero en ocasiones por definición no es lo más adecuado. Además, incluso en algunas funciones también necesito de éstos.

Me preguntaba si había alguna manera de evitar tener que añadir este parámetro más. Algunos procedimientos y funciones ya de por si requieren de varios parámetros y ya como lo siento como un estorbo.

La primera opción que se vino a la cabeza fue disponer de una variable privada:


delphi
  1. var
  2. ErrorCode: integer;
  3. Status: TStatus;



Y disponer de unas funciones de lectura para a éstas: GetLastErrorCode y GetLastStatus y hacer que los procedimientos y funciones directamente trabajen con las variables.

El problema está en que habrá ocasiones en que dos o más procedimientos o funciones se realicen en "simultáneo" y cada uno por tanto requiera leer su valor de control con lo que se pisan los resultados y el último valor será el del último método en ejecutarse.

Se que esto se solucionaría con un enfoque basado en clases, pero es que me parece demasiado disponer de clases... ya de por si tengo muchas. Y además, las clases estarán prolongando la indirección ya que se requiere de tipos que son naturalmente estructurados y entonces estas clases estarán ocultando estos tipos y los usarán de forma interna.

Es decir:

Clases (Clientes) -> Clases (API) => Tipos (API)

Cuando la idea es:

Clases (Clientes) -> API (tipos, funciones y procedimientos)

De este modo las clases clientes tengan en sus atributos, de ser necesarios, aquellos tipos que ofrezca la API y aprovechar las funciones y procedimientos de la propia API. Por ejemplo:



delphi
  1. TClaseCliente1.Hacer(....);
  2. var TipoAPI: TTipoAPI;
  3. begin
  4.   HacerAlgo(TipoAPI); // llamada a un procedimiento API
  5. end;



Espero que se me entienda. Escucho alternativas.

Saludos,
  • 0

#2 ELKurgan

ELKurgan

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 519 mensajes
  • LocationEspaña

Escrito 22 mayo 2012 - 11:13

¿Y usando tipos "Record"?

No mataríamos moscas a cañonazos, como con las clases, y te proporciona muchas características similares.

Por cierto, excelente artículo de Cary Jensen sobre esto en http://caryjensen.bl...for-record.html

Un saludo
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.749 mensajes
  • LocationMadrid - España

Escrito 22 mayo 2012 - 11:13

En el caso de que no siempre haga falta usar los valores devueltos en esos parámetros, puedes, en lugar de pasarlos por referencia, pasar punteros.  Esto te permite usar valores por defecto, valor nulo.

Con esto la sintaxis es mas simple en algunas llamadas.

Saludos.
  • 0

#4 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 23 mayo 2012 - 02:51

Hola.

¿ Porqué no cambias los procedures por functions y devuelves esos valores (error/estado/...) en la función ?. Cuando no te interese ese resultado, podrás seguir usando la llamada como si fuese un procedure.

Otra cosa que podría ser útil es la sobrecarga de parámetros. Puedes tener dos definiciones de tus procedimientos, una con parámetros de error/estado/... y otra sin ellos, de manera que podrás utilizar una u otra en función de que lo necesites o temas que puedan aparecer errores.

Saludos.
  • 0

#5 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 23 mayo 2012 - 03:15

Podrías tener un TStringList (o similar) que sea global, y donde al llamar a tu function (nada de procedures, te tocaría convertirlos todos a function), si hay problemas, esta añadiría un item y en él escribe su "error code" o lo que sea, y te devuelve como respuesta el indexof de ese elemento. Si no hay error, te devuelve un -1 por ejemplo.

Sería como tener una pila de errores y poder leer el que corresponde a tu llamada a las APIs.

Ojo si las llamadas son desde distintos hilos, si todos los hilos tocan el stringlist a la vez vas a tener problemas!

Otro detalle es que cada "llamada" debería hacer un delete del item una vez leido el error code, o bien eliminar el item[0] hasta que no pases de 100 items para evitar que se coma la memoria el stack de errores.
  • 0

#6 seoane

seoane

    Advanced Member

  • Administrador
  • 1.242 mensajes
  • LocationEspaña

Escrito 23 mayo 2012 - 03:35

El problema está en que habrá ocasiones en que dos o más procedimientos o funciones se realicen en "simultáneo" y cada uno por tanto requiera leer su valor de control con lo que se pisan los resultados y el último valor será el del último método en ejecutarse.


¿Utilizas varios Threads? porque si no lo haces no hay problema, solo tendrías que llamar la función "GetLastErrorCode" justo después de cada función, al igual que se utiliza la API GetLastError de windows.

Y si los utilizas tampoco habría problemas, solo mas trabajo  :D  tendrías que guardar las variables de tal manera que fueran accesibles a todas las funciones pero con diferentes valores para cada thread.

Aquí un ejemplo en C
http://msdn.microsof...7(v=vs.85).aspx
  • 0

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 23 mayo 2012 - 07:04

¿Y usando tipos "Record"?

No mataríamos moscas a cañonazos, como con las clases, y te proporciona muchas características similares.

Por cierto, excelente artículo de Cary Jensen sobre esto en http://caryjensen.bl...for-record.html

Un saludo

Si dispusiera de Delphi 2005/6 en adelante podría hacer lo que se comenta en dicho artículo; aunque igual sería matar moscas a cañonazos. Y sinceramente no veo la utilidad de dotar a un tipo record de propiedades y métodos... para tal caso mejor declarar una clase. Me hace acordar de como crear clases en C.

En el caso de que no siempre haga falta usar los valores devueltos en esos parámetros, puedes, en lugar de pasarlos por referencia, pasar punteros.  Esto te permite usar valores por defecto, valor nulo.

Con esto la sintaxis es mas simple en algunas llamadas.

Saludos.

Me interesa esta posibilidad escafandra. ¿donde podría ver algún ejemplo de uso? Tu sabes que yo le huyo a los punteros y nos uso a menos que se me haga necesario  :p  :D  *-)

Hola.
¿ Porqué no cambias los procedures por functions y devuelves esos valores (error/estado/...) en la función ?. Cuando no te interese ese resultado, podrás seguir usando la llamada como si fuese un procedure.

Fue lo primero que se pasó por la cabeza. Hacer que sean funciones, pero en cuanto empecé a seguir implementando algunas de las funcionalidades me quedó un sabor a "algo no está bien" y que algunas de estas funciones deberían tener nombre y comportamiento de procedimientos. Asi es que luego pasé al enfoque de parámetros por referencia y convertí a esas "funciones procedure" en procedure con el parámetro adecuado. Y así es como ahora llegué a esta situación.

Otra cosa que podría ser útil es la sobrecarga de parámetros. Puedes tener dos definiciones de tus procedimientos, una con parámetros de error/estado/... y otra sin ellos, de manera que podrás utilizar una u otra en función de que lo necesites o temas que puedan aparecer errores.

Saludos.

Ya algunos están sobrecargados amigo. Y no es posible evitar de esta forma los parámetros de control, ya sea que se utilice cualquiera de las funciones o procedimientos sobrecargados. Por naturaleza del problema y diseño todas las versiones sobrecargadas necesitan regresar este valor para control.

Es decir tengo:



delphi
  1. procedure HacerEsto(Param1, Param2, .... var ErrorCode: integer); overload;
  2. procedure HacerEsto(Param1: TParam1; var ErrorCode: integer); overload;



Podrías tener un TStringList (o similar) que sea global, y donde al llamar a tu function (nada de procedures, te tocaría convertirlos todos a function), si hay problemas, esta añadiría un item y en él escribe su "error code" o lo que sea, y te devuelve como respuesta el indexof de ese elemento. Si no hay error, te devuelve un -1 por ejemplo.

Sería como tener una pila de errores y poder leer el que corresponde a tu llamada a las APIs.

Ojo si las llamadas son desde distintos hilos, si todos los hilos tocan el stringlist a la vez vas a tener problemas!

Otro detalle es que cada "llamada" debería hacer un delete del item una vez leido el error code, o bien eliminar el item[0] hasta que no pases de 100 items para evitar que se coma la memoria el stack de errores.

Estuve pensando en algo parecido a esa lista de errores/estado que describes. Aunque no con la forma de funciones para que regresara el índice, ya que me quería evitar las versión en funciones. Ahora me doy cuenta de que es fundamental de que se me permita regresar el índice para poder leer el error adecuado a la llamada producida.


El problema está en que habrá ocasiones en que dos o más procedimientos o funciones se realicen en "simultáneo" y cada uno por tanto requiera leer su valor de control con lo que se pisan los resultados y el último valor será el del último método en ejecutarse.


¿Utilizas varios Threads? porque si no lo haces no hay problema, solo tendrías que llamar la función "GetLastErrorCode" justo después de cada función, al igual que se utiliza la API GetLastError de windows.

No utilizo hilos... no por el momento y en esta versión; aunque no se descarta su uso.

Justamente inspirado en esa API Windows es que luego empecé a considerar esta alternativa pero me quedó la duda de si no tendré problemas por varias llamadas rápidas y que en ocasiones se realizan en forma "asincronizada".

Te explico, gracias al patrón Observador es que propago llamadas desde una clase a otras en cuanto cambia algo; gracias al poder de los eventos. Resulta ser que luego la clase que "escucha" estos eventos al ser notificada decide si le toca o no hacer algo. Ese algo en ocasiones implica hacer llamadas y uso por tanto de esta API.
Se podría distinguir 3 grupos de clases clientes (que vendría a ser parte del dominio) cada grupo tiene su función. Estos grupos se apoyan en unas clases Sujetos que son quienes les notifica los cambios y hacen uso de la API. Cuando algo cambia en un sujeto deberá propagar notificar a los observadores de los grupos registrados para que actualicen su "estado". Vendría a ser algo así:



delphi
  1. +--------+ +--------+ +--------+
  2. | Grupo1 |      | Grupo2 |      | Grupo3 |
  3. +--------+      +--------+      +--------+
  4.     ^              ^  ^              ^
  5.     |              |  |              |
  6.     |  +------+  |  |   +------+  |
  7.     +-->| Suj1 |<--+  +-->| Suj1 |<--+
  8.         +------+          +------+
  9.     ^              ^
  10.     |     |
  11.             |  +-----+    |
  12.             +-->| API |<---+
  13.                 +-----+



El diseño es tan armónico que un cambio en las condiciones del grupo1 lleva a cambios que se deben notificar hacia el 2, luego desde el grupo 2 al grupo 3. Finalmente el grupo 3 puede operar tranquilamente y no tener que notificar nada al resto a menos que se cumpla una condición. En cuanto se detecta el caso debe realizar operaciones. Esto al final se traduce a que se le notifique al sujeto1 de unos cambios y debe proceder a nuevas llamadas y notificaciones para el grupo 1, y así se produce de nuevo un nuevo ciclo.
Hagamos de cuenta que es un círculo:



delphi
  1. +--------+    +--------+    +--------+
  2. | Grupo1 |---->| Grupo2 |---->| Grupo3 |---+
  3. +--------+    +--------+    +--------+  |
  4.   ^                            |  ^      |
  5.   |                            |  |      |
  6.   |                            +---+      |
  7.   |   |
  8.   +---------------------------------------+



Técnicamente el grupo 2 también tiene un ciclo propio para que luego de una confirmación de todo su proceso interno se notifique al grupo 3. Pero aquí lo he omitido por simplicidad para explicar la condición inicial.
Inicialmente tiene lugar solamente el grupo1, al confirmar sus operaciones, el grupo 2 puede trabajar... mientras tanto el grupo 1 se "queda a la espera" de novedades (que el grupo 3, gracias a mediadores fuera de esta discusión le hará saber). Una vez que 2 confirma tiene lugar el grupo 3, y ahora el grupo 2 también está a la espera. Y finalmente el grupo 3 puede operar sin problemas.

El asunto es que llegado al punto 3, pueden darse casos en que el grupo 2 pueda recibir notificaciones y volver a realizar sus operaciones sin que tenga que pasar por el grupo 1, y estos cambios no necesariamente van a afectar a que 3 tenga que actualizar sus operaciones (ya que de hecho, solo toma lo procesado por el grupo 2). De este modo de allí que el grupo 2 y 3 hagan uso de la API en "simultáneo". Solo en casos especiales en que el grupo 3 detecta es que tiene lugar un nuevo ciclo completo:

Entonces los ciclos, son:



delphi
  1. +--------+    +--------+    +--------+
  2. | Grupo1 |---->| Grupo2 |---->| Grupo3 |---+
  3. +--------+    +--------+    +--------+  |
  4.   ^              |  ^          |  ^    |
  5.   |              |  |          |  |    |
  6.   |              +---+          +---+    |
  7.   |   |
  8.   +---------------------------------------+


Se han omitido los sujetos que hacen de intermediadores entre cada grupo a fin de que el "dibujo" se vea más claro. Naturalmente es que estos sujetos son los encargados de ir dando aviso de un grupo a otro y trabajan en forma directa con la API.

Entonces me asaltó la duda de si estos sujetos que actúan con la API  podrán trabajar sin problemas sin pisarse la cola mutuamente. ¿Tu dices que no debería haber problemas? De ser así pruebo este enfoque y me voy a por eso directamente y me dejo de llorar por la cantidad de parámetros... de última veo la posibilidad de descomponer más a las funciones y procedimientos y de ese modo reducir la cantidad de parámetros.

Otro motivo por el cual utilizo esta forma de API es que es de propósito general. No es algo especializada y única para este problema... dispone de las herramientas para varias aplicaciones.
Son los sujetos que actúan con ella quienes ya ofrecen una interfaz adecuada al resto del dominio.

Y si los utilizas tampoco habría problemas, solo mas trabajo  :D  tendrías que guardar las variables de tal manera que fueran accesibles a todas las funciones pero con diferentes valores para cada thread.

Aquí un ejemplo en C
http://msdn.microsof...7(v=vs.85).aspx

ha... ha... hachús..... snif... snif ¿Alguien tiene pañuelo? Ver C me enfermó  :D  :p

Saludos,
  • 0

#8 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.749 mensajes
  • LocationMadrid - España

Escrito 23 mayo 2012 - 07:52

Me interesa esta posibilidad escafandra. ¿donde podría ver algún ejemplo de uso? Tu sabes que yo le huyo a los punteros y nos uso a menos que se me haga necesario  :p  :D  *-)


Te pongo un ejemplo muy simple:


delphi
  1. function Divide(N, D: integer; resto: pinteger = nil): integer;
  2. begin
  3.   Result:= N div D;
  4.   if(resto<>nil) then  resto^:= N mod D;
  5. end;



La función devuelve la división de enteros pero si le pasamos el parámetro resto, entonces nos devuelve en él el resto de la división.


delphi
  1. var
  2. C,  R: integer;
  3. begin
  4. C:=  Divide(20, 3, @R);  // En C tendremos 6 (C = 6) y en R tendremos 2  (R = 2)



Saludos.

  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 23 mayo 2012 - 08:22

Hola escafandra,
¿Y ese mismo principio lo podría utilizar para cualquier tipo?
Por ejemplo digamos que tengo algo como:



delphi
  1. TStatus = (sEstado1, sEstado2, sEstado3);



Si yo tuviera que pasar un puntero de este tipo ¿tendría que hacer algo como esto?:



delphi
  1. type
  2. PStatus = ^TSatus;



De modo que:



delphi
  1. procedure HacerAlgo(..., status: PStatus);



Y la llamada sería:



delphi
  1. HacerAlgo(..., @status);



¿Es así?

Ahora que lo pienso esto sería igual que tener el parámetro var, y estaría en la misma.  ^o|

¿Y se podrá hacer una combinación de este puntero y en vez de un GetLastError un DefineVariableError()? Es decir, indicarle a la API que deje el resultado de control y estado en una variable previamente definida... yo me digo si se le podría pasar el puntero a dicha variable de modo que al finalizar la operación ya quede en ella el resultado. De este modo cada vez que se necesite invocar a una llamada de la API definirle la variable.

Aunque... creo que esto tampoco evita el problema de varias llamadas a la vez... es más en el peor caso el primero que haga la llamada se pierde el valor de su variable. pucha  :p O me la juego a que funcione con esto de GetLastError o manejo una pila de errores.  ^o|

Saludos,
  • 0

#10 escafandra

escafandra

    Advanced Member

  • Moderadores
  • PipPipPip
  • 3.749 mensajes
  • LocationMadrid - España

Escrito 23 mayo 2012 - 08:48

Todo tu  planteamiento a cerca de los punteros es correcto. La diferencia conm el paso por referencia es que puedes asumir un valor por defecto y por lo tanto no pasar el parámetro si no lo vas a usar.

En el ejemplo que te puse puedes hacer esto:


delphi
  1. C:=  Divide(20, 3, @R);


o esto:


delphi
  1. C:=  Divide(20, 3);



En realidad lo que facilita es la sintaxis de llamada cuando no precisas dicho parámetro.


Saludos.


  • 0

#11 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 23 mayo 2012 - 09:14

Entiendo.
La ventaja de esta técnica es como dices que me evita tener que pasar el parámetro de control si le establezco la posibilidad de un valor por defecto como nulo.

El asunto es que tendría que ver hasta que punto me puedo evitarlo. No es que quiera evitarme esta variable de control, que me hace en falta y me ayuda para determinar el flujo de las operaciones. Más que nada apunto a alternativas de como disponer de esta variable de control sin tener que llenar de parámetros a mis procedimientos y funciones y a su vez poder tener un medio seguro para el acceso a esta API desde diferentes clases del dominio.

Parecería que en realidad tocará examinar si es posible una estructuración de las funciones y procedimientos para que absorban algunos de los parámetros. Estamos hablando en algunos casos de hasta 5 o 6 parámetros. Y no me gusta superar esa barrera.

Saludos,

  • 0

#12 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 25 mayo 2012 - 12:16

Hola,
Al final he estado haciendo un rediseño a la API y logré, hasta el momento, controlar la cantidad de parámetros. Me valgo de parámetros por referencia para devolver el código de control o estado. Dispongo tanto de procedimientos y funciones.

Hasta el momento la mayor cantidad de parámetros que requiere una de las funciones es de 4. Espero no llegar a los 6; de ser así evaluaré y pondré a prueba de emplear una API del tipo GetLastError como la que dispone Windows. Por el momento marcha bien, sin problemas.

No se si colocar el hilo como resuelto, quizá hay alguien más que pudiera recomendar otras opciones y alternativas que no he contemplado.

En lo que tengo que esforzarme es mejorar mi productividad... el miércoles estuve en 0,0625 KLDC/h y el jueves apenas mejoró algo con 0,1035 KLDC/h. Hoy es feriado en mi patria, al celebrar un año más del inicio de grito de independencia y el día pinta gris, con mucho frio... y considerando que me desperté a las 9 y haber dormido apenas 5 horitas... ufff ¿saben las ganas que tengo de teclear? A ver si llego a la mitad del miércoles  :p  8o|

Saludos,
  • 0

#13 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.118 mensajes
  • LocationArgentina

Escrito 15 junio 2012 - 08:42

Aportando de nuevo al hilo, al final he diseñado a la API de forma tal que se separe, parcialmente, las operaciones de los controles y variables de estado. Para ello concebí unas funciones que regresan además del valor, una variable de referencia con la variable de control/estado. Mayormente estas funciones tienen el nombre de CheckX(), o VerifyX():



delphi
  1. function CheckX(Param1, Param2... var ErrCode: integer): boolean;
  2. function VerifyX(Param1, Param2... var State: TState): tipo-dato;



Luego por otra parte están los procedimientos que implementan las operaciones, y asumen que están garantizadas las condiciones suficientes y/o necesarias para operar.
Esta división permite que:
1) Si SIEMPRE se puede tener certeza de que se va poder operar directamente invocar al procedimiento en cuestión.
2) Si no se tiene demasiado control de las situaciones, están a disposición las funciones de evaluación de modo que basta hacer algo del estilo:



delphi
  1. if CheckX(....)
  2.   then ProcedimientoX;



O más complejo como:



delphi
  1. Can := CheckX(....; Err);
  2. case Err do
  3. 0: ProcedimientoX;
  4. 1: ShowMessage('Se ha detectado error en el operando 1');
  5. end;



Este diseño me ha permitido equilibrar, además, la cantidad de parámetros ya que se han repartido entre las funciones Check/Verify y los correspondientes procedimientos. De este modo no se ha superado la barrera de los 5 parámetros (de hecho, el que más tiene es de 4).

Hasta ahora está funcionando en óptimas condiciones, y se va ampliando, ganando más peso en la medida en que se va distribuyendo las funciones más avanzadas de la API (algo como del estilo de niveles que ofrece Android) en unidades. Se podría decir que el "Kernel" de la API está ya listo para comerse. Que da gusto ver cuando las cosas resultan bien, y más cuando a la primera obtienes 0 errores.  :)  (h)  (y)

Yo doy por concluído esto.

Saludos,
  • 0