Mostrando postagens com marcador Orientação a Objetos. Mostrar todas as postagens
Mostrando postagens com marcador Orientação a Objetos. Mostrar todas as postagens

Acoplamento e Coesão


Acoplamento

Acoplamento é o degrau em que uma classe conhece sobre os membros de outra classe. Se o único conhecimento que a classe A tem sobre a classe B, é que a classe B foi exposta através de sua interface, então as classes A e B são fracamente acopladas, que é uma coisa boa. Se, por outro lado, a classe A se baseia em partes da classe B que não fazem parte da interface de B, então elas são bastante acopladas, o que não é uma coisa boa. Em outras palavras, se A sabe mais do que deveria sobre a forma como B foi implementada, então A e B estão bastante ligadas.
Dessa forma, supondo que o desenvolvedor queira fazer uma melhoria na classe B, que não tem conhecimento da classe A; nesse caso, ele tentará não alterar qualquer código que faça parte da implementação da sua interface, que ele já conhece; assim, ele irá alterar apenas os códigos que não façam parte dessa implementação, o que leva a quebra da classe A, pois são exatamente esses códigos que A conhece.
O extremo oposto também é ruim, ou seja, quando a classe A não sabe absolutamente nada sobre a classe B, e a classe B não sabe absolutamente nada sobre a classe A. Se as classes são sempre alteradas, existe a chance de a outra classe quebrar. Isso é chamado de encapsulamento pobre.
Acoplamento fraco é o estado desejável de ter classes que sejam bem encapsuladas, minimizar as referências a si, e limitar a amplitude do uso da API. Acoplamento forte é o estado indesejável de ter classes que quebram as regras do acoplamento fraco.

Coesão

Enquanto o acoplamento tem a ver com a forma como as classes interagem umas com as outras, a coesão é sobre como uma classe é projetada. A coesão indica o grau em que uma classe tem uma finalidade única e bem orientada. Isso quer dizer que quanto mais focalizada for a classe, maior a sua coesão, o que é uma coisa boa. O principal benefício da alta coesão é que normalmente tais classes são mais fáceis de manter (e menos freqüentemente alteradas) do que as classes com baixa coesão. Outro benefício da coesão alta é que as classes bem focalizadas tendem a ser mais reutilizadas.
Alta coesão é o estado desejável de uma classe cujos membros suportam uma única regra ou responsabilidade bem focada. Baixa coesão é o estado indesejável de uma classe cujos membros suportam múltiplas regras ou responsabilidades desfocadas.



Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide

Métodos e Variáveis Estáticas


Variáveis e métodos estáticos pertencem à classe e não a uma instância. Isso quer dizer que eles podem ser utilizados sem ter que instanciar a classe. Além disso, quando esta classe for carregada, as variáveis estáticas serão compartilhadas por todos os objetos.
Variáveis estáticas não necessitam serem inicializadas, pois recebem o valor default, da mesma forma que variáveis de instância.
Utilizam-se variáveis estáticas para guardar valores que são específicos da classe e não de um objeto, ou seja, todos os objetos terão acesso à mesma cópia da variável estática. Isso quer dizer que quando a classe for chamada pela primeira vez pela JVM, a variável estática será inicializada; se essa variável for incrementada no construtor da classe, toda vez que essa classe for instanciada, essa variável será incrementada e esse valor será compartilhado para todas as instâncias.

public class Pessoa {  
      public static int cont = 0;
     
      public Pessoa() {
            cont++;
      }
     
      public static void main(String[] args) {
            Pessoa pessoa = new Pessoa();
            System.out.println(cont); //1
            pessoa = new Pessoa();
            System.out.println(cont); //2
            /* Qualquer parte do código que tiver
             * acesso a essa variável acessará
             * esse mesmo valor */
      }
}

Métodos estáticos não acessam variáveis de instância e métodos não estáticos, apenas variáveis estáticas e métodos estáticos, pois não há objeto criado no momento em que o método estático é chamado.

public class Aluno {   
      public int cod;
      public static int cont;
     
      public static void aprender() {
            /* métodos estáticos só podem acessar
             * variáveis e métodos estáticos */
            cont++;
            cod++;
            setMatricula();
            setNome();
      }
     
      public void fazerExercicio() {
            /* métodos não-estáticos podem acessar
             * variáveis e métodos de instância ou
             * estáticos */
            cont++;
            cod++;
            setMatricula();
            setNome();
      }
     
