CodeGym /Blogue Java /Random-PT /Princípios de POO
John Squirrels
Nível 41
San Francisco

Princípios de POO

Publicado no grupo Random-PT
Java é uma linguagem orientada a objetos. Isso significa que você precisa escrever programas Java usando um paradigma orientado a objetos. E esse paradigma envolve o uso de objetos e classes em seus programas. Vamos tentar usar exemplos para entender o que são classes e objetos e como aplicar os princípios básicos de OOP (abstração, herança, polimorfismo e encapsulamento) na prática.

O que é um objeto?

O mundo em que vivemos é feito de objetos. Olhando ao redor, podemos ver que estamos cercados por casas, árvores, carros, móveis, pratos e computadores. Todas essas coisas são objetos, e cada uma delas tem um conjunto de características, comportamentos e propósitos específicos. Estamos acostumados com objetos e sempre os usamos para fins muito específicos. Por exemplo, se precisamos ir trabalhar, usamos um carro. Se queremos comer, usamos pratos. E se quisermos descansar, encontramos um confortável sofá. Os seres humanos estão acostumados a pensar em termos de objetos para resolver problemas na vida cotidiana. Esta é uma das razões pelas quais os objetos são usados ​​na programação. Essa abordagem é chamada de programação orientada a objetos. Vamos dar um exemplo. Imagine que você desenvolveu um novo telefone e deseja iniciar a produção em massa. Como desenvolvedor do telefone, você sabe para que serve, como funciona e quais são suas partes (corpo, microfone, alto-falante, fios, botões, etc.). Além do mais, só você sabe como conectar essas partes. Mas você não planeja fazer os telefones pessoalmente - você tem toda uma equipe de trabalhadores para fazer isso. Para eliminar a necessidade de explicar repetidamente como conectar as peças do telefone e garantir que todos os telefones sejam feitos da mesma maneira, antes de começar a produzi-los, você precisa fazer um desenho que descreva como o telefone está organizado. Em OOP, chamamos essa descrição, desenho, diagrama ou modelo de classe. Ele forma a base da criação de objetos quando o programa está em execução. Uma classe é uma descrição de objetos de certo tipo — como um modelo comum que consiste em campos, métodos e um construtor. Um objeto é uma instância de uma classe.

Abstração

Vamos agora pensar em como podemos passar de um objeto no mundo real para um objeto em um programa. Usaremos o telefone como exemplo. Este meio de comunicação tem uma história que se estende por mais de 100 anos. O telefone moderno é um dispositivo muito mais complexo do que seu antecessor do século XIX. Ao usar o telefone, não pensamos em sua organização e nos processos que ocorrem dentro dele. Simplesmente usamos as funções fornecidas pelos desenvolvedores do telefone: botões ou tela sensível ao toque para inserir um número de telefone e fazer chamadas. Uma das primeiras interfaces de telefone foi uma manivela que precisava ser girada para fazer uma chamada. Claro, isso não era muito conveniente. Mas cumpriu sua função com perfeição. Se você comparar os telefones mais modernos e os primeiros, você pode identificar imediatamente as funções mais importantes para o dispositivo do final do século XIX e para o smartphone moderno. Eles são a capacidade de fazer chamadas e a capacidade de receber chamadas. Na verdade, é isso que torna o telefone um telefone, e não outra coisa. Agora é só aplicar um princípio da POO: identificar as características e informações mais importantes de um objeto. Este princípio é chamado de abstração. Em OOP, a abstração também pode ser definida como um método de representação de elementos de uma tarefa do mundo real como objetos em um programa. A abstração está sempre associada à generalização de certas propriedades de um objeto, portanto, o principal é separar as informações significativas das insignificantes no contexto da tarefa em questão. Além disso, pode haver vários níveis de abstração. Deixar' Vamos tentar aplicar o princípio da abstração aos nossos telefones. Para começar, identificaremos os tipos de telefones mais comuns — desde os primeiros até os atuais. Por exemplo, poderíamos representá-los na forma do diagrama da Figura 1. Princípios da POO - 2Usando a abstração, podemos agora identificar as informações gerais nesta hierarquia de objetos: o objeto abstrato geral (telefone), características comuns do telefone (por exemplo, ano de sua criação) e a interface comum (todos os telefones podem receber e fazer chamadas). Veja como fica em Java:

public abstract class AbstractPhone {
    private int year;

