JavaRush /Blogue Java /Random-PT /Pausa para café #90. 4 pilares da programação orientada a...

Pausa para café #90. 4 pilares da programação orientada a objetos

Publicado no grupo Random-PT
Fonte: The Geek Asian Vamos dar uma olhada nos quatro fundamentos da programação orientada a objetos e tentar entender como eles funcionam. A programação orientada a objetos (OOP) é ​​um dos principais paradigmas de programação. Pode ser fácil e simples ou, pelo contrário, muito complexo. Tudo depende de como você decide desenvolver sua aplicação. Pausa para café #90.  4 pilares da programação orientada a objetos - 1Existem 4 pilares da OOP:
  1. Encapsulamento.
  2. Herança.
  3. Abstração.
  4. Polimorfismo.
Discutiremos agora cada um deles com uma breve explicação e um exemplo de código real.

1. Encapsulamento

Todos nós estudamos o encapsulamento para ocultar elementos de dados e permitir que os usuários acessem os dados usando métodos públicos. Chamamos esses getters e setters. Agora vamos esquecer isso e encontrar uma definição mais simples. O encapsulamento é um método de restringir o usuário de alterar diretamente membros de dados ou variáveis ​​de classe para manter a integridade dos dados. Como vamos fazer isso? Restringimos o acesso às variáveis ​​mudando o modificador de acesso para privado e expondo métodos públicos que podem ser usados ​​para acessar dados. Vejamos exemplos específicos abaixo. Isso nos ajudará a entender como podemos usar o encapsulamento para manter a integridade dos dados. Sem encapsulamento:
/**
 * @author thegeekyasian.com
 */
public class Account {

  public double balance;

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.balance = -54;
  }
}
No trecho de código acima, o método main() acessa a variável balance diretamente. Isso permite ao usuário definir qualquer valor duplo para a variável balance da classe Account . Podemos perder a integridade dos dados ao permitir que qualquer pessoa defina o saldo para qualquer número inválido, como -54 neste caso. Com encapsulamento:
/**
 * @author thegeekyasian.com
 */
public class Account {

  private double balance;

  public void setBalance(double balance) {

    if(balance >= 0) { // Validating input data in order to maintain data integrity
	  this.balance = balance;
    }

    throw new IllegalArgumentException("Balance cannot be less than zero (0)");
  }

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.setBalance(1); // Valid input - Allowed
  	theGeekyAsianAccount.setBalance(-55); // Stops user and throws exception
  }
}
Neste código, restringimos o acesso à variável balance e adicionamos um método setBalance() que permite aos usuários definir o valor do saldo para Account . O setter verifica o valor fornecido antes de atribuí-lo à variável. Se o valor for menor que zero, uma exceção será lançada. Isso garante que a integridade dos dados não seja comprometida. Depois de explicar os exemplos acima, espero que o valor do encapsulamento como um dos quatro pilares da OOP esteja claro.

2. Herança

Herança é um método de obtenção de propriedades de outra classe que compartilham características comuns. Isso nos permite aumentar a reutilização e reduzir a duplicação de código. O método também possui o princípio da interação filho-pai, quando um elemento filho herda as propriedades de seu pai. Vamos mergulhar em dois exemplos rápidos e ver como a herança torna o código mais simples e reutilizável. Sem herança:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square {

  private int width; // Duplicate property, also used in class Rectangle

  public Square(int width) {
	this.width = width;
  }

  public int getArea() { // Duplicate method, similar to the class Rectangle
	return this.width * this.width;
  }
}
As duas classes semelhantes compartilham as propriedades de largura e o método getArea() . Podemos aumentar a reutilização de código fazendo uma pequena refatoração onde a classe Square acaba herdando da classe Rectangle . Com herança:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square extends Rectangle {

  public Square(int width) {
	super(width, width); // A rectangle with the same height as width is a square
  }
}
Simplesmente estendendo a classe Rectangle , obtemos a classe Square como um tipo Rectangle . Isso significa que ele herda todas as propriedades comuns a Square e Rectangle . Nos exemplos acima, vemos como a herança desempenha um papel importante em tornar o código reutilizável. Também permite que uma classe herde o comportamento de sua classe pai.

3. Abstração

Abstração é uma técnica de apresentar apenas detalhes essenciais ao usuário, ocultando detalhes desnecessários ou irrelevantes de um objeto. Ajuda a reduzir a complexidade operacional do lado do usuário. A abstração nos permite fornecer uma interface simples ao usuário sem solicitar detalhes complexos para executar uma ação. Simplificando, dá ao usuário a capacidade de dirigir um carro sem exigir que ele entenda exatamente como o motor funciona. Vejamos primeiro um exemplo e depois discutiremos como a abstração nos ajuda.
/**
* @author thegeekyasian.com
*/
public class Car {

  public void lock() {}
  public void unlock() {}

  public void startCar() {

	checkFuel();
	checkBattery();
	whatHappensWhenTheCarStarts();
  }

  private void checkFuel() {
	// Check fuel level
  }

  private void checkBattery() {
	// Check car battery
  }