      public static void setMatricula() { }
     
      public void setNome() { }   
}

Acessando métodos e variáveis estáticas

Já que não é necessário ter um objeto para invocar um método ou uma variável estática, então a maneira utilizada para acessá-los é através do uso do operador ponto (.), onde se utiliza o nome da classe seguido do operador ponto e o nome do membro estático.
Entretanto, um membro estático também pode ser chamado através de um objeto. Mas mesmo acessando uma variável estática através de um objeto as regras são as mesmas.

public class Carro {   
      static String nome;
     
      public static String getNome() {
            return "Carro1";
      }
     
      public static void main(String[] args) {
            /* acessando a variável estática nome e o
             * método estático getNome através de
             * instanciação; as regras continuam
             * mesmo acessando dessa maneira */
            Carro carro = new Carro();
            carro.nome = "Carro";
            carro.getNome();
            /* Sintaxe correta para acessar métodos e
             * variáveis estáticas */
            Carro.nome = "Carro";
            Carro.getNome();
      }
}

Métodos estáticos não podem ser sobrescritos, mas podem ser reescritos.
Objetos instanciados como subclasse terão os métodos estáticos chamados apenas do tipo da referência e não do objeto. Por exemplo: se a classe A possui o método estático doA(), e esse método é redefinido na sua subclasse B como doA(), se A for instanciado como B (A a = new B();), o objeto só poderá chamar os membros estáticos de A, ao contrário do que ocorre com membros de instância, que serão chamados do tipo da referencia (B).


public class Prova {   
      public static void aplicar() {
            System.out.println("Prova");
      }    
}

public class Java extends Prova {
      /* redefinição (e não override) do
       * método aplicar de Prova */
      public static void aplicar() {
            System.out.println("Prova Java");
      }
     
      public static void main(String[] args) {
            Prova[] provas = { new Prova(),
                                      new Java(),
                                      new Prova() };
            for(int x = 0; x < provas.length; x++)
                  provas[x].aplicar();
            /* os métodos e variáveis estáticas
             * chamadas são sempre da referência
             * (Animal) e nunca do objeto */
      }    
}

Saída da execução:  Prova
    Prova
    Prova


Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide

Construtores e Instanciação


Não é possível criar um objeto sem invocar um construtor; não é possível criar um objeto invocando apenas o construtor do objeto atual, mas também o de suas superclasses. Construtores são códigos executados quando o new é usado; são blocos de inicialização.

Construtor Básico

Toda classe, incluindo classes abstratas, deve ter um construtor; mas isso não quer dizer que é obrigatório tê-lo explicitamente na classe.
Construtores não têm retorno, seus nomes devem combinar exatamente com o nome da classe, e são usados para inicializar o estado de variáveis de instância.

public class Telefone {
      String numero;
      int tipo;  
      // Construtores não possui retorno
      public Telefone(String num, int tipo) {
            this.numero = num;
            this.tipo = tipo;
      }    
      public static void main(String[] args) {
            /* Erro - Essa classe não possui um
             * construtor sem argumentos */
            Telefone telefone = new Telefone();
           
            Telefone telefone2 = new Telefone("79", 1);
      }
}

Construtor Encadeado

Quando existe uma árvore de herança, os construtores se comportam de maneira encadeada.
Por exemplo: existe uma árvore de herança onde Horse herda de Animal e este herda de Object. Ao instanciar uma variável do tipo Horse acontece o seguinte: o construtor de Horse é chamado; todo construtor invoca o construtor da sua superclasse com uma chamada implícita ao método super() (exceto se o construtor de Horse invoca outro construtor sobrecarregado na mesma classe), ou seja, o construtor de Animal é chamado; por sua vez, Animal chama o construtor de Object; como esta é uma classe do topo, todas as suas variáveis são inicializadas com os valores que foram declaradas; o construtor de Object completa a execução; as variáveis de Animal  recebem seus valores explícitos e seu construtor termina a execução;  as variáveis de Horse são inicializadas recebendo seus valores explícitos e seu construtor finaliza a execução.
Isso quer dizer que os construtores sempre são chamados baixo para cima na árvore de herança, mas são executados de cima para baixo, ou seja, primeiro são executados os construtores das superclasses até chegar à última subclasse.

public class Empresa { 
      String cnpj;
      int cod;   
      public Empresa() {
            //const. Empresa chama o const. Object
            this.cnpj = "00000";
            System.out.println("Const. Empresa");
      }
}

public class Filial extends Empresa {
      int codEmpresa;  
      public Filial() {
            //const. Filial chama o c. sobrecarregado
            /*quando um construtor chama outro
             * construtor, a chamada deve estar sempre
             * na primeira linha*/
            this("1111");
            this.codEmpresa = 1;
            System.out.println("Const. Filial");    
      }    
      public Filial(String cnpj) {
            //const. sobrecarregado chama o c. Empresa
            this.cnpj = cnpj;
            System.out.println("C. Sobrecar. Filial");
      }    
}

public class AppEmpresa {   
      public static void main(String[] args) {
            Filial filial = new Filial();
      }
}

Saída dessa execução: Const. Empresa
C. Sobrecar. Filial
Const. Filial

Regras para Construtores

·         Construtores podem usar qualquer modificador de acesso; um construtor privado significa que somente a classe que o declara pode usá-lo; se outra classe quiser acessar uma classe com construtor private, terá que ser através de um método estático ou uma variável que permita acesso a uma instância criada de dentro da classe;
·         O nome do construtor deve ter o mesmo nome da classe;
·         Construtores não devem ter tipos de retorno;
·         É permitido, mas não aconselhável, ter um método com o mesmo nome da classe; a diferença entre os dois é que o método terá um tipo de retorno;
·         Se um construtor não for declarado na classe explicitamente, um construtor default será automaticamente gerado pelo compilador;
·         O construtor default não tem argumentos;
·         Se já existir qualquer construtor na classe, o construtor default não será gerado;
·         Todo construtor possui, na primeira linha, uma chamada explícita ao método super()  (para o construtor da superclasse, se houver) ou a um construtor sobrecarregado através do método this(); caso não haja uma chamada explícita a um desses métodos, o compilador irá gerar uma chamada ao super();
·         Se um construtor for criado, em oposição a chamar o default, e não for feita uma chamada explícita ao método super() ou ao método this(), o compilador irá inserir dinamicamente uma chamada ao super(), caso haja uma superclasse;
·         Uma chamada ao método super() pode ser sem argumento ou incluir argumentos passados para o construtor superior;
·         Um construtor sem argumento não é necessariamente o default, entretanto, o default é sempre um construtor sem argumentos; o construtor default é provido pelo compilador, não é declarado explicitamente;
·         Não é permitido fazer uma chamada a um método de instância, ou acessar uma variável de instância, antes do método super() ou this()  ser executado;
·         Somente variáveis e métodos estáticos podem ser acessados como parte do código para chamar super() e this(). Ex.: super(A.NAME);
·         Classes abstratas possuem construtores, e eles sempre são chamados quando uma classe concreta é instanciada;
·         Interfaces não possuem construtores, porque não fazem parte da árvore de herança de objetos;
·         A única maneira de um construtor ser invocado é de dentro de outro construtor.


Determinando se um construtor default será criado

Se uma superclasse não possui um construtor default ou um construtor sem argumento, a subclasse não poderá ter um construtor default, ou seja, será obrigatória a declaração de um construtor na subclasse para que este possa chamar explicitamente o método super() passando os devidos argumentos.
O construtor default possui o mesmo modificador de acesso da classe, não possui argumentos e possui chamada para o método super().
Construtores não podem ser herdados, pois não são métodos.
Um construtor pode ser diretamente invocado, somente por outro construtor (utilizando uma chamada a super() ou this() ).

public class Roupa {   
      /* classe Roupa não possui construtor
       * default nem construtor sem arg. */
      Roupa(String nome) {}
}

public class Camisa extends Roupa {
      /* como Roupa não possui const. default ou
       * const. sem argumentos, é obrigatória a
       * implementação de um construtor com
       * argumento na subclasse (Camisa) junto
       * com a chamada explícita ao super() */
      Camisa(String i) {
            super(i);        
      }
}

Construtores sobrecarregados

Sobrecarregar um construtor significa escreve-lo em várias versões, cada uma com uma lista de argumentos diferente. A primeira linha de um construtor deve ser uma chamada (explícita ou implícita) a um dos métodos super() ou this(); caso o construtor chame this(), ele não mais chamará super(), a incumbência passará para o próximo construtor chamado por this(), e assim por diante. Isso significa que um construtor nunca poderá ter a chamada para os dois ao mesmo tempo.

Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide