8 . D i se ñ o d e cl a se s - Universidad Michoacana de...

27
8. Diseño de clases Objetivos Diseñar clases usando la notación del Lenguaje Unificado de Modelado ( UML ); escribir código para una clase especificada; diferenciar entre acceso public y private para atributos y métodos; entender el significado del término encapsulación; pasar objetos como parámetros; desarrollar clases colección propias; comprender las ventajas de la programación orientada al objeto. 8.1 Introducción En este capítulo se revisa cómo desarrollar las clases que se necesitan para los programas. En el capítulo anterior se revisó cómo crear y usar objetos, y cómo se usan los métodos de una clase sin saber nada acerca de cómo trabajan. En este capítulo se revisan algunas de las clases usadas en el capítulo previo para revisar cómo están construidas, y cómo se pueden escribir clases propias. Se inicia con la clase Rectangulo . 8.2 Diseño de Clases con UML En el capítulo anterior se indica que una clase consiste de: un conjunto de atributos (los datos); un conjunto de métodos que pueden acceder o cambiar esos atributos. Cuando se diseña una clase se debe considerar que datos la clase necesitará guardar, y qué métodos son requeridos para acceder los datos. La clase Rectangulo necesita guardar dos elementos de datos, la base y la altura del rectángulo; estos serán números reales, así que double podría ser el tipo apropiado para estos atributos. Cuando se diseñan clases es útil iniciar usando una notación de diagrama. Esto se hace haciendo uso del Lenguaje Unificado de Modelado ( UML ). En esta 120

Transcript of 8 . D i se ñ o d e cl a se s - Universidad Michoacana de...

Page 1: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

8. Diseño de clases

Objetivos

● Diseñar clases usando la notación del Lenguaje Unificado de Modelado ( UML );

● escribir código para una clase especificada;

● diferenciar entre acceso public y private para atributos y métodos;

● entender el significado del término encapsulación;

● pasar objetos como parámetros;

● desarrollar clases colección propias;

● comprender las ventajas de la programación orientada al objeto.

8.1 Introducción

En este capítulo se revisa cómo desarrollar las clases que se necesitan para los programas. En el capítulo anterior se revisó cómo crear y usar objetos, y cómo se usan los métodos de una clase sin saber nada acerca de cómo trabajan.

En este capítulo se revisan algunas de las clases usadas en el capítulo previo para revisar cómo están construidas, y cómo se pueden escribir clases propias. Se inicia con la clase Rectangulo .

8.2 Diseño de Clases con UML

En el capítulo anterior se indica que una clase consiste de:

● un conjunto de atributos (los datos); ● un conjunto de métodos que pueden acceder o cambiar esos atributos.

Cuando se diseña una clase se debe considerar que datos la clase necesitará guardar, y qué métodos son requeridos para acceder los datos. La clase Rectangulo necesita guardar dos elementos de datos, la base y la altura del rectángulo; estos serán números reales, así que double podría ser el tipo apropiado para estos atributos.

Cuando se diseñan clases es útil iniciar usando una notación de diagrama. Esto se hace haciendo uso del Lenguaje Unificado de Modelado ( UML ). En esta

120

Page 2: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

notación, una clase está representada por una caja dividida en tres secciones. La sección primera proporciona el nombre de la clase, la sección segunda lista los atributos, y la sección tercera lista los métodos. Enseguida se muestra el diagrama de clase para Rectangulo :

Rectangulo Nombre de la clase

- base : double -altura : double Atributos

+ Rectangulo ( double , double ) + getBase () : double + getAltura () : double + setBase ( double ) + setAltura ( double ) + calcularArea () : double + calcularPerimetro (): double

Métodos

La notación UML requiere que se indiquen los nombres de los atributos junto con sus tipos, separados por dos puntos.

El concepto de encapsulación u ocultamiento de información, es la técnica de hacer los atributos accesibles sólo a los métodos de la propia clase, y esta característica de los lenguajes orientados al objeto ha contribuido a que la orientación al objeto se convierta en un estándar de programación actual. Restringiendo el acceso de esta forma, los programadores pueden mantener los datos de sus clases sellados a otras clases, porque ellos tienen el control de cómo se accede actualmente.

La forma como la clase Rectangulo ha sido diseñada impide que no se pueda usar directamente los atributos base y altura desde otra clase. Si se quiere encontrar el área del rectángulo desde el método main() de otro programa, no se puede hacer accediendo a los datos base y altura directamente, porque el acceso a esos atributos está denegado.

Para hacer lo anterior se debe llamar al método calcularArea() del objeto Rectangulo . El diseño está hecho de esta manera para evitar que alguien inadvertidamente cambie los valores de base y altura . Solo los métodos pueden acceder a los atributos.

Los signos más y menos que están en el diagrama UML de la clase Rectangulo informan la encapsulación; un signo menos significa que el atributo o método es private , es decir, es accesible solamente a métodos dentro de la misma clase. Un signo más indica que es public , es accesible desde métodos de otras clases. Es un buen hábito hacer los atributos privados, y los métodos públicos, logrando de esta forma la encapsulación.

121

Page 3: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

Para la notación de los métodos los tipos de los parámetros están encerrados entre paréntesis, por ejemplo: +setBase(double) Aquí se observa que el método setBase() requiere un parámetro double , que es el valor de la base nueva. Los tipos devueltos son colocados después de los paréntesis, precedidos por dos puntos, por ejemplo: getBase(): double Se observa que el método getBase() regresa un valor de tipo double que es el valor actual del atributo privado base.

