miércoles, 9 de abril de 2008

Polimorfismo

  • Sobrecarga
  • Sobre-escritura
  • Ligadura dinámica


La herencia es mucho más potente que simplemente una herramienta de reutilización


Cuando estamos aprendiendo programación orientada a objetos, nos maravilla la potencia de la herencia y la reutilización de código, en este paper vamos a ver que otras ventajas de la programación orientada a objetos se basan en la herencia.

El polimorfismo, como su mismo nombre sugiere múltiples formas, se refiere a la posibilidad de acceder a un variado rango de funciones distintas a través del mismo interfaz. O sea, que, en la práctica, un mismo identificador puede tener distintas formas (distintos cuerpos de función, distintos comportamientos) dependiendo, en general, del contexto en el que se halle inserto. El polimorfismo se puede establecer mediante la sobrecarga, sobre-escritura y la ligadura dinámica.

Sobrecarga.
El término sobrecarga se refiere al uso del mismo identificador u operador en distintos contextos y con distintos significados.


Si para cada funcionalidad necesitada fuese necesario escribir un método, el código resultante sería inmanejable, imagínense, por ejemplo que los desarrolladores de java hubiesen creado un método para escribir en pantalla una cadena de texto, otro diferente para escribir un entero, otro para escribir un doble, otro para escribir un carácter, otro para una cadena y un entero y así para todas las combinaciones posibles, seria casi imposible conocer dichos métodos en totalidad; en cambio sabemos que con “System.out.print()” o “System.out.println()” podemos escribir cualquier mensaje en pantalla.

Este tipo de codificación nos es permitido gracias a la sobrecarga, la cual se aplica a métodos y constructores.

La sobrecarga de métodos conduce a que un mismo nombre pueda representar distintos métodos con distinto tipo y número de parámetros, manejados dentro de la misma clase. En el ámbito de la POO, la sobrecarga de métodos se refiere a la posibilidad de tener dos o más métodos con el mismo nombre pero funcionalidad diferente. Es decir, dos o más métodos con el mismo nombre realizan acciones diferentes. El compilados usará una u otra dependiendo de los parámetros usados. Esto también se aplica a los constructores. De hecho, es la aplicación más habitual de la sobrecarga.

La forma de diferenciar varios métodos sobrecargados es a través de sus parámetros, ya sea por la cantidad, el tipo o el orden de los mismos, veamos un ejemplo:

public class Articulo {
 private float precio;
 public void setPrecio() {
  precio = 3.50;
 }
 public void setPrecio(float nuevoPrecio) {
  precio = nuevoPrecio;
 }
 public void setPrecio(float costo, int porcentajeGanancia) {
       precio = costo + (costo * porcentajeGanancia);
    }
}


Sobre-escritura:
La sobre-escritura, se aplica a los métodos y esta directamente relacionada a la herencia y se refiere a la re-definición de los métodos de la clase base en las subclases, por ejemplo, en la relación de herencia del ejemplo de las figuras aunque la clase base “Figura” tiene los métodos “calcularArea” y “calcularPerimetro”, las subclases “Circulo”, “Cuadrado”, “Triangulo” y “Rectangulo” redefinen estos métodos ya que el calculo del área y el perímetro de cada uno de ellos es diferente.

class Figura { 
 protected double area; 
     protected double perimetro; 
     public Figura() { 
       this.area=0; 
         this.perimetro=0; 
     } 
     public double getArea() { 
         return area; 
     } 
     public double getPerimetro() { 
         return perimetro; 
     } 
     public void calcularArea(){} 
     public void calcularPerimetro(){} 
}

public class Circulo extends Figura { 
     private double radio; 
     public Circulo() { 
         super(); 
     } 
     public double getRadio() { 
         return radio; 
     } 
    public void setRadio(double radio) { 
         this.radio = radio; 
     } 
    public void calcularArea() { 
       this.area = Math.PI*Math.pow(this.radio,2.0); 
     }  
    public void calcularPerimetro() { 
       this.perimetro = 2*Math.PI*this.radio; 
 } 
}
 


Ligadura dinámica:
Gracias a la ligadura dinámica, pueden invocarse operaciones en objetos obviando el tipo actual del éstos hasta el momento de la ejecución del código, es decir que me perite definir elementos como un tipo e instanciarlos como un tipo heredado. Pero cual puede ser la utilidad de este concepto, para que me sirve obviar el tipo de un objeto para luego tomar esta decisión?.