    public AbstractPhone(int year) {
        this.year = year;
    }
    public abstract void call(int outgoingNumber);
    public abstract void ring(int incomingNumber);
}
Em um programa, podemos criar novos tipos de telefones usando essa classe abstrata e aplicando outros princípios básicos de OOP, que exploraremos a seguir.

Encapsulamento

Com a abstração, identificamos o que é comum a todos os objetos. Mas cada tipo de telefone é único, de alguma forma diferente dos outros. Em um programa, como traçamos limites e identificamos essa individualidade? Como fazemos para que ninguém possa acidentalmente ou deliberadamente quebrar nosso telefone ou tentar converter um modelo em outro? No mundo real, a resposta é óbvia: você precisa colocar todas as peças em uma capa de telefone. Afinal, se você não o fizer - em vez de deixar todas as partes internas do telefone e conectar os fios do lado de fora - algum experimentador curioso definitivamente desejará "melhorar" nosso telefone. Para evitar esse tipo de manipulação, o princípio do encapsulamento é usado no projeto e na operação de um objeto. Este princípio afirma que os atributos e o comportamento de um objeto são combinados em uma única classe, o objeto' A implementação interna de s é oculta do usuário e uma interface pública é fornecida para trabalhar com o objeto. A tarefa do programador é determinar quais atributos e métodos de um objeto devem estar disponíveis para acesso público e quais são detalhes de implementação interna que devem ser inacessíveis.

Encapsulamento e controle de acesso

Suponha que as informações sobre um telefone (seu ano de produção ou o logotipo do fabricante) estejam gravadas na parte traseira quando ele é feito. A informação (seu estado) é específica para este modelo particular. Podemos dizer que o fabricante garantiu que essa informação fosse imutável - é improvável que alguém pensasse em remover a gravação. No mundo Java, uma classe descreve o estado de objetos futuros usando campos e seu comportamento é descrito usando métodos. O acesso ao estado e comportamento de um objeto é controlado usando modificadores aplicados a campos e métodos: privado, protegido, público e padrão. Por exemplo, decidimos que o ano de produção, o nome do fabricante e um dos métodos são detalhes de implementação interna da classe e não podem ser alterados por outros objetos no programa. Em código,

public class SomePhone {

    private int year;
    private String company;
    public SomePhone(int year, String company) {
        this.year = year;
        this.company = company;
    }
private void openConnection(){
    // findSwitch
    // openNewConnection...
}
public void call() {
    openConnection();
    System.out.println("Calling");
}

public void ring() {
    System.out.println("Ring-ring");
}

 }
O modificador private permite que os campos e métodos da classe sejam acessados ​​apenas dentro desta classe. Isso significa que é impossível acessar campos privados de fora, porque os métodos privados não podem ser chamados. Restringir o acesso ao método openConnection também nos permite alterar livremente a implementação interna do método, uma vez que é garantido que o método não será usado ou interromperá o trabalho de outros objetos. Para trabalhar com nosso objeto, deixamos os métodos call e ring disponíveis usando o modificador public. Fornecer métodos públicos para trabalhar com objetos também faz parte do encapsulamento, pois se o acesso fosse totalmente negado, ele se tornaria inútil.

Herança

Vamos dar outra olhada no diagrama de telefones. Você pode ver que é uma hierarquia na qual um modelo tem todos os recursos dos modelos localizados mais acima ao longo de sua ramificação e adiciona alguns próprios. Por exemplo, um smartphone usa uma rede celular para comunicação (tem as propriedades de um telefone celular), é sem fio e portátil (tem as propriedades de um telefone sem fio) e pode receber e fazer chamadas (tem as propriedades de um telefone). O que temos aqui é a herança das propriedades do objeto. Na programação, herança significa usar classes existentes para definir novas classes. Vamos considerar um exemplo de uso de herança para criar uma classe de smartphone. Todos os telefones sem fio são alimentados por baterias recarregáveis, que têm uma certa duração de bateria. Assim, adicionamos esta propriedade à classe de telefone sem fio:

public abstract class CordlessPhone extends AbstractPhone {

    private int hour;

    public CordlessPhone (int year, int hour) {
        super(year);
        this.hour = hour;
    }
    }
Os telefones celulares herdam as propriedades de um telefone sem fio e implementamos os métodos de chamada e toque nesta classe:

public class CellPhone extends CordlessPhone {
    public CellPhone(int year, int hour) {
        super(year, hour);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming call from " + incomingNumber);
    }
}
E, finalmente, temos a classe dos smartphones, que, ao contrário dos celulares clássicos, possui um sistema operacional completo. Você pode expandir a funcionalidade do seu smartphone adicionando novos programas que podem ser executados em seu sistema operacional. No código, a classe pode ser descrita da seguinte forma:

public class Smartphone extends CellPhone {
    
    private String operationSystem;

    public Smartphone(int year, int hour, String operationSystem) {
        super(year, hour);
        this.operationSystem = operationSystem;
    }
public void install(String program) {
    System.out.println("Installing " + program + " for " + operationSystem);
}

}
Como você pode ver, criamos bastante código novo para descrever a classe Smartphone , mas temos uma nova classe com novas funcionalidades. Este princípio de OOP permite reduzir significativamente a quantidade de código Java necessária, facilitando assim a vida do programador.

Polimorfismo

Apesar das diferenças na aparência e no design de vários tipos de telefones, podemos identificar alguns comportamentos comuns: todos eles podem receber e fazer chamadas e todos têm um conjunto de controles razoavelmente claro e simples. Em termos de programação, o princípio da abstração (com o qual já estamos familiarizados) nos permite dizer que os objetos telefone possuem uma interface comum. É por isso que as pessoas podem facilmente usar diferentes modelos de telefones que possuem os mesmos controles (botões mecânicos ou tela sensível ao toque), sem se aprofundar nos detalhes técnicos do dispositivo. Assim, você usa o celular constantemente e pode facilmente fazer uma ligação do telefone fixo do seu amigo. O princípio da OOP que diz que um programa pode usar objetos com uma interface comum sem nenhuma informação sobre a estrutura interna do objeto é chamado de polimorfismo. Deixar' Vamos imaginar que precisamos de nosso programa para descrever um usuário que pode usar qualquer telefone para ligar para outro usuário. Veja como podemos fazer isso:

public class User {
    private String name;

    public User(String name) {
        this.name = name;
            }

    public void callAnotherUser(int number, AbstractPhone phone){
// And here's polymorphism: using the AbstractPhone type in the code!
        phone.call(number);
    }
}
 }
Agora vamos descrever vários tipos de telefones. Um dos primeiros telefones:

public class ThomasEdisonPhone extends AbstractPhone {

public ThomasEdisonPhone(int year) {
    super(year);
}
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Crank the handle");
        System.out.println("What number would you like to connect to?");
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
Um telefone fixo comum:

public class Phone extends AbstractPhone {

    public Phone(int year) {
        super(year);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
E, finalmente, um telefone com vídeo legal:

public class VideoPhone extends AbstractPhone {

    public VideoPhone(int year) {
        super(year);
    }
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Connecting video call to " + outgoingNumber);
    }
    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming video call from " + incomingNumber);
    }
  }
Criaremos objetos no método main() e testaremos o método callAnotherUser() :

AbstractPhone firstPhone = new ThomasEdisonPhone(1879);
AbstractPhone phone = new Phone(1984);
AbstractPhone videoPhone=new VideoPhone(2018);
User user = new User("Jason");
user.callAnotherUser(224466, firstPhone);
// Crank the handle
// What number would you like to connect to?
user.callAnotherUser(224466, phone);
// Calling 224466
user.callAnotherUser(224466, videoPhone);
// Connecting video call to 224466
Chamar o mesmo método no objeto de usuário produz resultados diferentes. Uma implementação específica do método call é selecionada dinamicamente dentro do método callAnotherUser() com base no tipo específico de objeto passado quando o programa está em execução. Essa é a principal vantagem do polimorfismo – a capacidade de escolher uma implementação em tempo de execução. Nos exemplos de classes de telefone dados acima, usamos substituição de método — um truque em que alteramos a implementação de um método definido na classe base sem alterar a assinatura do método. Isso basicamente substitui o método: o novo método definido na subclasse é chamado quando o programa é executado. Normalmente, quando sobrescrevemos um método, o @Overrideanotação é usada. Ele informa ao compilador para verificar as assinaturas dos métodos substituídos e substituídos. Por fim, para garantir que seus programas Java sejam consistentes com os princípios de OOP, siga estas dicas:
  • identificar as principais características de um objeto;
  • identificar propriedades e comportamentos comuns e usar herança ao criar classes;
  • use tipos abstratos para descrever objetos;
  • tente sempre ocultar métodos e campos relacionados à implementação interna de uma classe.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION