Modelo:Introducción á P.O.O.

De Wiki do Ciclo ASIR do IES de Rodeira
Saltar á navegación Saltar á procura

Introducción á Programación Orientada a Obxecto.

Todas as linguaxes de programación pretenden acercar a programación das computadoras á linguaxe humana. A linguaxe ensamblador permite enfocar a programación como unha serie de movementos de información e de operacións matemáticas permitíndonos esquecernos do funcionamento electrónico da máquina; un paso máis son as linguaxes imperativas (C, COBOL, FORTRAN, Pascal ...), que permiten acercar aínda máis a programación á linguaxe humana. Sen embargo, a programación coas linguaxes imperativas aínda esixe que pensemos en termos da estructura dos computadores máis que en termos da estructura do problema a resolver.

A programación orientada a obxectos (OOP) pretende proporcionar utilidades ó programador para representar os distintos elementos que interveñen nun problema. Deste xeito, un programa sería un conxunto de obxectos interactuando entre eles.

Por exemplo, un programa que controlara automaticamente a luz dunha habitación podería constar dun obxecto lámpada e un obxecto sensor. O obxecto sensor enviaría ó obxecto lámpada a mensaxe on cando a luz baixa dun límite e off cando sube dese límite.

Ficheiro:Lampada.gif
Envío de Mensaxes entre obxectos

Os obxectos almacenan datos é se lles pode facer peticións para que realicen operacións sobre si mesmo. En teoría pódese coller calquera compoñente conceptual do problema (edificios, sofás, lámpadas, ... etc) e representalos mediante un obxecto no programa.

Existen distintas aproximacións á programación orientada a obxectos, en C++ por exemplo, pode escribirse código "de programa" sen formar parte de ningún obxecto, e en JAVA Todo é un obxecto.

  • Un programa é un conxunto de obxectos dicíndolle uns ós outros qué facer mandándolles mensaxes. En realidade as mensaxes consistirán en chamar funcións pertencentes ó obxecto ó que lle mandamos a mensaxe.
  • Cada obxecto ten a súa propia memoria composta doutros obxectos. É dicir, pódense crear novos obxectos xuntando varios obxectos xa existentes, aumentando a complexidade do programa ó mesmo tempo que permanece oculta nos novos obxectos.
  • Cada obxecto ten un tipo ou 'clase'. Ou sexa, cada obxecto é un elemento pertencente a unha clase determinada. A característica máis importante dunha clase son as mensaxes que pode recibir (as súas funcións).

As mensaxes as que pode responder un obxecto atópanse especificadas na súa interface, que ven imposta polo tipo de obxecto (a súa clase). De aquí en diante especificaremos os obxectos do seguinte xeito:

Ficheiro:Lampada1.gif
Representación dun obxecto

Por exemplo, en JAVA un obxecto deste tipo utilizaríase do seguinte xeito:

Lampada Luz = new Lampada();
Luz.On();

Neste caso, Lámpada é o nome da clase (o tipo) e Luz é un 'recipiente' onde se pode almacenar unha referencia a un obxecto concreto dese tipo. Mediante o operador new créase un novo obxecto do tipo Lámpada e almacénase a referencia en Luz. A partir dese instante pódense enviar mensaxes o novo obxecto utilizando Luz. Por exemplo 'On' prende a lámpada.

As mensaxes que se lle poden enviar a un obxecto reciben o nome de métodos da clase (en realidade son funcións que operan sobre a propia clase). Ademais, un obxecto pode estar composto por outros obxectos que reciben o nome de membros da clase ou atributos.

Entre os programadores nas linguaxes orientadas a obxecto pódese distinguir entre os creadores de clases e os programadores cliente, de xeito parecido ós creadores de librerías e os usuarios das mesmas nas linguaxes tradicionais. Os creadores de clases encárganse de desenvolver novas clases de obxectos de utilidade xeral, mestras que os programadores cliente utilizarán estas para desenvolver o seu proxecto particular.

Unha das maiores vantaxes da programación orientada a obxectos é que permite a reutilización de código con gran facilidade ademais de simplificar tamén o mantemento do mesmo. Como as aplicacións non son máis que un conxunto de obxectos enviándose mensaxes, é posible modificar os obxectos de forma independente sen afectar ó resto do programa. Tamén é posible verificar o comportamento dos obxectos de forma individual, e localizar o obxecto erróneo con facilidade.

Para conseguir todo esto é necesario facer que o usuario da clase non poda modificar partes críticas do obxecto que poidan afectar o correcto funcionamento do mesmo, e dicir, separar o interface da clase da súa implantación, ocultando o cliente o código de funcionamento interno da clase (Unicamente nos interesa que unha lámpada se pode prender, non cómo). Para facer esto as linguaxes orientadas a obxecto posúen unha serie de modificadores de acceso, que en JAVA son public, private e protected, ademais do acceso por defecto que se chama friendly.

  • public : Todas as outras clases teñen acceso os métodos ou membros public.
  • private : Unicamente se pode acceder os métodos ou membros private desde dentro da propia clase.
  • protected : Unicamente se pode acceder os métodos ou membros protected desde dentro da propia clase ou desde unha clase herdada.
  • friendly : Unicamente se pode acceder os métodos ou membros friendly desde as clases desenvolvidas no mesmo package.

Unha vez que temos unha clase creada e probada, debería ser unha unidade de código útil e reutilizable. A forma máis simple de reutilizar unha clase e creando obxectos desa clase, pero tamén é posible colocar un obxecto desa clase dentro dunha clase nova. Unha clase pode estar composta de calquera número de outros obxectos.

Esta forma de crear novas clases chámase composición ou agregación, e a miúdo se indica como unha relación 'ten un', por exemplo, un coche ten un motor, e dicir que o obxecto coche ten un membro que é un obxecto motor.

Ficheiro:Motor.gif
Composición/Agregación de obxectos

A Herdanza

Outro concepto importante na programación orientada a obxectos é a herdanza. Moitas veces precisamos dunha nova clase que se comporte de xeito moi similar a unha xa existente, co que sería bo ter un xeito de facer unha copia da clase orixinal e logo facer modificacións e engadidos á copia para ter a nova clase. Esto conséguese mediante a herdanza. A clase orixinal recibe o nome de clase base, pai ou super, mentres que a nova clase chámase clase derivada ou clase filla.


Unha vez que derivamos unha clase herdaremos o seu interface e membros de clase, e poderemos reescribir os métodos que queiramos para modificar o seu comportamento. Os métodos que non reescribamos manterán o comportamento da clase base. Este tipo de relación chámase tamén 'é un'. Por exemplo: Un gorrión é un paxaro.

Tamén é posible engadirlle novos métodos ó interface da clase derivada ampliando así a súa funcionalidade. Neste caso a relación chámase 'é coma un'. Cando teñamos que engadir novos métodos a unha clase derivada paga a pena cuestionarse si eses novos métodos poden pertencer en realidade a clase base.

Vexamos un exemplo:

Tomemos a clase figura xeométrica 'Figura'. Cada 'Figura' ten uns atributos (obxectos membros): tamaño, cor, e posición e é posible realizar sobre ela varias operacións distintas: debuxala, borrala, movela, coloreala ou rotala. A partir de aquí podemos desenvolver novas clases coma 'Circulo', 'Cadrado' e 'Triangulo', que a súa vez son 'Figuras', e polo tanto poderán automaticamente debuxarse, moverse, borrarse, colorearse ou rotarse, ademais de ter un tamaño unha cor e unha posición. Cando creemos un novo obxecto de tipo 'Círculo' poderemos tratalo como un 'Círculo' ou como unha 'Figura', xa que os 'Círculos' son 'Figuras'. O tratar un obxecto dunha clase derivada coma si fora da clase base chámase upcasting.

Ficheiro:Herdanza1.png
Exemplo de Herdanza

Se a forma de debuxarse, moverse, borrarse, colorearse ou rotarse non coincide coa forma en que o fan as 'Figuras' en xeral, será necesario reescribir o método na clase derivada (sobreposición de métodos). Tamén é posible escribir varias versións dun mesmo método dentro dunha clase, o que recibe o nome de sobrecarga de métodos sempre e cando os argumentos que reciban os métodos sexan distintos, xa que o intérprete utilizará os argumentos para elixir o método a chamar.

As veces non é posible implantar todos os métodos dunha clase por ser demasiado xeral, servindo esta unicamente como base para derivar novas clases. Unha clase que non ten implantada toda a súa interface recibe o nome de clase abstracta, e o método que non está programado chámase método abstracto. Tamén é posible definir clases que non teñan definido ningún dos métodos do seu interface, vendo a ser un 'molde' que teñen que cumprir todas as clases derivadas. Estas clases denomínanse interfaces.

No caso particular de JAVA, todas as clases que se poden utilizar derivan dunha clase xenérica chamada Object, o que lles da a tódolos obxectos algunhas características comúns. JAVA é polo tanto unha linguaxe con unha xerarquía de clases con unha soa raíz, a diferencia de outras linguaxes de raíz múltiple como C++. Co JDK de JAVA proporciónase unha extensa librería de clases que cobren a maior parte das necesidades básicas de calquera programador.

NOTA
En algunhas linguaxes unha clase pode derivar de varias clases (herdanza múltiple), sen embargo unha clase en JAVA so pode ter unha clase base (herdanza sinxela).

É moi importante coñecer a xerarquía de clases dun obxecto, xa que nos proporcionará a información necesaria sobre o seu uso e posibilidades.

Ficheiro:Xerarquia.png
Xerarquía de Clases

Coma xa vimos antes é posible crear novas clases derivando a partir dunha clase base. Deste xeito podemos ter a clase 'Figura' e derivar dela a clase 'Triangulo' co que un 'Triangulo' é unha 'Figura'. A causa de esto é posible tratar a un obxecto da clase 'Triangulo' coma un 'Triangulo' ou como unha 'Figura' segundo nos conveña (ollo, o contrario non é certo xa que unha 'Figura' non ten por que ser un 'Triangulo'). Tendo en conta esto... ¿ Qué sucedería no caso seguinte ?

Ficheiro:Paxaros.png
¿Que ocurre neste caso?

Coma os pingüíns teñen un xeito de moverse distinto a maior parte dos paxaros é necesario reescribir o método de moverse, o que non é o caso da pomba. O programa principal invoca ó método move () da clase PAXARO, e tanto os obxectos PINGÜÍN como POMBA son tamén obxectos PAXARO. ¿ A qué método move () se chamará ?. Dende o punto de vista do programador é irrelevante o método que se invoque, xa que o único que lle interesa é que o paxaro se mova, non cómo. Por outra banda o compilador non pode sabelo en tempo de compilación, xa que depende do obxecto PAXARO de que se trate. A solución que toman as linguaxes orientadas a obxecto consiste en non determinar a dirección do método o que se vai a saltar hasta que se execute o programa, sendo en tempo de execución cando se decide a función a chamar. Podes ver mellor o funcionamento executando o seguinte exemplo:

public class ClaseBase {

  void Nome () {
    System.out.println("Son a clase BASE");
  }

  /* 
      Método Inicial do Programa
  */
  public static void main (String[] args) {

    ClaseBase n,n1,n2,n3;

    n= new ClaseBase(); n.Nome();
    n1=new ClaseDerivada1(); n1.Nome();
    n2=new ClaseDerivada2(); n2.Nome();
    n3=new ClaseDerivada3(); n3.Nome();
  }

}

class ClaseDerivada1 extends ClaseBase {

  void Nome () {
    System.out.println("Son a Clase Derivada1");
  }
}


class ClaseDerivada2 extends ClaseBase {
  void Nome () {
    System.out.println("Son a Clase Derivada2");
  }
}

class ClaseDerivada3 extends ClaseBase {

}

Constructores e Destructores

Como podemos observar no exemplo anterior, para crear un obxecto en JAVA é necesario utilizar unha instrucción especial chamada new. En JAVA todas as declaracións son referencias a obxectos (é dicir, unha variable que almacenará a dirección de memoria onde se atopa o obxecto referenciado) e é necesario crear os obxectos mediante chamadas explícitas a new mentres que outras linguaxes coma C++ si que permiten crear obxectos mediante unha simple declaración.


Sen embargo o que tódalas linguaxes orientadas a obxecto teñen en común é a existencia dos métodos constructores. Un método constructor invócase de xeito automático no instante en que se crea un novo obxecto. Os métodos constructores en case todas as linguaxes orientadas a obxectos teñen o mesmo nome que a clase.

 Se nós o escribir a clase non proporcionamos un método constructor o sistema creará ún por defecto. 

Por suposto, é posible sobrecargar o método constructor de xeito que teñamos varias formas de crear un obxecto. Cando nós creamos un novo obxecto, invocaranse de xeito automático os métodos constructores de toda a xerarquía de clases asociada comezando pola raíz, xa que un obxecto derivado é ademais un obxecto da clase pai.... etc.

A maior parte das linguaxes orientadas a obxecto teñen tamén outro método especial destinado a liberar toda a memoria utilizada por un obxecto cando é destruído. Este método que se invoca de xeito automático o destruír un obxecto chámase destructor. JAVA, sen embargo non ten métodos destructores, xa que a memoria é liberada de xeito automático cando é necesario mediante un recolector de lixo. O recolector de lixo encárgase de eliminar os obxectos que non están a ser utilizados cando o sistema ande baixo de memoria. Esto, aínda que é unha gran vantaxe para o programador (evita que nos poidamos esquecer de liberar a memoria), é tamén un problema, xa que non temos forma de saber o instante en que un obxecto é eliminado. Podes observar todo o proceso executando o seguinte exemplo:

public class ClaseBase {
  static int i;

  ClaseBase() { // Constructor
    i++; 
    System.out.println("Creando Obxecto ClaseBase "+i);
  }

  void Nome () {
    System.out.println("Son a clase BASE");
  }

  /*
     Método de Inicio
  */
  public static void main (String[] args) {
    ClaseBase n,n1,n2,n3;

    n= new ClaseBase(); n.Nome();
    n1=new ClaseDerivada1(); n1.Nome();
    n2=new ClaseDerivada2(); n2.Nome();
  }

}

class ClaseDerivada1 extends ClaseBase {
  
  ClaseDerivada1() {
    System.out.println("Creando Obxecto ClaseDerivada1 ...");

  }

  void Nome () {
    System.out.println("Son a Clase Derivada1");
  }
}

class ClaseDerivada2 extends ClaseBase {
  void Nome () {
    System.out.println("Son a Clase Derivada2");
  }
}