Gracias a que en java la definición de los tipos de objetos se puede producir por atado posterior (late binding), no nos debe preocupar a que tipo de elemento le paso un mensaje ya que el compilador tomara la decisión de que objeto ejecutará que método de acuerdo a la forma de crear la instancia.

Este concepto es bastante complejo de entender ya que estamos acostumbrados a definir los elementos de acuerdo a lo que necesitamos, es decir, si requiero un entero lo declaro como entero, para que declararía yo un elemento como un tipo y lo usaría como otro?. No siempre se puede determinar exactamente el tipo de elemento que va a usarse en la ejecución de nuestro programa, para este tipo de casos es cuando es útil aplicar el atado posterior o ligadura dinámica, y se debe tener por lo menos una relación de herencia que por lo menos me permita determinar un tipo base para la declaración, sea cual sea el subtipo que se instancie.

Veamos un ejemplo que nos aclare un poco este concepto:

class Mamifero {
  public void mover() {
    System.out.println("Ahora es un mamifero el que se mueve");
  }
}

class Perro extends Mamifero {
  public void mover() {
    System.out.println("Ahora es un perro el que se mueve");
  }
}

class Gato extends Mamifero {
  public void mover() {
    System.out.println("Ahora es un gato el que se mueve");
  }
}

public class Polimorfismo {
  public static void muevete(Mamifero m) {
    m.mover();
  }
  public static void main(String[] args) {
    Gato bisho = new Gato();
    Perro feo = new Perro();
    muevete(bisho);
    muevete(feo);
  }
}


Vemos que el método “muevete” llama al método mover de un mamífero y aunque el no sabe con qué clase de mamífero trata, funciona y se llama al método correspondiente al objeto específico que lo llama (es decir, primero un gato y luego un perro). Si no existiera la ligadura dinámica tendríamos que crear un método “muevete” para los mamíferos de tipo “Gato” y otro para los de tipo “Perro”.

Veamos otro ejemplo donde las instancias se crean de forma aleatoria:

public abstract class Instrumento {
     public Instrumento() { }
     public abstract void tocar();
     public abstract void tocar(String nota);
 public abstract void afinar();
}

public class Cuerda extends Instrumento{
 public Cuerda() {}
      public void afinar() {
         System.out.println("Cuerda.afinar()");
     }
 public void tocar() {
         System.out.println("Cuerda.tocar()");
     }
     public void tocar(String nota){
         System.out.println("Cuerda.tocar()"+nota);
     }
}

public class Percusion extends Instrumento{
     public Percusion() {}
     public void afinar() {
         System.out.println("Percusion.afinar()");
     }
     public void tocar() {
         System.out.println("Percusion.tocar()");
     }
     public void tocar(String nota){
         System.out.println("Percusion.tocar()"+nota);
     }
}

public class Viento extends Instrumento{
     public Viento() {}
     public void afinar() {
         System.out.println("Viento.afinar()");
     }
     public void tocar() {
         System.out.println("Viento.tocar()");
     }
     public void tocar(String nota){
         System.out.println("Viento.tocar()"+nota);
     }
}

public class LigaduraDinamica {
    public static void main(String[] args){
        String[] notas = {"Do","Re","Mi","Fa","Sol","La","Si"};
        Instrumento orquesta[] = new Instrumento[10];

        for(int i=0;i<10 br="" i="">            orquesta[i]=generarInstrumento(new java.util.Random().nextInt(3));
        }

        for(int i=0;i<10 br="" i="">            afinarInstrumento(orquesta[i]);
        }

        for(int i=0;i<10 br="" i="">            tocarInstrumento(orquesta[i]);
        }

        for(int i=0;i<10 br="" i="">            tocarInstrumento(orquesta[i],notas[i%7]);
        }
    }
    
    public static Instrumento generarInstrumento(int i){
        switch(i){
        default:
        case 0:
            return new Viento();
        case 1:
            return new Percusion();
        case 2:
            return new Cuerda();
        }
    }
    
    public static void afinarInstrumento(Instrumento o){
        o.afinar();
    }
    
    public static void tocarInstrumento(Instrumento o){
        o.tocar();
    }
    
     public static void tocarInstrumento(Instrumento o,String nota){
        o.tocar(nota);
    }
}


Veamos que el método “generarInstrumento” aleatoriamente me crea las instancias de los diferentes tipos de instrumentos y que nuestro arreglo de elementos del tipo de instrumentos es llenado con diferentes elementos de los cuales no podemos controlar que tipo de instancia es creada, solo sabemos que son objetos basados en “Instrumento”, sin embargo los métodos “tocarInstrumento” y “afinarInstrumento” que reciben como parámetro un Instrumento llaman adecuadamente a los métodos “afinar” o “tocar” según la instancia que reciben.

sábado, 5 de abril de 2008

Calculadora en Java

Hoy traigo un ejemplo sencillo que implementa ejemplo de herencia y composición, la idea es hacer una calculadora que opere en binario, octal, decimal y hexadecimal; ya que no es idea del ejercicio enseñar los sistemas numéricos sino mostrar un ejemplo sencillo en java, la calculadora solo operará en enteros y hará las operaciones básicas: suma, resta, multiplicación y división.


Para las transformaciones entre los sistemas usaremos los métodos de la clase Integer Integer.parseInt() para convertir de los binario, octal o hexadecimal a decimal e Integer.toBinaryString(), Integer.toOctalString() e Integer.toHexString() para convertir de cada uno de los sistemas a decimal.

Para implementar la herencia lo haremos de una clase "Sistema" hacia cada uno de los sistemas numéricos representados en el ejemplo y la composición estará implementada en nuestro modelo por una clase "Conversion" que servirá como componente para hacer conversiones entre sistemas, además de los elementos que nos servirán para componer nuestra interfaz gráfica, asi como manejadores para cada uno de los sistemas numéricos.

Entonces la clase base ne nuestro aplicativo sera la llamada "Sistema" que sera la que se encarga de sumar, restar, multiplicar y dividir en base decimal y cada clase que herede de ella determinará en que base va a trabajar.

Veamos como quena nuestra clase Sistema:

abstract public class Sistema {
protected int numeroA;
protected int numeroB;
protected int resultado;
protected char operacion;
protected int base;

public Sistema() {
this.numeroA = 0;
this.numeroB = 0;
this.resultado = 0;
this.operacion = ' ';
}

public void setNumeroA(int n){
this.numeroA = n;
}

public void setNumeroB(int n){
this.numeroB = n;
}

public void setResultado(int n){
this.resultado = n;
}

public void setOperacion(char o){
this.operacion = o;
}

public int getNumeroA(){
return this.numeroA;
}

public int getNumeroB(){
return this.numeroB;
}

public int getResultado(){
return this.resultado;
}

public char getOperacion(){
return this.operacion;
}

public void suma(){
this.resultado = this.numeroA + this.numeroB;
}
public void resta(){
this.resultado = this.numeroA - this.numeroB;
}
public void multiplicacion(){
this.resultado = this.numeroA * this.numeroB;
}
public void division(){
this.resultado = this.numeroA / this.numeroB;
}

public void establecerNumeroA(String a){
int n = Integer.parseInt(a,base);
this.setNumeroA(n);
}
public void establecerNumeroB(String b){
int n = Integer.parseInt(b,base);
this.setNumeroB(n);
}
public String retornarNumeroA(){
String cad="";
switch(base){
case 2:
cad = Integer.toBinaryString(numeroA);
break;
case 8:
cad = Integer.toOctalString(numeroA);
break;
case 10:
cad = String.valueOf(numeroA);
break;
case 16:
cad = Integer.toHexString(numeroA);
break;
}
return cad;

}
public String retornarNumeroB(){
String cad="";
switch(base){
case 2:
cad = Integer.toBinaryString(numeroB);
break;
case 8:
cad = Integer.toOctalString(numeroB);
break;
case 10:
cad = String.valueOf(numeroB);
break;
case 16:
cad = Integer.toHexString(numeroB);
break;
}
return cad;
}
public String retornarResultado(){
String cad="";
switch(base){
case 2:
cad = Integer.toBinaryString(resultado);
break;
case 8:
cad = Integer.toOctalString(resultado);
break;
case 10:
cad = String.valueOf(resultado);
break;
case 16:
cad = Integer.toHexString(resultado);
break;
}
return cad;
}

}


Ahora como queda la clase "Binario" que hereda de "Sistema"

public class Binario extends Sistema{
public Binario() {
this.base=2;
}
}


Ahora como queda la clase "Octal" que hereda de "Sistema"

public class Octal extends Sistema{
public Octal() {
this.base=8;
}
}


Ahora como queda la clase "Decimal" que hereda de "Sistema"

public class Decimal extends Sistema{
public Decimal() {
this.base=10;
}
}


Ahora como queda la clase "Hexadecimal" que hereda de "Sistema"

public class Hexadecimal extends Sistema{
public Hexadecimal() {
this.base=16;
}
}


Ahora como queda la clase "Conversion"

public class Conversion {

public Integer fromHexadecimal(String cad){
return Integer.parseInt(cad,16);
}

public Integer fromOctal(String cad){
return Integer.parseInt(cad,8);
}

public Integer fromBinario(String cad){
return Integer.parseInt(cad,2);
}

public String toHexadecimal(int i){
return Integer.toHexString(i);
}

public String toOctal(int i){
return Integer.toOctalString(i);
}

public String toBinario(int i){
return Integer.toBinaryString(i);
}

public String combertirSistema(int actual,int nuevo, String cad){
String res = "";
int num=0;
if(cad.length()>0){
switch(actual){
case 2:
num = fromBinario(cad);
break;
case 8:
num = fromOctal(cad);
break;
case 10:
num = Integer.parseInt(cad,10);
break;
case 16:
num = fromHexadecimal(cad);
break;
}
switch(nuevo){
case 2:
res = toBinario(num);
break;
case 8:
res = toOctal(num);
break;
case 10:
res = Integer.toString(num);
break;
case 16:
res = toHexadecimal(num);
break;
}
}
return res;
}

}


Y ahora como queda la clase "AppCalculadora" que sera la que pone todo junto en una interfaz gráfica que representa la calculadora

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import co.edu.udistrital.prycalculadora.logica.*;

public class AppCalculadora implements ActionListener{

private JFrame frame;
private ButtonGroup btgSistema;
private JButton[] btnBotones;
private JRadioButton[] jrbSistemas;
private JTextField txtResultado;
private Container cpane;
int s;
Sistema d;
Conversion c;

public AppCalculadora() {
initComponents();
}

private void initComponents(){
s=10;
d = new Decimal();
c = new Conversion();
frame = new JFrame("Calculadora de Cuatro Sistemas") ;
btgSistema = new ButtonGroup();
btnBotones = new JButton[24];
jrbSistemas = new JRadioButton[4] ;
txtResultado = new JTextField(0);

int cont=0;
btnBotones[cont++]=new JButton("D");
btnBotones[cont++]=new JButton("E");
btnBotones[cont++]=new JButton("F");
btnBotones[cont++]=new JButton("/");
btnBotones[cont++]=new JButton("A");
btnBotones[cont++]=new JButton("B");
btnBotones[cont++]=new JButton("C");
btnBotones[cont++]=new JButton("*");
btnBotones[cont++]=new JButton("7");
btnBotones[cont++]=new JButton("8");
btnBotones[cont++]=new JButton("9");
btnBotones[cont++]=new JButton("-");
btnBotones[cont++]=new JButton("4");
btnBotones[cont++]=new JButton("5");
btnBotones[cont++]=new JButton("6");
btnBotones[cont++]=new JButton("+");
btnBotones[cont++]=new JButton("1");
btnBotones[cont++]=new JButton("2");
btnBotones[cont++]=new JButton("3");
btnBotones[cont++]=new JButton("=");
btnBotones[cont++]=new JButton("0");
btnBotones[cont++]=new JButton("Acerca de ... ");
btnBotones[cont++]=new JButton("Cl");
btnBotones[cont++]=new JButton("CE");

cont=0;
jrbSistemas[cont++]=new JRadioButton("BIN");
jrbSistemas[cont++]=new JRadioButton("OCT");
jrbSistemas[cont++]=new JRadioButton("DEC");
jrbSistemas[cont++]=new JRadioButton("HEX");

for(int i=0;i<jrbSistemas.length;i++){
btgSistema.add(jrbSistemas[i]);
}

cpane = frame.getContentPane();
cpane.setLayout(null);

txtResultado.setBounds(10,10,260,30);
txtResultado.setEditable(false);
txtResultado.setBackground(Color.white);
txtResultado.setHorizontalAlignment(JTextField.RIGHT);
cpane.add(txtResultado);

int x=10;
int y=45;
for(int i=0;i<jrbSistemas.length;i++){
x=i*70+10;
jrbSistemas[i].setBounds(x,y,60,20);
jrbSistemas[i].addActionListener(this);
cpane.add(jrbSistemas[i]);

}
x=10;
y=35;
for(int i=0;i<btnBotones.length;i++){
if(i%4==0){
x=10;
y+=35;
}else{
x+=70;
}
if(i!=20 && i!=21){
btnBotones[i].setBounds(x,y,50,30);
}else{
if(i==20)
btnBotones[i].setBounds(x,y,120,30);
if(i==21)
btnBotones[i].setBounds(10,y+35,260,30);
}


btnBotones[i].addActionListener(this);
cpane.add(btnBotones[i]);
}

jrbSistemas[2].setSelected(true);

elegirSistema(10);

frame.pack();
frame.setSize(290,350);
frame.setResizable(false);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

private void elegirSistema(int sistema){

for(int i=0;i<btnBotones.length;i++){
if(!btnBotones[i].getText().equals("+")&&
!btnBotones[i].getText().equals("-")&&
!btnBotones[i].getText().equals("*")&&
!btnBotones[i].getText().equals("/")&&
!btnBotones[i].getText().equals("=")){
btnBotones[i].setEnabled(false);
}
}
switch(sistema){
case 2:
for(int i=0;i<btnBotones.length;i++){
if(!btnBotones[i].getText().equals("A")&&
!btnBotones[i].getText().equals("B")&&
!btnBotones[i].getText().equals("C")&&
!btnBotones[i].getText().equals("D")&&
!btnBotones[i].getText().equals("E")&&
!btnBotones[i].getText().equals("F")&&
!btnBotones[i].getText().equals("9")&&
!btnBotones[i].getText().equals("8")&&
!btnBotones[i].getText().equals("7")&&
!btnBotones[i].getText().equals("6")&&
!btnBotones[i].getText().equals("5")&&
!btnBotones[i].getText().equals("4")&&
!btnBotones[i].getText().equals("3")&&
!btnBotones[i].getText().equals("2")){
btnBotones[i].setEnabled(true);
}
}
break;
case 8:
for(int i=0;i<btnBotones.length;i++){
if(!btnBotones[i].getText().equals("A")&&
!btnBotones[i].getText().equals("B")&&
!btnBotones[i].getText().equals("C")&&
!btnBotones[i].getText().equals("D")&&
!btnBotones[i].getText().equals("E")&&
!btnBotones[i].getText().equals("F")&&
!btnBotones[i].getText().equals("9")&&
!btnBotones[i].getText().equals("8")){
btnBotones[i].setEnabled(true);
}
}
break;
case 10:
for(int i=0;i<btnBotones.length;i++){
if(!btnBotones[i].getText().equals("A")&&
!btnBotones[i].getText().equals("B")&&
!btnBotones[i].getText().equals("C")&&
!btnBotones[i].getText().equals("D")&&
!btnBotones[i].getText().equals("E")&&
!btnBotones[i].getText().equals("F")){
btnBotones[i].setEnabled(true);
}
}
break;
case 16:
for(int i=0;i<btnBotones.length;i++){
btnBotones[i].setEnabled(true);
}
break;
}
txtResultado.setText(c.combertirSistema(s, sistema, txtResultado.getText()));
s=sistema;
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
AppCalculadora c = new AppCalculadora();
}

public void actionPerformed(ActionEvent e){
if(e.getActionCommand().equals("Cl")||e.getActionCommand().equals("CE")){
txtResultado.setText("");
}
if(e.getActionCommand().equals("BIN")){
elegirSistema(2);
d = new Binario();
}
if(e.getActionCommand().equals("OCT")){
elegirSistema(8);
d = new Octal();
}
if(e.getActionCommand().equals("DEC")){
elegirSistema(10);
d = new Decimal();
}
if(e.getActionCommand().equals("HEX")){
elegirSistema(16);
d = new Hexadecimal();
}
if(!e.getActionCommand().equals("+")&&
!e.getActionCommand().equals("-")&&
!e.getActionCommand().equals("*")&&
!e.getActionCommand().equals("/")&&
!e.getActionCommand().equals("=")&&
!e.getActionCommand().equals("Acerca de ... ")&&
!e.getActionCommand().equals("Cl")&&
!e.getActionCommand().equals("CE")&&
!e.getActionCommand().equals("BIN")&&
!e.getActionCommand().equals("OCT")&&
!e.getActionCommand().equals("DEC")&&
!e.getActionCommand().equals("HEX")){
txtResultado.setText(txtResultado.getText()+e.getActionCommand());
}

if(e.getActionCommand().equals("+")||
e.getActionCommand().equals("-")||
e.getActionCommand().equals("*")||
e.getActionCommand().equals("/")){
d.setOperacion(e.getActionCommand().charAt(0));
switch(s){
case 2:
d.establecerNumeroA(txtResultado.getText());
break;
case 8:
d.setNumeroA(c.fromOctal(txtResultado.getText()));
break;
case 10:
d.setNumeroA(Integer.parseInt(txtResultado.getText()));
break;
case 16:
d.setNumeroA(c.fromHexadecimal(txtResultado.getText()));
break;
}
txtResultado.setText("");
}

if(e.getActionCommand().equals("=")){
switch(s){
case 2:
d.setNumeroB(c.fromBinario(txtResultado.getText()));
break;
case 8:
d.setNumeroB(c.fromOctal(txtResultado.getText()));
break;
case 10:
d.setNumeroB(Integer.parseInt(txtResultado.getText()));
break;
case 16:
d.setNumeroB(c.fromHexadecimal(txtResultado.getText()));
break;
}
switch(d.getOperacion()){
case '+':
d.suma();
break;
case '-':
d.resta();
break;
case '*':
d.multiplicacion();
break;
case '/':
d.division();
break;
}
switch(s){
case 2:
txtResultado.setText(c.toBinario(d.getResultado()));
break;
case 8:
txtResultado.setText(c.toOctal(d.getResultado()));
break;
case 10:
txtResultado.setText(String.valueOf(d.getResultado()));
break;
case 16:
txtResultado.setText(c.toHexadecimal(d.getResultado()));
break;
}
}
if(e.getActionCommand().equals("Acerca de ... ")){
JOptionPane.showMessageDialog(null, "Calculadora de ejemplo");
}
}


}




Espero que el ejemplo le sea de utilidad

miércoles, 2 de abril de 2008

Encapsulamiento

Si todos los elementos tienen el mismo comportamiento e igual visibilidad que sentido tiene programar con la orientación a objetos


Si cuando se hace el diseño de una solución de software, todos los elementos que la componen se comportan de igual manera y tiene niveles de acceso sin restricciones, no estamos modelando con orientación a objetos y tenemos que replantear o el modelo o el paradigma sobre el cual deseamos plantear la solución de software.

Paquetes.
Los paquetes en java nos permiten agrupar clases por funcionalidad, y es el elemento que se asocia a las llamadas “librerías” o unidades de librerías.

Cuando se trabaja en c o c++, en la programación estructurada, y se requería de elementos ya implementados como librerías matemáticas, por ejemplo, se recurría a los “#include” para cargar dichas librerías y poder usar las diferentes funciones que nos brindan.

En java cuando deseamos cargar un grupo de librerías o una en especial, tenemos que conocer en que paquete están definidas, java nos proporciona la palabra reservada import para obtener los paquetes (package) donde se encuentran los elementos que necesitamos.

Existen dos formas de utilizar la palabra reservada import:

import java.util.*;


Lo que nos trae todas la clases definidas dentro del paquete o,

import java.util.Random;


Lo que nos trae la clase Random definida dentro del paquete.

Con la primera forma podemos usar dentro de nuestra clase las diferentes clases que se encuentran en java.util, por ejemplo podremos definir objetos de tipo Random o de tipo ArrayList; con la segunda forma solo tendremos a nuestra disposición la clase Random de java.util, pero las demás clases no.
Los paquetes en java también nos ayudan a evitar colisiones de nombre con otros paquetes creados por terceros, por ejemplo, si nosotros creamos una clase que nos permita manejar un vector y de hecho llamáramos esta clase como Vector, ya sabemos que java posee una clase dentro del paquete java.util que se encarga de esto como podría java diferenciar cual clase debe encargarse del manejo de los elementos de tipo Vector, eso se puede manejar con la declaración del elemento de la siguiente forma:
 
java.util.Vector v = new java.util.Vector();

si lo que deseamos es un objeto de tipo vector que se encuentra definido en java.util, si por el contrario deseamos uno basado en nuestra clase Vector entonces:

mipaquete.Vector v = new mipaquete.Vector();

evitando colisiones:
Una forma de evitar las colisiones entre nombres de paquetes es simular nombres de dominios en nuestros paquetes, por ejemplo podemos usar el dominio de nuestra universidad o empresa para evitar que nuestros paquetes tengan colisión de nombres con otros paquetes de java o con otros desarrollados por terceros, un ejemplo de esto seria algo como esto:

package co.edu.udistrital.miproyecto.logica;


Modificadores de acceso:


Los modificadores de acceso son palabras reservadas del lenguaje que nos permiten restringir la visibilidad y acceso a los elementos de nuestra solución de software. Existen 4 tipos de modificadores de acceso en java:

  • default - friendly (amistoso)
  • public (publico)
  • protected (protegido)
  • private (privado)

Estos cuatro elementos son aplicables a los atributos y métodos de nuestras clases y los dos primeros a nuestras clases en si, pero veamos que nos dice cada uno de ellos sobre la visibilidad de los elementos.

default: cualquier elemento declarado sin asignarle un modificador de acceso especifico, toma el nivel de acceso por defecto, este nivel es llamado el nivel amistoso o con visibilidad de paquete porque estos elementos se comportan como elementos públicos (cualquiera los puede ver) para la clase y todas las clases definidas dentro del paquete; pero se comportan como privados (nadie los puede ver) por fuera del paquete.

public: los elementos de tipo públicos son visibles desde cualquier parte, es decir se pueden leer o modificar des cualquier clase que conforme la solución de software.

protected: los elementos protegidos serán visibles para la misma clase y las clases que heredan de esta.

private: los elementos privados solo son visibles dentro de la clase que fueron declarados.

veamos como se ve esto en una tabla:



Clase Sub-clases Paquete Fuera del paquete
default si si si no
public si si si si
protected si si no no
private si no no no



Recordemos lo que nos reza el concepto de encapsulación:

Encapsular se refiere al hecho de ocultar la implementación es decir ocultar los atributos de nuestros objetos y hacer visibles los métodos que escriben o retornan dichos valores. De esta forma el usuario de la clase puede obviar la implementación de los métodos y propiedades para concentrarse sólo en cómo usarlos. Por otro lado se evita que el usuario pueda cambiar su estado de maneras imprevistas e incontroladas.

Entonces para garantizar este principio es necesario tener claros los conceptos revisados en este escrito y hacer buen uso de los modificadores de acceso y los paquetes de clases.

Qué reglas me ayudarían a garantizar la encapsulación de mis desarrollos?

  • Los atributos de las clases deben ser privados o protegidos.
  • Se deben crear métodos públicos para leer o escribir los valores de estos atributos (setters/getters)
  • Si las clases solo tienen funcionalidad dentro del paquete estas no deben ser públicas si no amistosas.

siguiendo estas reglas básicas podemos garantizar inicialmente la encapsulación en nuestros desarrollo.

Reutilización del código y relaciones entre clases

“Reutilización de código” no se refiere a copiar (ctrl+c) y pegar (ctrl+v)

Lo primero que se les viene a la cabeza a los estudiantes (y a muchos profesionales) cuando se les menciona la reutilización del código es el famoso copiar y pegar al que se han acostumbrado en la programación estructurada, y de echo muchos lo hacen en poo, lo cual es una de las practicas que más encarece el desarrollo de software. Como todo en Java, el problema se resuelve con las clases. Para reutilizar el código creamos nuevas clases pero, en lugar de partir de cero, partimos de clases, relacionadas con nuestra clase, que han sido ya creadas y depuradas. El truco está en usar las clases sin ensuciar el código existente.

Una forma de hacer esto es crear objetos de nuestras clases existentes dentro de la nueva clase. Esto se conoce como composición porque la nueva clase está compuesta de objetos de clases existentes. Estamos reutilizando la funcionalidad del código, y no la forma.

Otra forma es crear una nueva clase como un tipo de una clase ya existente. Tomamos la forma de la clase existente y añadimos código a la nueva, sin modificar la clase existente. Esta forma de crear nuevos objetos se llamada herencia, y lo que hacemos es extender la clase en la que nos basamos para crear la nueva.

Composición:
Hasta ahora hemos usado la composición de cierta manera, ej. cuando hacemos una interfaz gráfica de usuario, nuestra clase de interfaz gráfica esta compuesta por un frame, unos panel, botones, etc. todos estos objetos componen el objeto de interfaz gráfica. Es decir que la composición consiste en poner manejadores de objetos dentro de nuestra clase, estos manejadores de objetos no serán otra cosa que instancias de las clases en las que nos estamos basando para crear la nueva clase.

Recordemos que la forma para determinar cuando usar composición es cuando podemos decir que nuestra nueva clase “tiene un” elemento de otro tipo de objetos, por ejemplo un cronómetro tiene: horas, minutos y segundos, es decir que una clase Cronometro esta compuesta por otras clases llamadas: Horas, Minutos y Segundos.

Veamos como seria esta clase:

public class Cronometro {
Horas h;
Minutos m;
Segundos s;

String cadena;
int seg,min,hor;

public Cronometro() {
seg=0;
min=0;
hor=0;
h = new Horas();
m = new Minutos();
s = new Segundos();
cadena = new String("0 : 0 : 0");
}

public String avanzar(){
seg = s.forward();
if(seg==0){
min=m.forward();
if(min==0){
hor=h.forward();
}
}
cadena = hor + " : " + min + " : " + seg;
return cadena;
}

public String reset(){
seg = s.reset();
min = m.reset();
hor = h.reset();
cadena = hor + " : " + min + " : " + seg;
return cadena;
}
}


Nuestra clase Cronometro está compuesta entre otras cosas por objetos del tipo Horas, Minutos y Segundos y a través del constructor de nuestra clase creamos las instancias de cada una de ellas.

Herencia:
En java aunque no establezcamos de manera explicita la herencia siempre está presente, ya que todas las clases que creemos heredan de la clase Object, por eso es valido decir que en java todo es un objeto. La sintaxis para la composición es obvia pero, para realizar la herencia, hay una forma claramente distinta. Cuando heredamos, estamos diciendo "Esta nueva clase es como esa clase antigua", por ejemplo es decir que la clase Horas “es una” UnidadDeTiempo. Afirmamos esto en el código dando el nombre de la clase como siempre pero, antes de la apertura del límite cuerpo de clase, pondremos la palabra clave "extends" seguida por el nombre de la clase base. Cuando hagamos esto, obtendremos automáticamente todos los datos miembros y métodos de la clase base.

Primero veamos como seria la clase UnidadDeTiempo:


public class UnidadDeTiempo {
int valor;
int tope;

public int forward(){
if(valor == tope)
valor=0;
else
valor++;
return valor;
}

public int reset(){
valor=0;
return valor;
}
}


Y nuestra clase Horas:


public class Horas extends UnidadDeTiempo{
public Horas() {
this.valor=0;
this.tope=23;
}
}


De esta manera sin necesidad de tener que escribir nuevamente todos el código de UnidadDeTiempo lo tememos disponible en la clase Horas, pero que partes tenemos disponibles?, todos los atributos y los métodos de la clase padre están disponibles en la clase hija pero dependiendo de los modificadores de acceso o visibilidad de estos, por ejemplo los atributos y métodos de tipo friendly solo estarán disponibles para las clases hijas que heredan de una clase padre en el mismo paquete, los atributos y métodos de tipo public estarán disponibles para todas las clases que hereden de la clase padre sin importar que se halle o no en el mismo paquete; los miembros protected también son accesibles desde las clases hijas.

El código de nuestra clases hijas no tienen porque limitarse solo al código heredado, de hecho casi siempre la herencia se hace para extender la funcionalidad de las clases heredadas añadiendo nuevos métodos y atributos.

La composición y la herencia:
Tanto la composición como la herencia permiten poner sub-objetos dentro de tu nueva clase. Podríamos preguntarnos cuál es la diferencia entre los dos, y cuándo elegir uno en lugar del otro. La composición es generalmente usada cuando deseamos las características de una clase existente dentro de una nueva clase, pero no su interfaz. Es decir, ponemos un para poder usarlo para implementar características de nuestra nueva clase, pero el usuario de esa nueva clase verá el interfaz que hemos definido en lugar del interfaz del objeto insertado.

Los objetos miembros usan la implementación ocultándose a sí mismos, por lo que esto es una cosa segura a hacer y, cuando el usuario sabe que estamos uniendo un conjunto de partes, hace que el interfaz sea más fácil de entender.

Cuando heredamos, estamos cogiendo una clase existente y creando una versión especial de esa clase. En general, esto significa que estamos tomando una clase de propósito general, especializándola para un caso o necesidad particular. Pensando un poco, podrá entender que no tendría sentido construir un coche usando un objeto vehículo (un coche no contiene un vehículo, ¡es un vehículo!). La relación es- un viene expresada por la herencia, y la relación tiene un viene expresada por la composición.