  private void whatHappensWhenTheCarStarts() {
	// Magic happens here
  }
}
No código acima, os métodos lock() , unlock() e startCar() são públicos e o restante é privado para a classe. Tornamos mais fácil para o usuário “dirigir o carro”. Claro, ele poderia verificar manualmente checkFuel() e checkBattery() antes de ligar o carro com startCar() , mas isso apenas complicaria o processo. Com o código acima, tudo o que o usuário precisa fazer é usar startCar() e a classe cuidará do resto. Isso é o que chamamos de abstração.

4. Polimorfismo

O último e mais importante dos quatro pilares da OOP é o polimorfismo. Polimorfismo significa “muitas formas”. Como o nome sugere, é uma função que permite realizar uma ação de múltiplas ou diferentes maneiras. Quando falamos em polimorfismo, não há muito o que discutir, a menos que falemos sobre seus tipos. Existem dois tipos de polimorfismo:
  1. Sobrecarga de método - polimorfismo estático (Static Binding).
  2. Substituição de método - polimorfismo dinâmico (Dynamic Binding).
Vamos discutir cada um desses tipos e ver qual é a diferença entre eles.

Sobrecarga de método - polimorfismo estático:

Sobrecarga de método ou polimorfismo estático, também conhecido como ligação estática ou ligação em tempo de compilação, é um tipo no qual as chamadas de método são determinadas em tempo de compilação. A sobrecarga de métodos nos permite ter vários métodos com o mesmo nome, com diferentes tipos de dados de parâmetros, ou diferentes números de parâmetros, ou ambos. Mas a questão é: por que a sobrecarga de métodos (ou polimorfismo estático) é útil? Vejamos os exemplos abaixo para entender melhor a sobrecarga de métodos. Sem sobrecarga de método:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sumInt(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sumDouble(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sumInt(1, 2);
	number.sumDouble(1.8, 2.5);
  }
}
No exemplo acima, criamos dois métodos com nomes diferentes, apenas para somar dois tipos diferentes de números. Se continuarmos com uma implementação semelhante, teremos múltiplos métodos com nomes diferentes. Isso reduzirá a qualidade e a disponibilidade do código. Para melhorar isso, podemos usar a sobrecarga de métodos usando o mesmo nome para métodos diferentes. Isso permitirá que o usuário tenha uma opção como ponto de entrada para somar diferentes tipos de números. A sobrecarga de métodos funciona quando dois ou mais métodos têm o mesmo nome, mas parâmetros diferentes. O tipo de retorno pode ser igual ou diferente. Mas se dois métodos tiverem o mesmo nome, os mesmos parâmetros, mas tipos de retorno diferentes, isso causará sobrecarga e um erro de compilação! Com sobrecarga de método:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sum(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sum(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sum(1, 2);
	number.sum(1.8, 2.5);
  }
}
No mesmo código, com algumas pequenas alterações, conseguimos sobrecarregar os dois métodos, tornando os nomes iguais para ambos. O usuário agora pode especificar seus tipos de dados específicos como parâmetros de método. Em seguida, ele executará uma ação com base no tipo de dados fornecido. Essa ligação de método é feita em tempo de compilação porque o compilador sabe qual método será chamado com o tipo de parâmetro especificado. É por isso que chamamos isso de vinculação em tempo de compilação.

Substituição de método - polimorfismo dinâmico:

Ao contrário da sobrecarga de métodos, a substituição de métodos permite que você tenha exatamente a mesma assinatura de vários métodos, mas eles devem estar em várias classes diferentes. A questão é: o que há de tão especial nisso? Essas classes possuem um relacionamento IS-A, ou seja, devem herdar uma da outra. Em outras palavras, na substituição de método ou no polimorfismo dinâmico, os métodos são processados ​​dinamicamente em tempo de execução quando o método é chamado. Isso é feito com base na referência ao objeto com o qual foi inicializado. Aqui está um pequeno exemplo de substituição de método:
/**
* @author thegeekyasian.com
*/
public class Animal {

  public void walk() {
	System.out.println("Animal walks");
  }
}

public class Cat extends Animal {

  @Override
  public void walk() {
	System.out.println("Cat walks");
  }
}

public class Dog extends Animal {

  @Override
  public void walk() {
	System.out.println("Dog walks");
  }
}

public class Main {

  public static void main(String[] args) {

	Animal animal = new Animal();
	animal.walk(); // Animal walks

	Cat cat = new Cat();
	cat.walk(); // Cat walks

	Dog dog = new Dog();
	dog.walk(); // Dog walks

	Animal animalCat = new Cat(); // Dynamic Polymorphism
	animalCat.walk(); // Cat walks

	Animal animalDog = new Dog(); // Dynamic Polymorphism
	animalDog.walk(); //Dog walks
  }
}
Neste exemplo principal, atribuímos dinamicamente objetos do tipo “Cachorro” e “Gato” ao tipo “Animal”. Isso nos permite chamar o método walk() em instâncias referenciadas dinamicamente em tempo de execução. Podemos fazer isso usando substituição de método (ou polimorfismo dinâmico). Isso conclui nossa breve discussão sobre os quatro pilares da POO e espero que seja útil para você.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION