Extendiendo el polimorfismo…

… o “Diciendo estupideces sobre orientación a objetos”. Sigan leyendo el post y después se darán cuenta de cuál es el título que tiene que ir.

Aproximando una función

La semana que viene entregamos el TP final de DIED. Se trata de una especie de calculadora avanzada. Comenzó como un evaluador de expresiones. Luego se extendió para soportar funciones de usuario personalizadas, como “sumar(a,b) = a + b”, y finalmente el software tiene que graficar funciones, y también aproximar con la técnica de diferencias divididas (disculpen el habérselo recordado). Con respecto a esto último, uno va agregando puntos en un eje cartesiano, los mueve, lo borra, y el polinomio se va generando/graficando en vivo y en directo.

Otra característica (en este caso del módulo graficador) es que se puedan graficar expresiones en función de y. Ya había clases especiales para evaluar una expresión, pero sólo en función de x: un analizador (que se encargaba de tokenizar la expresión), un evaluador, una clase Expresion, etc. Utilizamos el patrón de diseño Visitor.

Graficando funciones

Ahora había que especializar aquellas clases para soportar la variable y. Lo que se podría haber hecho, simplemente, es haber modificado los originales, y listo. Que soporte otra variable además de x. Seguramente no íbamos a violar el enunciado del TP, pero ¿qué pasa si esto no es factible? (en realidad en el TP no es “tan” factible hacer simplemente eso). Es decir, si hay que especializarlas sí o sí. Lo que hay que cambiar es muy poco… simplemente agregar dos líneas en un sector del método crearTokens, que chequeen si el token (que todavía es un string) es “y”, y en ese caso crear el token Variable (ahora ya es un objeto token):

...
} else if(token.equals("y")) {
actual = new Variable(token);
}
...

Primero pensé que con sobreescribir dicho método en mi nueva clase (copiando y pegando todo el código y agregando esas dos líneas) todo iba a funciona de mil maravillas. El problema es que antes se llaman a otros métodos. La cadena de llamadas es analizar -> ->tokenizar -> crearTokens. Toda esa cadena debe ser sobreescrita también, aunque los dos primeros métodos tienen exactamente el mismo código. Esto es así porque de otra forma se estarían llamando métodos de la clase padre, que es lo que no quiero.

Se me ocurre, desconociendo totalmente cómo funciona un compilador, que esto podría ser mejor. En la clase padre, podríamos “nombrar” parte del código como sigue:

protected void crearTokens(String expresion) {
...
#sector CreandoTokens
if(token.equals("+")) {
actual = new Suma();
} else if(token.equals("-")) {
actual = new Resta();
} else if(token.equals("*")) {
actual = new Multiplicacion();
} else if(token.equals("/")) {
actual = new Division();
} else if(token.equals("^")) {
actual = new Potencia();
} else if(token.equals("!")) {
actual = new Factorial();
} else if(token.equals("%")) {
actual = new Resto();
}
#endsector
...
} // Fin del método 'crearTokens'

Luego en la clase hija, sobreesribimos sólo esa porción del método (agregamos dos líneas al principio del sector “CreandoTokens”):

protected void crearTokens(String expresion) {
...
#sector CreandoTokens
if(token.equals("y")) {
actual = new Variable(token);
} else if(token.equals("+")) {
actual = new Suma();
} else if(token.equals("-")) {
actual = new Resta();
} else if(token.equals("*")) {
actual = new Multiplicacion();
} else if(token.equals("/")) {
actual = new Division();
} else if(token.equals("^")) {
actual = new Potencia();
} else if(token.equals("!")) {
actual = new Factorial();
} else if(token.equals("%")) {
actual = new Resto();
}
#endsector
...
} // Fin del método 'crearTokens'

Y lo mismo para los demás métodos de la cadena (analizar y tokenizar). Sobreescribiríamos un sector de dichos métodos, justo donde se llama al siguiente, para que así quede en claro qué metodos de cuáles clases utilizar.