Cuando no hay tipo regresado, no aparece nada después de los paréntesis, como en los métodos setBase() y setAltura() .

El primer método, Rectangulo() , es el constructor. El constructor siempre tiene el mismo nombre que la clase, y en este caso requiere dos parámetros de tipo double : Rectangulo(double, double)

Se debe observar que un constructor nunca tiene un tipo de regreso . Luego se verá que ni siquiera se usa void antes del nombre; si se hace el compilador pensará que es un método regular.

Para la clase Rectangulo se proporcionan métodos para leer y escribir los atributos, y es convención que el nombre de tales métodos comienzan con get y set respectivamente. No siempre es el caso que se proporcionen métodos set para cambiar los atributos. En ocasiones se configura la clase para que la única forma de asignar valores a los atributos sea vía el constructor. Esto significa que los valores de base y altura podrían ser puestos solo al momento de que un objeto Rectangulo fuera creado, y no podrían ser cambiados después. Es una política buena dar acceso de escritura solo a los atributos que claramente requieren ser cambiados durante la existencia del objeto.

8.3 Implementación de Clases

8.3.1 Clase Rectangulo

Con el diseño básico de la clase Rectangulo se puede escribir el código de esta. Se muestra enseguida su código y posteriormente se hacen algunos comentarios. Esta clase será posteriormente usada en la sección 9.4 .

public class Rectangulo { // los atributos private double base; private double altura;

122

Page 4: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

// los métodos // el constructor public Rectangulo(double baseEnt, double alturaEnt) { base = baseEnt; altura = alturaEnt; } // permite leer el atributo base public double getBase() { return base; } // permite leer el atributo altura public double getAltura() { return altura; } // permite escribir el atributo base public void setBase(double baseEnt) { base = baseEnt; } // permite escribir el atributo altura public void setAltura(double alturaEnt) { altura = alturaEnt; } // regresa el área del rectángulo public double calcularArea() { return base * altura; } // regresa el perímetro del rectángulo public double calcularPerimetro() { return 2 * (base + altura); } }

En la primera línea se declara la clase Rectangulo :

public class Rectangulo {

Enseguida están los atributos. Un objeto Rectangulo necesitará atributos para guardar valores para la base y la altura del rectángulo, y estos serán de tipo double . La declaración de los atributos en el diagrama UML es:

123

Page 5: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

- base : double -altura : double

La implementación en Java de lo anterior es:

private double base; private double altura;

Los atributos son declarados como cualquier otra variable, excepto que estos son declarados fuera de cualquier método, y además están precedidos de la palabra reservada private , que corresponde al signo menos en la notación UML. Con private se restringe el alcance de los atributos sólo a los métodos de la propia clase.

Los atributos de una clase son accesibles a todos los métodos de la clase, diferente a las variables locales, que son accesibles sólo a los métodos en dónde fueron declaradas.

Después de los atributos están los métodos de la clase Rectangulo . El primero es el constructor, el cual tiene el mismo nombre que la clase, y, diferente a cualquier otro método, este no tiene ningún tipo de regreso, ni siquiera void . Su códificación es:

public Rectangulo(double baseEnt, double alturaEnt) { base = baseEnt; altura = alturaEnt; }

El constructor está declarado como public . A diferencia de los atributos, se quiere que los métodos sean accesibles desde afuera, con lo anterior los métodos pueden ser llamados por métodos de otras clases.

La definición del constructor no solo reserva algo de espacio en memoria, también se hacen dos sentencias de asignación para asignar los parámetros a los atributos.

Cuando se define un constructor en una clase este es llamado un constructor definido por el usuario . En el caso de que no se defina un constructor propio, entonces uno es proporcionado automáticamente, y este es referido como el constructor por defecto . El constructor por defecto no toma parámetros cuando es usado para crear un objeto, como se indica en el siguiente ejemplo:

Rectangulo miRectangulo = new Rectangulo();

entonces todo lo que pasa es que se reserva memoria para el objeto nuevo.

124

Page 6: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

Cuando se han definido los constructores propios, el constructor por defecto no está más disponible automáticamente. Si se quisiera que estuviera disponible se tendría que redefinir explícitamente. En el caso de la clase Rectangulo se definiría así:

public Rectangulo() { }

Al igual que los métodos regulares, los constructores pueden ser sobrecargados, así se pueden definir varios constructores en la clase. Cuando se crea un objeto a partir de la lista de parámetros se sabe a qué constructor se está refiriendo.

Revisando enseguida el método getBase() , este regresa el valor del atributo base . En el diagrama UML fue declarado como: + getBase () : double

Su código es:

public double getBase() { return base; }

El método fue declarado como public , por la indicación del signo + en diagrama UML, permitiendo que sea accedido por métodos de otras clases.

El método getAltura() se comporta de forma similar pero con el atributo altura .

El método setBase() definido en el diagrama UML como: + setBase ( double )

está implementado así:

public void setBase(double baseEnt) { base = baseEnt; }

El método no regresa un valor, así su tipo de retorno es void . Por otra parte, requiere un parámetro de tipo double que se asignará al atributo base . El cuerpo del método consiste de una sola línea la cual asigna el valor de baseEnt al atributo base .

El método setAltura() se comporta de forma similar pero con el atributo altura .

El método calcularArea() : + calcularArea () : double

125

Page 7: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

Está implementado así:

public double calcularArea() { return base * altura; }

El método no tiene parámetros formales porque no ocupa ningún dato para realizar su función y regresa un double . Su única sentencia regresa el área del rectángulo multiplicando sus dos atributos.

El método calcularPerimetro() es parecido al método descrito previamente.

Los métodos definidos en esta clase tratan solo con la funcionalidad básica de la clase, no se incluyen métodos que traten con entrada o salida, como los vistos en el capítulo 5 . Los métodos del capítulo 5 son sólo usados por la clase en la cual están escritos, pero ahora estos métodos serán usados por otras clases de las cuales no se sabe nada. Cuando se desarrolla una clase siempre se deben restringir los métodos a las funciones esenciales que definen la clase, cómo calcular el área y el perímetro del Rectangulo , y excluir cualquiera que incluya las funciones de entrada o salida de un programa.

8.3.2 Clase CuentaBancaria

El diagrama UML para la clase CuentaBancaria , usada en el capítulo previo, se muestra enseguida.

CuentaBancaria

- numero : String -nombre : String -balance : double

+ CuentaBancaria ( String , String ) + getNumero () : String + getNombre () : String + getBalance () : double + depositar ( double ) + retirar ( double ): boolean

Los atributos numero y nombre están declarados como String ; es válido que los atributos de una clase sean objetos de otra clase.

El código de la clase CuentaBancaria es:

public class CuentaBancaria { // los atributos

126

Page 8: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

private String numero; private String nombre; private double balance; // los métodos // constructor public CuentaBancaria(String numeroEn, String nombreEn) { numero = numeroEn; nombre = nombreEn; balance = 0; } // métodos para leer atributos public String getNombre() { return nombre; } public String getNumero() { return numero; } public double getBalance() { return balance; } // métodos para depositar y retirar dinero public void depositar(double monto) { balance = balance + monto; } public boolean retirar(double monto) { if (monto > balance) { return false; // no se hizo retiro } else { balance = balance - monto; return true; // retiro exitoso } } }

Las primeras tres líneas declaran los atributos de la clase:

private String numero; private String nombre; private double balance;

En el constructor:

public CuentaBancaria(String numeroEn, String nombreEn) {

127

Page 9: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

numero = numeroEn; nombre = nombreEn; balance = 0; }

se puede ver que cuando un objeto nuevo de la clase CuentaBancaria es creado, a numero y nombre se les asignarán los valores de los parámetros pasados al método. A balance se le asignará el valor de cero porque cuando alguien abre una cuenta nueva hay un balance cero hasta que un depósito es hecho.

Los siguientes tres métodos, getNumero() , getNombre() y getBalance() están configurados para que los valores de los atributos correspondientes, que han sido declarados como private , puedan ser leídos.

El método depositar()

public void depositar(double monto) { balance = balance + monto; }

no regresa un valor; por lo tanto es declarado para ser de tipo void . Requiere que un valor sea enviado, la cantidad a ser depositada, y por lo tanto tiene un parámetro de tipo double en los paréntesis. La acción consiste en agregar el depósito al atributo balance del objeto CuentaBancaria .

En el método retirar()

public boolean retirar(double monto) { if (monto > balance) { return false; // no se hizo retiro } else { balance = balance - monto; return true; // retiro exitoso } }

la cantidad es sustraída solo si hay fondos suficientes, es decir, si la cantidad a retirar no es más grande que el balance. Si este no es el caso entonces un valor de false es regresado y el método termina. De otra forma la cantidad es sustraída del balance y un valor de true es regresado. El tipo de retorno del método es por lo tanto boolean .

128

Page 10: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

8.4 Palabra Reservada static

static , al igual que public y private es llamado un modificador . Un modificador determina la forma particular que una clase, atributo o método es accedido.

Considerar la clase CuentaBancaria discutida en la sección previa, para la cual se quiere tener un método adicional que agregue el interés, con la tasa actual, al balance del cliente. Sería útil tener un atributo llamado tasa para guardar el valor de la tasa actual de interés. La tasa de interés es la misma para cualquier cliente, y si esta cambia, se quiere cambiar para cada cliente en el banco, es decir, para cada objeto de la clase. Se puede lograr esto declarando la variable como static . Un atributo declarado como static es un atributo de clase ; cualquier cambio que sea hecho será hecho a todos los objetos en la clase. La forma como lo logra el programa es creando una sola copia del atributo y haciéndola accesible a todos los objetos.

El atributo con static se accede sin referencia a un objeto específico. Se pueden declarar métodos como setTasa() y getTasa() como static . Esto hace que los métodos sean métodos de clase ; no se refieren a un objeto en específico. Se verá en el programa ProbadorCuentaBancaria2 que se puede llamar un método de clase usando el nombre de la clase en vez del nombre del objeto.

Se muestra enseguida el diseño de la clase CuentaBancaria2 , para incluir tres métodos nuevos así como el atributo nuevo tasa static . Los métodos getTasa() y setTasa() permiten leer y escribir respectivamente al nuevo atributo y han sido declarados como static . El método agregarInteres() es el método que agrega el interés al balance del cliente.

CuentaBancaria2

- numero : String -nombre : String -balance : double -tasa : double

+ CuentaBancaria ( String , String ) + getNumero () : String + getNombre () : String + getBalance () : double + depositar ( double ) + retirar ( double ): boolean +setTasa(double) +getTasa() : double +agregarInteres()

129

Page 11: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

El código para la clase CuentaBancaria2 se muestra enseguida, los nuevos elementos han sido puestos en negritas.

public class CuentaBancaria2 { private String numero; private String nombre; private double balance; private static double tasa; public CuentaBancaria2(String numeroEn, String nombreEn) { numero = numeroEn; nombre = nombreEn; balance = 0; } public String getNombre() { return nombre; } public String getNumero() { return numero; } public double getBalance() { return balance; } public void depositar(double monto) { balance = balance + monto; } public boolean retirar(double monto) { if (monto > balance) { return false; // no se hizo retiro } else { balance = balance - monto; return true; // retiro exitoso } } public static void setTasa(double tasaEn) { tasa = tasaEn; } public static double getTasa() { return tasa; } public void agregarInteres() { balance = balance * (1 + tasa/100); } }

130

Page 12: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

El programa siguiente, ProbadorCuentaBancaria2a , usa la clase CuentaBancaria2 nueva.

public class ProbadorCuentaBancaria2a { public static void main(String[] args) { // crear una cuenta bancaria CuentaBancaria2 cuenta1 = new CuentaBancaria2("91827364","Alan Pulido"); // crear otra cuenta bancaria CuentaBancaria2 cuenta2 = new CuentaBancaria2("82736451","Carlos Vela"); // hacer un depósito en la primera cuenta cuenta1.depositar(1000); // hacer un depósito en la segunda cuenta cuenta2.depositar(2000); // poner la tasa de interés CuentaBancaria2.setTasa(10); // agregar el interés a las cuentas cuenta1.agregarInteres(); cuenta2.agregarInteres(); // mostrar los detalles de las cuentas System.out.println(" Número: " + cuenta1.getNumero()); System.out.println(" Nombre: " + cuenta1.getNombre()); System.out.println(" Tasa: " + CuentaBancaria2.getTasa()); System.out.println("Balance: " + cuenta1.getBalance()); System.out.println(); // línea en blanco System.out.println(" Número: " + cuenta2.getNumero()); System.out.println(" Nombre: " + cuenta2.getNombre()); System.out.println(" Tasa: " + CuentaBancaria2.getTasa()); System.out.println("Balance: " + cuenta2.getBalance()); } }

Los métodos de clase pueden ser útiles. Por supuesto, se ha declarado al método main() , y otros métodos dentro de la misma clase, como static , porque estos métodos pertenecen a la clase y no a un objeto específico.

8.5 Inicializar Atributos

Java no da un valor inicial a las variables locales , siendo esta la razón por la cual se obtiene un error del compilador si se intenta usar una variable no inicializada. Los atributos son siempre inicializados por Java. Los atributos numéricos como int o double son inicializados a cero; los atributos char también reciben un valor Unicode de cero; los atributos boolean son inicializados a false ; y los objetos son inicializados a null . Por lo anterior, si el método getTasa() es llamado antes que la tasa de interés sea puesta usando el método setTasa() , este regresará cero.

131

Page 13: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

No obstante, a pesar de lo anterior, es una buena práctica de programación siempre dar un valor inicial a sus atributos, en lugar de que lo haga el compilador. Una razón es que no se puede asumir que todos los lenguajes de programación inicializan las variables de la misma forma. En la clase CuentaBancaria2 no hay ningún problema al inicializar la variable tasa cuando es declarada:

private static double tasa = 0.0;

De hecho, una técnica que se podría usar es dar al atributo tasa algún valor especial inicial, como un valor negativo, para indicar al usuario de esta clase que la tasa de interés no ha sido puesta todavía.

8.6 Clase ScannerFacil

Se usó la clase ScannerFacil en el capítulo previo con lo cual se podía hacer la entrada desde el teclado más fácil. Se revisan ahora los conceptos para entender cómo la clase trabaja. Su código es:

import java.util.Scanner; public class ScannerFacil { public static int nextInt() {

Scanner sc = new Scanner(System.in); int i = sc.nextInt(); return i;

} public static double nextDouble() {

Scanner sc = new Scanner(System.in); double d = sc.nextDouble(); return d;

} public static String nextString() {

Scanner sc = new Scanner(System.in); String s = sc.nextLine(); return s;

} public static char nextChar() {

Scanner sc = new Scanner(System.in); char c = sc.next().charAt(0); return c;

} }

132

Page 14: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

Cada método se hizo como método static , de esta manera se puede usar el nombre de la clase cuando se llame algún método. Por ejemplo:

int numero = ScannerFacil.nextInt();

En el método nextString() se usa el método nextLine() de la clase Scanner y como un objeto Scanner nuevo es creado cada vez que el método es llamado, entonces no hay problema al usarlo después de un nextInt() o un nextDouble() , a diferencia de lo que sucede cuando se usa un solo objeto Scanner .

8.7 Pasar Objetos como Parámetros

En el capítulo 5 se indicó que cuando una variable es pasada a un método solo el valor de esa variable es pasado, y por lo tanto un método no puede cambiar el valor de la variable original. En el capítulo 6 se revisó que en el caso de un arreglo es el valor de la localidad de memoria, una referencia, la que es pasada y por lo tanto el valor de los elementos del arreglo original pueden ser cambiados por el método llamado.

Se revisa el siguiente programa, ProbarParametro , para probar lo que sucede con los objetos.

public class ProbarParametro { public static void main(String[] args) {

// crear cuenta bancaria nueva CuentaBancaria probarCuenta =

new CuentaBancaria("1","Juan Ortiz"); probar(probarCuenta); // mandar la cuenta al método probar System.out.println(" Número: "+probarCuenta.getNumero()); System.out.println(" Nombre: "+probarCuenta.getNombre()); System.out.println("Balance: "+probarCuenta.getBalance());

} // método que hace un depósito en la cuenta bancaria static void probar(CuentaBancaria cuenta) {

cuenta.depositar(2500); } }

Cuando se ejecuta la aplicación anterior se observa que el depósito ha sido exitoso, es decir, el atributo del objeto ha sido cambiado. Lo anterior se debe a que se le mandó al método una referencia del objeto CuentaBancaria original, probarCuenta . Por lo que cuenta es una copia de la referencia probarCuenta y apunta al objeto original e invoca esos métodos del objeto. Por lo que la siguiente línea de código llama al método depositar() del objeto CuentaBancaria original.

133

Page 15: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

cuenta.depositar(2500);

Se podría pensar que esto es bueno, y qué hará la vida del programador más fácil. Sin embargo, se necesita ser cuidadoso. Es inadvertidamente fácil permitir a un método cambiar los atributos del objeto.

8.8 Clases Colección

En el capítulo 7 se introdujo la idea de una clase colección, una clase que tiene una colección de objetos. Se mostró como usar la clase ArrayList para guardar una colección de objetos de un tipo específico, y cómo usar un par de métodos de ArrayList .

Los métodos de las clases colección no están adaptados a ningún tipo específico. Por ejemplo, un método cómo remove() requiere que se le mande una referencia del objeto a ser quitado. Sin embargo, normalmente se podría referir a un objeto tal como una cuenta bancaria por un campo único tal como el número de cuenta, así un programa usando un ArrayList necesitaría primero encontrar en la lista el objeto que se quiere, y luego removerlo.

La aproximación anterior funciona bien, pero sería más conveniente si se pudiera adaptar la clase colección para dar los métodos que se necesitan. Por ejemplo, en el caso de una lista de cuentas bancarias sería más fácil depositar o retirar fondos de una cuenta específica referida por su número de cuenta, o quitar una cuenta con su número particular.

Esto se puede hacer fácilmente creando una clase colección propia con un atributo el cual es una colección, como un ArrayList . Lo anterior se ha hecho enseguida en la colección clase propia llamada Banco .

8.8.1 La Clase Banco

Cuando un objeto propio consiste de otros objetos, esta relación es llamada agregación . Esta asociación, representada en UML por un diamante, es referida frecuentemente como una relación parte-de . Por ejemplo, la asociación entre un carro y los pasajeros en el carro es agregación. La composición , representada por un diamante lleno, es una forma de agregación en la que el "todo" es realmente dependiente de la "parte". Por ejemplo, la asociación entre un carro y su motor es una composición , porque un carro no puede existir sin un motor. Una clase colección es una implementación de la relación de agregación.

La asociación entre el contenedor objeto, Banco , y el objeto contenido, CuentaBancaria , se muestra en el diagrama UML siguiente:

134

Page 16: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

El asterisco al final de la línea que une las clases indica que el objeto Banco contiene cero o más objetos CuentaBancaria . El diseño para la clase Banco se da enseguida:

Banco

-lista:ArrayList<CuentaBancaria>

-buscar(String):int +getTotal():int +getElemento(String):CuentaBancaria +agregarCuenta(String,String):boolean +depositarDinero(String,double):boolean +retirarDinero(String,double):boolean +removerCuenta(String):boolean

La clase Banco sólo tendrá un atributo, lista , el cual es una colección de CuentaBancaria .

Hay siete métodos, los cuales se describen enseguida:

-buscar(String):int Es un método auxiliar; es declarado como private porque se pretende que no sea llamado por otras clases. Este acepta una cadena que representa el número de cuenta. Luego regresa el índice de la cuenta que tiene ese número de cuenta en el ArrayList . Si el número de cuenta no existe, entonces un índice falso (-999) será regresado para indicar falla.

+getTotal():int El método devuelve la cantidad total de cuentas actualmente en el sistema.

+getElemento(String):CuentaBancaria El método recibe un String representando un número de cuenta, y regresa la CuentaBancaria con ese número de cuenta. Si el número de cuenta no es válido, un valor null será regresado.

+agregarCuenta(String,String):boolean El método recibe dos cadenas representando el número y el nombre de la cuenta respectivamente, y agrega una cuenta con esa información a la lista de cuentas. Si

135

Page 17: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

una cuenta con ese número ya existe, la cuenta nueva no será agregada y el método regresará un valor de false , caso contrario regresa true .

+depositarDinero(String,double):boolean Acepta un String , representando un número de cuenta particular, y un monto el cual será depositado en esa cuenta. Regresa true si el depósito es exitoso, o false de otro modo (no existe tal cuenta).

+retirarDinero(String,double):boolean Acepta un String , representando un número de cuenta particular, y un monto de dinero el cual será retirado de la cuenta. Regresa true si el retiro se puede hacer, o false de otra forma. La razón por la cual el retiro no se puede realizar es porque no existe ese número de cuenta o porque existe la cuenta pero con fondos insuficientes. El método no indica cuál de las razones sucedió.

+removerCuenta(String):boolean Acepta un String , representando un número de cuenta, y quita esa cuenta de lista . Regresa true si la cuenta fue removida, o false de otro modo (no existe esa cuenta).

El código para la clase Banco es presentado a continuación.

import java.util.ArrayList; public class Banco { ArrayList<CuentaBancaria> lista = new ArrayList<>(); // método auxiliar para encontrar el índice una cuenta dada private int buscar(String cuenta) { for(int i = 0; i <= lista.size() - 1; i++) { CuentaBancaria cuentaTemp = lista.get(i); // cuenta String numeroTemp = cuentaTemp.getNumero(); // número if(numeroTemp.equals(cuenta)) { // ¿es la cuenta buscada? return i; // devolver índice } } return -999; } // regresar la cantidad total de elementos public int getTotal() { return lista.size(); } // regresar una cuenta con un número particular de cuenta public CuentaBancaria getElemento(String cuenta) {

136

Page 18: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

int indice = buscar(cuenta); if(indice != -999) { // revisar que la cuenta exista return lista.get(indice); } else { return null; // no existe tal cuenta } } // agregar un elemento a la lista public boolean agregarCuenta(String cuenta, String nombre) { if(buscar(cuenta) == -999) { // si la cuenta no existe lista.add(new CuentaBancaria(cuenta, nombre)); // agregar return true; } return false; } // depositar dinero en una cuenta específica public boolean depositarDinero(String numCuenta,double monto) { CuentaBancaria cuenta = getElemento(numCuenta); if(cuenta != null) { cuenta.depositar(monto); return true; // indicar éxito } else { return false; // indicar fallo } } // retirar dinero de una cuenta específica public boolean retirarDinero(String numCuenta, double monto) { CuentaBancaria cuenta = getElemento(numCuenta); if(cuenta != null && cuenta.getBalance() >= monto) { cuenta.retirar(monto); return true; // indicar éxito } else { return false; // indicar fallo } } // quitar una cuenta public boolean removerCuenta(String cuenta) { int indice = buscar(cuenta); // hallar índice de la cuenta if(indice != -999) { // ¿cuenta existe? lista.remove(indice); return true; // quitar fue exitoso

137

Page 19: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

} else { return false; // quitar no fue exitoso } } }

En la clase se ha declarado e inicializado un solo atributo, un ArrayList el cual guardará los objetos CuentaBancaria .

ArrayList<CuentaBancaria> lista = new ArrayList<>();

El método buscar() , declarado como private porque solo está para ayudar a otros métodos de la misma clase, en vez de ser accedido por otras clases. Se regresa un valor "tonto" si el número de cuenta no es válido, lo anterior ha sido usado cuando se ha buscado en un arreglo de enteros, en el capítulo 6 .

private int buscar(String cuenta) { for(int i = 0; i <= lista.size() - 1; i++) { CuentaBancaria cuentaTemp = lista.get(i); // cuenta índice i String numeroTemp = cuentaTemp.getNumero(); // obt. número if(numeroTemp.equals(cuenta)) { // ¿es la cuenta buscada? return i; // regresar índice } } return -999; }

En cada iteración del ciclo la cuenta con ese índice es recuperada usando el método get() de ArrayList y se asigna a un objeto CuentaBancaria , cuentaTemp . El número de cuenta de cuentaTemp es luego asignado a una variable String , numeroTemp . Esta es comparada con el número de cuenta que ha sido pasado. Si el número de cuenta empata, se regresa el índice de ese elemento. De otra forma, el ciclo continúa hasta el final de la lista, determinado por el uso del método size() de ArrayList . El método entonces regresa el valor tonto de -999, indicando que no hay elemento que exista con ese número de cuenta.

El método getTotal() regresa la cantidad total de elementos actuales en la lista usando el método size() de ArrayList :

public int getTotal() { return lista.size(); }

El método getElemento() recupera una cuenta con un número particular. Se usa el método buscar() para encontrar el índice de la cuenta con el número dado,

138

Page 20: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

luego se revisa que el índice no sea -999, es decir que la cuenta exista, y se devuelve la cuenta. Si el índice no es válido se regresa un valor de null .

public CuentaBancaria getElemento(String cuenta) { int indice = buscar(cuenta); if(indice != -999) { // revisar que la cuenta exista return lista.get(indice); } else { return null; // no existe tal cuenta } }

El método agregarCuenta() también usa el método buscar() para revisar que una cuenta con un número no exista, es decir que buscar() regrese el valor de -999. Si este es el caso se usa el método add() de ArrayList para agregar un objeto CuentaBancaria nuevo, el cual se crea a partir de los parámetros pasados al método y se regresa true para indicar éxito. En el caso de que la cuenta exista, se regresa un valor false , indicando que no se agregó una cuenta nueva.

public boolean agregarCuenta(String cuenta, String nombre) { if(buscar(cuenta) == -999) { // si la cuenta no existe lista.add(new CuentaBancaria(cuenta, nombre)); // agregar return true; } return false; }

El método depositarDinero() recibe el número de la cuenta a la cual se quiere depositar, y la cantidad a ser depositada. Se recupera la cuenta bancaria con el método getElemento() revisado previamente. Si esta no tiene el valor null entonces se usa el método depositar() de CuentaBancaria para incrementar el balance, y se regresa true para indicar éxito. Si cuenta tiene el valor null esto indica que no hay cuenta con ese número, y en ese caso se regresa false .

public boolean depositarDinero(String numCuenta, double monto) { CuentaBancaria cuenta = getElemento(numCuenta); if(cuenta != null) { cuenta.depositar(monto); return true; // indicar éxito } else { return false; // indicar fallo } }

139

Page 21: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

El método retirarDinero() es parecido excepto que se necesita tener una revisión adicional en la sentencia if para revisar si hay suficientes fondos o no para hacer el retiro.

El método removerCuenta() usa el método remove() de ArrayList , la cual quita un elemento con un índice particular. El índice es encontrado llamando al método buscar() como antes, y primero se tiene que asegurar que la cuenta exista para quitarla y regresar true , caso contrario el método devuelve false .

public boolean removerCuenta(String cuenta) { int indice = buscar(cuenta); // encontrar índice de la cuenta if(indice != -999) { // ¿cuenta existe? lista.remove(indice); return true; // quitar fue exitoso } else { return false; // quitar no fue exitoso } }

En el caso de que no se use un ArrayList para la colección y se decida usar un arreglo se debe considerar la complejidad de la tarea remover. Como el arreglo podría estar parcialmente lleno, se necesitaría introducir una variable para saber la posición del último elemento en el arreglo. Luego todos los elementos siguientes al que fue removido deberán ser desplazados una posición para sobreescribir al elemento quitado. Además, se debe asegurar que cuando un elemento sea agregado este sea agregado al final del arreglo reducido, así el final del arreglo se reduciría. Además se tienen que cuidar otras situaciones, pero cuando se usa ArrayList , este se encarga de todo este trabajo afortunadamente.

8.8.2 Uso de la Clase Banco

El programa AplicacionBanco usa la clase Banco , observar que se usa la clase ScannerFacil .

public class AplicacionBanco { public static void main(String[] args) { char opcion; Banco miBanco = new Banco(); // ofrecer menú do { System.out.println(); System.out.println("1. Crear cuenta nueva"); System.out.println("2. Quitar una cuenta");

140

Page 22: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

System.out.println("3. Depositar"); System.out.println("4. Retirar"); System.out.println("5. Revisar cuenta"); System.out.println("6. Salir"); System.out.println(); System.out.print("Ingresar opción [1-6]: "); // obtener opción opcion = ScannerFacil.nextChar(); System.out.println(); // procesar las opciones del menú switch (opcion) { case '1': opcion1(miBanco); break; case '2': opcion2(miBanco); break; case '3': opcion3(miBanco); break; case '4': opcion4(miBanco); break; case '5': opcion5(miBanco); break; case '6': break; default: System.out.println("Entrada no válida"); }

} while (opcion != '6');

} // agregar cuenta nueva static void opcion1(Banco banco) {

// obtener información del usuario System.out.print("Ingresar número de cuenta: "); String numero = ScannerFacil.nextString(); System.out.print("Ingresar nombre de cuenta: "); String nombre = ScannerFacil.nextString(); // agregar cuenta a la lista boolean resultado = banco.agregarCuenta(numero, nombre); if(resultado) {

System.out.println("Cuenta agregada");

141

Page 23: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

} else {

System.out.println("Cuenta ya existe"); }

} // quitar cuenta static void opcion2(Banco banco) {

// obtener numero de cuenta para remover System.out.print("Ingresar número de cuenta: "); String numero = ScannerFacil.nextString(); // borrar elemento si este existe boolean encontrado = banco.removerCuenta(numero); if (encontrado) {

System.out.println("Cuenta removida"); } else {

System.out.println("No existe tal número de cuenta"); }

} // depositar dinero en una cuenta static void opcion3(Banco banco) {

// Obtener detalles del usuario System.out.print("Ingresar número de cuenta: "); String numero = ScannerFacil.nextString(); System.out.print("Ingresar monto a depositar: "); double monto = ScannerFacil.nextDouble(); boolean encontrado = banco.depositarDinero(numero, monto); if(encontrado) { System.out.println("Dinero depositado"); } else { System.out.println("No existe tal cuenta"); } } // retirar dinero de una cuenta static void opcion4(Banco banco) { // Obtener detalles del usuario System.out.print("Ingresar número de cuenta: "); String numero = ScannerFacil.nextString(); System.out.print("Ingresar monto a retirar: "); double monto = ScannerFacil.nextDouble(); boolean ok = banco.retirarDinero(numero, monto);

142

Page 24: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

if(ok) { System.out.println("Retiro hecho"); } else { System.out.println( "No existe tal cuenta o fondos insuficientes"); } } // revisar detalles de la cuenta static void opcion5(Banco banco) { // Obtener detalles del usuario System.out.print("Ingresar número de cuenta: "); String numero = ScannerFacil.nextString(); CuentaBancaria cuenta = banco.getElemento(numero); if(cuenta != null) { System.out.println(" Número: " + cuenta.getNumero()); System.out.println(" Nombre: " + cuenta.getNombre()); System.out.println("Balance: " + cuenta.getBalance()); System.out.println(); } else { System.out.println("No existe tal cuenta"); } } }

Se está familiarizado con este tipo de programas manejados por menú, así que no hay mucho que decir, excepto que es el primer ejemplo de una aplicación el cual podría pensarse como un tipo de aplicación que podría ser usada en un ambiente de negocios real. Por supuesto, en el mundo exterior tales aplicaciones son más sofisticadas que esta, pero son, en principio, no muy diferentes de lo realizado. La aplicación involucra una cantidad de clases propias, y se han usado juntas para tener una sola aplicación.

La forma como el programa hace uso de algunas características de la clase Banco que han sido incorporadas en la AplicacionBanco . Por ejemplo, en el método opcion1() y de forma similar en otros métodos se hace uso del hecho de que el método agregarCuenta() de Banco regresa true si la cuenta nueva fue agregada exitosamente, y false de otra forma.

143

Page 25: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

En el método opcion5() se usa el hecho de que el método obtenerElemento() regresa null si la cuenta no fue encontrada.

8.9 Beneficios de la Programación Orientada al Objeto

En este capítulo y en el previo se ha visto cómo crear clases y usarlas como tipos de datos en los programas. Se ha visto como el proceso de construcción de clases permite esconder datos de una clase. Los lenguajes de programación basados en clases y objetos, o lenguajes orientados al objeto, han traído varios beneficios, y ahora son comunes. Enseguida se resumen algunos de los beneficios:

● La habilidad para encapsular datos dentro de una clase ha permitido construir sistemas más seguros.

● La aproximación orientada al objeto hace más fácil reusar clases varias veces. Por ejemplo, con la definición de CuentaBancaria se puede usar en programas diferentes sin tener que escribir una clase nueva cada vez. En el siguiente capítulo se revisará cómo es posible refinar clases existentes para cubrir necesidades adicionales por la técnica conocida como herencia . Si los sistemas pueden ser ensamblados de objetos reusables, esto incrementa la productividad.

● En la aproximación orientada al objeto es posible definir y usar clases las cuales no están todavía completas. Estas pueden entonces ser extendidas sin actualizar la operación de otras clases. Esto mejora los procesos de prueba. Se pueden construir prototipos sin tener que construir un sistema completo antes de probarlo y permitiendo al usuario del sistema verla.

● La aproximación orientada al objeto hace más fácil hacer cambios a sistemas una vez que han sido completados. Clases completas pueden ser reemplazadas, o clases nuevas pueden fácilmente ser agregadas.

● La forma orientada al objeto de hacer las cosas es una aproximación más natural. Los programas están basados en objetos que existen en el mundo real como: estudiantes, cuentas bancarias, clientes, etc.

● La naturaleza modular de la programación orientada al objeto mejora el proceso de desarrollo completo. La aproximación modular significa que las viejas metodologías mediante las cuales los sistemas se analizan primero, luego se diseñan, luego se implementan y se prueban pudieron dar paso a nuevos métodos mediante los cuales estos procesos estaban mucho más integrados y los sistemas se desarrollaban mucho más rápidamente.

144

Page 26: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

8.10 Preguntas

1. El diagrama UML siguiente representa el diseño para una clase Estudiante .

Estudiante

-matricula:String -nombre:String -matematicas:int -espanol:int -ciencias:int -cuota:double

+Estudiante(String,String) +getMatricula():String +getNombre():String +ingresarCalificaciones(int,int,int) +getMatematicas():int +getEspanol():int +getCiencias():int +calcularPromedio():double +getCuota():double +setCuota(double)

Se puede ver que los estudiantes tienen un nombre, una matrícula, algunas calificaciones para materias que están estudiando y la cuota. Entonces los métodos dados están para procesar estos datos.

a. ¿Qué indica el hecho de que se hayan subrayado ciertos atributos y métodos?

b. Escribir el código Java para las partes de la clase que han sido subrayadas.

2. Considerar la siguiente clase: a. ¿Cuál será la salida del siguiente programa?

public class AlgunaClase { private int x; public AlgunaClase() { x = 10; } public AlgunaClase(int xEn) { x = xEn; } public void setX(int xEn) { x = xEn; } public int getX() {

145

Page 27: 8 . D i se ñ o d e cl a se s - Universidad Michoacana de ...computo.fismat.umich.mx/computacion/computacion1/javafx/implementacion.pdfLos atributos de una clase son accesibles a todos

return x; } }

b. ¿Cuál será la salida del siguiente programa?

public class Prueba1 { public static void main(String[] args) { AlgunaClase miObjeto = new AlgunaClase(); System.out.println(miObjeto.getX()); } }

c. Justificar por qué el siguiente programa no compilará.

public class Prueba2 { public static void main(String[] args) { AlgunaClase miObjeto = new AlgunaClase(5,8); System.out.println(miObjeto.getX()); } }

d. ¿Cuál será la salida del siguiente programa?

public class Prueba2 { public static void main(String[] args) { int y = 20: AlgunaClase miObjeto = new AlgunaClase(5); System.out.println(miObjeto.getX()); prueba(y, miObjeto); System.out.println(y); System.out.println(miObjeto.getX()); } static void prueba(int z, AlgunaClase objeto) { z = 50; objeto.setX(100); } }

3. Considerar el programa Banco de la sección 8.8.1 . a. Adaptar el método retirarDinero() para distinguir las dos razones

por las que el método podría fallar. Observar que un método boolean no es suficiente ya que hay una posibilidad más. Una solución podría ser que el método regrese un entero.

b. Adaptar el programa AplicacionBanco de la sección 8.8.2 para que la opción 4 ahora use la versión nueva de retirarDinero() .

146