Siguiendo con esta línea de post, comparativos entre Java y .NET, casi objetiva, xD, Tal vez es porque las prácticas de la facultad me caen en Java, y cuando me pongo a ver un ejercicio o algo, siempre termino en Google, preguntándome/le porque el NetBeans no me colorea la palabra Operator y la respuestas que me he encontrado son que java no soporta la sobrecarga de operadores , aunque según he leído si lo utiliza internamente, esto es fácilmente deducible ya que para concatenar 2 cadenas de caracteres se utiliza el signo + al igual que para sumar 2 enteros.
No pienso enrollarme mucho en este post, existen multitud de tutoriales donde explican como funciona la sobrecarga de operadores, tan solo os voy a plasmar el código de una práctica de la facultad en una y otra plataforma, y dejaré que seáis vosotros los que opinéis. La practica requería implementar una clase para trabajar con fracciones matemáticas, también llamados quebrados, a muchos al igual que a mi seguro se os ocurriría que es el típico ejercicio para utilizar la sobrecarga de operadores, al igual que si os comentan el problema de hallar el factorial de un número, se os puede venir a la mente que es el típico ejercicio de recursividad, pero como ya comenté, java no los soporta.
Este sería el código de clase en Java ( a ver si se colorea bien diciendo que es C#)
1:
2: class QuebradoException extends Exception{
3: public QuebradoException() {}
4: public QuebradoException(String mensaje) {
5: super(mensaje);
6: }
7: }
8:
9: public class Quebrado implements Cloneable
10: {
11: private int n;
12: private int d;
13:
14: /*---------- Constructores ----------- */
15: public Quebrado(int n, int d) {
16: this.set(n,d);
17: }
18: Quebrado(int n) {
19: this(n,1);
20: }
21:
22: /*---------- Métodos de consulta ----------*/
23: public int num() {
24: return(this.n);
25: }
26: public int den() {
27: return(this.d);
28: }
29:
30: public Quebrado por(Quebrado q) {
31: return(new Quebrado( this.num()*q.num(), this.den()*q.den() ));
32: }
33:
34: public Quebrado sum(Quebrado q) {
35: return(new Quebrado( this.num()*q.den()+q.num()*this.den(), this.den()*q.den() ) );
36: }
37:
38: public Quebrado res(Quebrado q) {
39: return(sum(q.por(new Quebrado(-1))));
40: }
41:
42: public Quebrado div(Quebrado q) throws QuebradoException {
43: if(q.num()==0) {
44: throw (new QuebradoException());
45: }
46:
47: return(new Quebrado( this.num()*q.den(), this.den()*q.num() ));
48:
49: }
50: public boolean equals(Quebrado q) {
51:
52: // Dos numeros son iguales si al dividirlos obtenemos 1 o, en el caso de los quebrados,
53: // si el numerador y denominador de la división son iguales
54: return( this.num()*q.den()==this.den()*q.num() );
55: }
56:
57: public static int mcd(int N, int D) {
58: int a; // dividendo
59: int b; // divisor
60: int r; // resto
61:
62: // algoritmo de Euclides
63: a = Math.abs(N);
64: b = Math.abs(D);
65:
66:
67: while (b!=0) {
68: r = a % b;
69: a = b;
70: b = r;
71: }
72: return(a);
73: }
74:
75: public void set(int n, int d) {
76:
77: if(n*d<0)
78: { n=-1*Math.abs(n); }
79: else
80: { n=Math.abs(n); }
81: d=Math.abs(d);
82:
83: this.n=n;
84: this.d=d;
85: }
86:
87: public void simplifica() {
88:
89: int mimcd=0;
90: int nn=Math.abs(this.num());
91: int dd=Math.abs(this.den());
92:
93: if(nn>=dd)
94: { mimcd=mcd(nn, dd); }
95: else
96: { mimcd=mcd(dd, nn); }
97:
98: this.set(this.n/mimcd, this.d/mimcd);
99: }
100:
101: public String toString() {
102: return(this.num()+"/"+this.den());
103: }
104:
105: protected Object clone() throws CloneNotSupportedException{
106: // se puede hacer un clonado "simple" al no ser un objeto compuesto
107: return(super.clone());
108: }
109:
110: } // Class Quebrado
Como podéis apreciar es un código bastante sencillo, en donde podemos realizar las operaciones básicas (sumar, restar, simplificar,etc.) con las fracciones, si eres programador en .NET o en C++ o algún otro lenguaje que soporte sobrecarga de operadores, seguramente se te habría ocurrido algo similar a esto:
1:
2: Public Class QuebradoException
3: Inherits System.Exception
4: Public Sub New()
5: MyBase.New()
6: End Sub
7:
8: Public Sub New(ByVal message As String)
9: MyBase.New(message)
10: End Sub
11:
12: Public Sub New(ByVal message As String, ByVal inner As Exception)
13: MyBase.New(message, inner)
14: End Sub
15:
16: Public Sub New( _
17: ByVal info As System.Runtime.Serialization.SerializationInfo, _
18: ByVal context As System.Runtime.Serialization.StreamingContext)
19: MyBase.New(info, context)
20: End Sub
21: End Class
22:
23: Public Class Quebrado
24: Implements ICloneable
25:
26: #Region "Atributos de clase"
27: Private _n As Integer
28: Private _d As Integer
29: #End Region
30:
31: #Region "Contructores"
32: Public Sub New(ByVal n As Integer, ByVal d As Integer)
33: _n = n
34: _d = d
35: End Sub
36: Public Sub New(ByVal n As Integer)
37: Me.New(n, 1)
38: End Sub
39: #End Region
40:
41: #Region "Propiedades"
42: 'al hacer las propiedades de lectura/escritora,
43: 'no veo necesario implementar la funcion set (Vease clase Java)
44: Public Property Num() As Integer
45: Get
46: Return _n
47: End Get
48: Set(ByVal value As Integer)
49: _n = value
50: End Set
51: End Property
52:
53: Public Property Den() As Integer
54: Get
55: Return _d
56: End Get
57: Set(ByVal value As Integer)
58: _d = value
59: End Set
60: End Property
61:
62: #End Region
63:
64: #Region "Operaciones"
65: Public Shared Operator *(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
66: Return New Quebrado(q1.Num() * q2.Num(), q1.Den() * q2.Den())
67: End Operator
68:
69: Public Shared Operator +(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
70: Return New Quebrado(q1.Num() * q2.Den() + q2.Num() * q1.Den(), q1.Den() * q2.Den())
71: End Operator
72:
73: Public Shared Operator -(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
74: Return q1 + (q2 * New Quebrado(-1))
75: End Operator
76:
77: Public Shared Operator /(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
78: If q2.Num = 0 Then Throw New QuebradoException
79: Return New Quebrado(q1.Num() * q2.Den(), q1.Den() * q2.Num())
80: End Operator
81: #End Region
82:
83: #Region "Funciones"
84: Public Overrides Function Equals(ByVal obj As Object) As Boolean
85: ' Dos numeros son iguales si al dividirlos obtenemos 1 o, en el caso de los quebrados,
86: ' si el numerador y denominador de la división son iguales
87: Return (Me.Num() * CType(obj, Quebrado).Den()) = (Me.Den() * CType(obj, Quebrado).Num())
88: End Function
89:
90: Public Overrides Function ToString() As String
91: Return Num() & "/" & Den()
92: End Function
93:
94: Public Shared Function mcd(ByVal N As Integer, ByVal D As Integer) As Integer
95: Dim a, b, r As Integer 'dividendo, divisor, resto respectivamente
96: 'usamos el algoritmo de Euclides
97: a = Math.Abs(N)
98: b = Math.Abs(D)
99:
100: While b <> 0
101: r = a Mod b
102: a = b
103: b = r
104: End While
105: Return a
106: End Function
107:
108: Public Sub simplifica()
109: Dim mimcd As Integer = 0
110: Dim nn, dd As Integer
111: nn = Math.Abs(Me.Num())
112: dd = Math.Abs(Me.Den())
113:
114: 'otra forma de poner las operaciones condicionales, que terminan con una simple asignacion
115: mimcd = IIf(nn >= dd, mcd(nn, dd), mcd(dd, nn))
116: If (nn >= dd) Then
117: mimcd = mcd(nn, dd)
118: Else
119: mimcd = mcd(dd, nn)
120: End If
121:
122: Me.Num /= mimcd
123: Me.Den /= mimcd
124: End Sub
125:
126: Public Function Clone() As Object Implements System.ICloneable.Clone
127: ' Se puede hacer una copia superficial del objeto al este no ser compuesto
128: Return Me.MemberwiseClone
129: End Function
130:
131: #End Region
Bueno, pues aquí está el código de la misma clase para VB.NET, no me voy a meter a fondo a comparar los códigos, pero si señalar un par de detalles. En primer lugar como habréis visto, en esta clase sobrecargado los operadores +,-,*,/ he intentado mantener la misma estructura que marcaba el ejercicio en Java para poder hacer la comparación. Alguien podría pensar que porque no he sobrecargado el operador = para comparar 2 quebrados, bueno creo que es una buena práctica sobrescribir los métodos heredados de la clase object y también he querido aproximar mi implementación a la propuesta por el ejercicio en Java.
Escribiendo esto me estoy acordando de algo que puede ser interesante y que ahora paso a comentar. Existen algunos métodos, que por el funcionamiento de .NET, deberían implementarse aunque no le tengamos en un principio darles un uso directo, supongo que ya te habrás dado cuenta de a que me refiero. Por ejemplo, si desease comparar dos quebrados para saber cual es el mayor o cual es el menor, podría hacer una función que los comparase simplificados y sobrecargar los operadores >,<,>=,<= para obtener el mayor o el menor, creo que la forma más optima de hacer esto sería implementado la interfaz IComparable, y redefiniendo la función compareTo(No es necesario que tenga este nombre, pero si que implemente dicho interfaz) , este método, independientemente de que es el que utilizaría para los métodos de los operadores anteriormente descritos, es el que hace posible poder ordenar un colección de objetos, realizar búsquedas más efectivas, etc…, en este caso una colección de Quebrados, sin apenas esfuerzos para .NET.
Cambiando un poco de tema, el uso de “Regiones de código”, seguramente os puede parecer algo trivial, pero una vez que te acostumbras a trabajar con ellas, y a estructurar el código de alguna forma (gusto del consumidor) es algo que se hecha en falta, os imagináis programar en .NET sin Intellisense con la cantidad de namespaces, clases que hay ? Evidentemente, el uso de regiones en el código no sirve de nada si no es soportado por el IDE de desarrollo. Como anecdota, tengo la teoría de que fue algo que Microsoft se sacó de la manga con VS 2002, ya que en VB 6 y anteriores (supongo, no soy tan mayor, xD) cuando se creaba un formulario con Visual Studio 6, el IDE creaba el código del diseño del mismo (Posiciones de los controles, tamaños, etc…), pero ese código no era editable, era el propio IDE quien lo ocultaba, si querías modificarlo a “pedal”, debías abrir los archivos con algún otro editor de texto. Con la llegada de .NET y el nuevo VS, en la clase de un formulario, aparecía una Reguion llamada “Codigo del diseñador de VS”, o algo así. En esta región es donde el IDE genera el código de posiciones, instancias de controles, etc.. que nosotros modificamos desde el diseñador. Ahora no encuentro el enlace, pero os puedo prometer que, hace ya algún tiempo, he leído que había personas que no les agradaba que apareciese el código del diseñador dentro de la clase, la verdad personalmente no le veía mucho sentido a esta queja, y fue uno de los comentarios que también recuerdo haber leído con la aparición de las clases parciales
Uso de los métodos tipo propiedades: como podéis apreciar la forma de acceder a los atributos denominador y numerador de la clase se realiza mediante al menos un método de por cada atributo accesible, esto lo define el principio de ocultación de la información de las POO, en java esto es realizado por un método normal y corriente, y separando la acción de GET y la de SET en 2 métodos, en .NET, aunque también es posible hacerlo de la misma forma que en Java, existen unos métodos especiales denominados propertys que encapsulan la funcionalidad del Set y Get de los atributos en un sólo método, pudiendo definir el comportamiento del método tanto al asignar un valor como al recuperarlo. Puede suceder que no interese en un determinado problema tener un método Set, en ese caso puede definirse una propiedad como ReadOnly, en donde solo definimos la parte Set del método o análogamente también podemos definirla como WriteOnly. Y que pasa si quiero que todo el dominio de mi aplicación pueda leer un dato y solo puedan escribir el dato las clases que hereden, evidentemente en java para obtener este comportamiento, definimos el método get como public y el el método set como protected. En .NET también se puede hacer por medio de las property , es cierto que no siempre se pudo, si queríamos hacer cosas por el estilo en versiones anteriores al la v 2.0, tendríamos que optar por métodos normales como hace Java. Pero a partir de la versión v2.0 de .NET inclusive, se podría dar distinta accesibilidad a cada “submétodo” de las propertys”.
Otro punto que me parece interesante de las propiedades es que son fácilmente distinguibles del resto de métodos, cosa que en Java no. Vale ponemos un comentario, o pueden ser métodos tan sencillos que también son diferenciables, pero esta diferencia de métodos vs propiedades nos sirve para mapear campos o columnas de controles de datos para mostrar en la capa de presentación, o diferenciarlos cuando usamos reflexion. Como obtendrías dinámicamente todos los datos (atributos) de una clase y el nombre de los mismos desde Java?
De acuerdo, no estoy cumpliendo lo prometido, dije que no me iba a enrollar, pero empiezas a escribir… y bueno ya sabéis lo que pasa, creo que no me queda nada más que comentar, y a decir verdad, si me queda, lo dejaré para otro día. Nada más dejaros un par de códigos de ejemplo para probar las clases creadas y podáis observar lo sencillo que se hace utilizar la clase hecha con operadores, y lo sencillo que utilizando este método. Y al final os dejo un ejemplo de al vida real donde podréis ver las ventajas de utilizar operadores los cuales Java no soporta (esto en negrilla es para que os animéis a comentar)
Código Main en JAVA:
1: public class Main {
2:
3: public static void main(String[] args) {
4:
5: Quebrado q1=new Quebrado(2,5);
6: Quebrado q2=new Quebrado(1,10);
7: Quebrado q3=new Quebrado(10,25);
8:
9: pinta(q1,q2,'+',q1.sum(q2));
10: pinta(q1,q2,'-',q1.res(q2));
11: pinta(q1,q2,'*',q1.por(q2));
12: pinta(q1,q2,'/',q1.div(q2));
13:
14: if (q1.equals(q3))
15: pinta(q1,"Es igual que",q3);
16: else
17: pinta(q1,"NO es igual que",q3);
18:
19: if (q1.equals(q2))
20: pinta(q1,"Es igual que",q2);
21: else
22: pinta(q1,"NO es igual que",q2);
23:
24: Quebrado simple;
25: simple=(Quebrado)q3.clone();
26: simple.simplifica();
27:
28: pinta(simple,"Es simplificado de ",q3);
29:
30: }
31: private static void pinta(Quebrado q1,String oper,Quebrado q)
32: {
33: System.out.println(q1.toString()+" "+oper+" "+ q.toString());
34: }
35:
36: private static void pinta(Quebrado q1,Quebrado q2,char oper,Quebrado q)
37: {
38: System.out.println(q1.toString()+" "+oper+" "+q2.toString()+" = "+ q.toString());
39: }
40:
41: }
Código Main en VB.NET: (Si lo hubiese puesto en C# creo que apenas tendría que cambiar nada xD)
1: Module Module1
2: Sub Main()
3: Dim q1, q2, q3 As Quebrado
4:
5: q1 = New Quebrado(2, 5)
6: q2 = New Quebrado(1, 10)
7: q3 = New Quebrado(10, 25)
8:
9: pinta(q1, q2, "+", q1 + q2)
10: pinta(q1, q2, "-", q1 - q2)
11: pinta(q1, q2, "*", q1 * q2)
12: pinta(q1, q2, "/", q1 / q2)
13:
14: If (q1.equals(q3)) Then
15: pinta(q1, "Es igual que", q3)
16: Else
17: pinta(q1, "NO es igual que", q3)
18: End If
19:
20: If (q1.Equals(q2)) Then
21: pinta(q1, "Es igual que", q2)
22: Else
23: pinta(q1, "NO es igual que", q2)
24: End If
25:
26: Dim simple As Quebrado
27: simple = q3.Clone()
28: simple.simplifica()
29:
30: pinta(simple, "Es simplificado de ", q3)
31: Console.Read()
32:
33: End Sub
34:
35: Private Sub pinta(ByVal q1 As Quebrado, ByVal oper As String, ByVal q2 As Quebrado)
36: Console.WriteLine(q1.ToString() + " " + oper + " " + q2.ToString())
37: End Sub
38:
39: Private Sub pinta(ByVal q1 As Quebrado, ByVal q2 As Quebrado, ByVal oper As Char, ByVal q As Quebrado)
40: Console.WriteLine(q1.ToString() + " " + oper + " " + q2.ToString() + " = " + q.ToString())
41: End Sub
42:
43: End Module
Viendo la utilidad: podríamos tener un programa que funcionase con valores Decimales (Double) con lo cual perderíamos precisión, y lo fácil que sería migrar dicho código al trabajo con fracciones, las formulas no deberíamos cambiar nada, ya que si tenemos a+b*c/d+a y contamos con que todos los valores son decimales, y alguno puede ser 3,333333…. podríamos cambiar la definición de las variables, y no tendríamos que cambiar nada en el código ya que nuestra clase utiliza los mismos operadores, y cambiaríamos nuestro valor por 10/3 por ejemplo. Sin embargo, en Java, a parte de tener que cambiar la definición de la variable, tendríamos que variar las fórmulas de mi programa, tal que la expresión anterior: a+b*c/d+a debería cambiarlo por algo como: (a.mas(b.por(c))).div((d.mas)) teniendo evidentemente que tener mucho cuidado con los paréntesis, las operaciones, etc …
Espero que os haya gustado, este fue la práctica que me animó a escribir esta serie de post y que espero poder continuar.
Saludos: René Pacios