El Principio de Incertidumbre de Heisenberg
Para aquellos que todavía dudan entre aceptar mi cordura o llegar a la conclusión de que estoy como una coladera, ahí van mis últimas reflexiones sobre las leyes físicas del Universo y la Programación Orientada a Objetos. Tengo que confesar, en primer lugar, que soy un físico frustrado. Un profesional frustrado es algo peligroso, sobre todo en el caso de los informáticos; ahí está el ejemplo de B.G. ¡Cuántos horrores nos habríamos ahorrado si una mano piadosa hubiera apartado en su infancia a este señor de los ordenadores (aunque fuese a collejas)!
Bien, pues lo mío es la Física. Y las partes complicadas, nada menos: mecánica cuántica, teoría general de la relatividad y cosas así de entretenidas. Incluso a veces imagino que el Universo es un gran ordenador, que se va inventando las leyes a la vez que ejecuta las leyes antiguas. "It from bit", como diría John Archibald Wheeler, el famoso físico.
LA CAJA NEGRA
¿Por qué todo el preámbulo anterior? Resulta que he estado leyendo el famoso libro sobre patterns, "Design Patterns", de E. Gamma et al. No voy a dar aquí mi opinión; todavía no he recibido la divina iluminación que prometen los autores. Pero me ha llamado bastante la atención la ausencia de cierto patrón muy frecuente: lo llamaré la "caja negra".
Pondré un ejemplo de una aplicación real para que comprendáis más fácilmente de qué se trata. Me traigo entre manos un proyecto para una financiera, y me las estoy viendo con préstamos, hipotecas, intereses variables y toda esa parafernalia. Muy importante para la aplicación es el cálculo de intereses y tablas de amortización. Si se tratara únicamente de los casos más sencillos, me valdría quizás una función para poder calcular las condiciones de un préstamo. Pero, aunque quizás no me crea, existen decenas de variables que alteran la fórmula que se emplea.
¿Qué hago, programo una gran e inmensa función con sepetecientos parámetros? ¡No, odio las funciones con muchos parámetros! Casi nunca te acuerdas del orden de los mismos, y casi siempre tienes que pasar sencillamente ciertos valores predeterminados. Además, el cálculo de intereses no ofrece como resultado un sencillo valor escalar, sino toda una tribu de ellos, sin contar la tabla de amortización, que es una estructura vectorial.
SIMULANDO UN PRESTAMO
Evidentemente, de lo que se trata es de diseñar una clase de objetos para representar
la parte de "cálculo" de un préstamo (un préstamo real tiene muchas otras
interfaces que implementar). No me alcanzaría el espacio para presentar también la
implementación, pero de lo que se trata, precisamente, es del diseño de la interfaz.
Ahí va una primera aproximación a lo que necesitamos (omitiré muchos atributos del
préstamo):
type
TisLoan = class(TComponent) // La "is" es por IntSight
// ...
public
property Financed: Currency; // La cantidad
property Term: Word; // El plazo
property IntRate: Double; // El interés
public
property FirstPmt: Currency; // La primera cuota
property RegularPmt: Currency; // Cuota regular
property COB: Currency; // Coste del préstamo
// Tabla de amortización (saldo, capital pagado, intereses)
property Balance[PmtNo: Word]: Currency;
property Principal[PmtNo: Word]: Currency;
property Interest[PmtNo: Word]: Currency;
end;
Se puede apreciar cómo se produce una clara división de las propiedades de la clase:
hay tres de ellas que posiblemente serán de lectura y escritura (las tres primeras). Las
restantes, en nuestro modelo simplificado, serán de sólo lectura. El problema consiste
en cuándo debemos activar el algoritmo de cálculo de intereses.
MAGNITUDES OBSERVABLES
Y aquí es dónde me voy a inspirar en la física y la filosofía. La forma de trabajo
que le voy a proponer es la siguiente: para realizar el cálculo de cuotas, tablas de
amortización y el resto de estos asuntos tan excitantes como un partido de petanca
(habrá quien se ponga cachondo con esto, digo yo), debemos asignar, en el orden que se
nos antoje, las propiedades "de entrada" de nuestro modelo. Mientras asignamos
estas propiedades, ¿qué valor deben ir tomando las propiedades "de salida"?
¡Ah, amigo!, eso será un misterio.
Había cierto señor Berkeley que sostenía más o menos que el mundo era un invento
nuestro: a eso se le llama idealismo, y desde el punto de vista lógico no es tan
disparatado como puede parecer. Para refutar su teoría le plantearon el conocido
"experimento de la moneda", que hacía muchos años habían ingeniado los
griegos. Un viajero tiene una moneda en el bolsillo. Según los materialistas, la moneda
simplemente "existe", pero según Berkeley la moneda existe dentro de la mente
del viajero. Aceptémoslo. En un descuido la moneda se cae en una encrucijada del camino
(el viajero tiene agujeros en los bolsillos). Un par de millas más adelante, (Berkeley
era inglés, claro) el viajero tantea su ropa y nota la ausencia de la moneda. La da por
perdida.
Un año después, algún tonto (siempre tienen suerte) pasa por el cruce de caminos y
encuentra la moneda. Lo que hará con ella ya no es problema nuestro. La pregunta es: ¿se
trata de la misma moneda? Si es la misma moneda, ¿qué es lo que garantiza la continuidad
de su existencia durante todo el tiempo que permanece perdida? Berkeley (se me olvidó
decir que era clérigo) respondió que durante ese intervalo la moneda había seguido
existiendo "...porque estaba a la vista de Dios". No le gustaba perder una
discusión.
Anécdotas aparte, resulta que algo en cierto modo similar ocurre en la física
cuántica. ¿Cuál es el estado de un electrón, digamos que su posición y velocidad, en
un momento dado? La teoría cuántica es incapaz de decirlo con exactitud. Nos consuela
con una función de densidad que adecuadamente tratada nos dice la probabilidad
de que el electrón tenga cierto estado u otro. Pero nos basta realizar una medición
sobre el mismo para que estas posibilidades "paralelas" tomen un rumbo bien
definido. "El Jardín de los Senderos que se Bifurcan", de Borges, es una
elaborada metáfora de esta parte de la física cuántica.
¿Qué tienen que ver los electrones con los préstamos? Pues que las propiedades de
salida de un préstamo mantendrán un estado indeterminado hasta que a alguien se le
ocurra preguntar el valor de alguna de ellas. En lo que queda de artículo veremos
cómo implementar clases que actúen de este modo; "cajas negras", como me gusta
llamarlas.
LA IMPLEMENTACION
El primer paso es sumamente sencillo: todas las clases tipo "caja negra"
deben tener una variable interna que indique si se han producido cambios en alguna de las
variables de entrada:
type
TisLoan = class(TComponent) // La "is" es por IntSight
protected
FDirty: Boolean;
// ...
end;
El siguiente paso consiste en implementar las escrituras de todas las propiedades de
entrada mediante métodos de la clase que marquen el atributo FDirty en las
asignaciones. Por ejemplo, la propiedad Term (el plazo del préstamo) se declara
del siguiente modo:
type
TisLoan = class(TComponent)
private
FTerm: Integer;
procedure SetTerm(Value: Integer);
public
property Term: Integer read FTerm write SetTerm;
// ...
end;
y su método de escritura se implementa así:
procedure TisLoan.SetTerm(Value: Integer);
begin
if Value <> FTerm then
begin
FTerm := Value;
FDirty := True;
end;
end;
Por su parte, el acceso en lectura a todas las propiedades de salida será implementado
mediante funciones. Por ejemplo, así se implementa la propiedad RegularPmt, que
contiene la cuota regular que debe pagarse:
type
TisLoan = class(TComponent)
private
FRegularPmt: Currency;
function GetRegularPmt: Currency;
public
property RegularPmt: Currency read GetRegularPmt;
end;
function TisLoan.RegularPmt: Currency;
begin
Recalculate;
Result := FRegularPmt;
end;
Observe el detalle fundamental: hay una llamada a un intrigante método Recalculate.
Bien, pues este Recalculate contiene el meollo de la clase:
procedure TisLoan.Recalculate;
begin
if FDirty then
begin
// Calcular los valores de salida y dejarlos en atributos privados
FDirty := False;
end;
end;
Queda clara ahora nuestra estrategia. Cada vez que modificamos una propiedad de
entrada, la clase se marca a sí misma como "sucia": los valores de salida son
potencialmente incorrectos. Pero no toma ninguna acción para corregir su estado hasta que
alguien pregunta por alguno de los valores de salida. En ese momento, la clase
"recuerda" que necesita actualizarse y calcula frenéticamente el valor
solicitado ... y todos los demás. Podemos entonces pedir cualquier otra propiedad de
salida, que ya no necesitaremos más cálculo; al menos mientras no volvamos a modificar
las condiciones iniciales.
CONCLUSIONES
Os podéis dar cuenta de que la parte verdaderamente interesante de esta clase es el algoritmo implementado dentro de Recalculate. Esto nos puede servir de guía para identificar los casos en que es conveniente una clase tipo "caja negra": cuando necesitamos encapsular una función con muchos parámetros de entrada y varios parámetros de salida. La caja negra nos permitirá ser máx flexibles en el momento de pasar los datos iniciales, pues no hace falta seguir un orden estricto en la asignación de ellos, e incluso podemos dar por buenos, en ocasiones, aquellos valores de entrada que coincidan con los valores por omisión de las propiedades correspondientes. También la consulta de los parámetros de salida se convierte en algo sencillo, pues podemos preguntar por ellos de uno en uno, y eficiente, pues no necesitamos recalcular en cada pregunta.
Por supuesto, hay que tener cuidado al implementar Recalculate y disparar excepciones cuando no disponemos de algunos de los valores de entrada, o si tienen valores inconsistentes.
|