Baixe o app para aproveitar ainda mais
Prévia do material em texto
INTRODUÇÃO À PROGRAMAÇÃO ORIENTADA A OBJETOS 04 pilares de Programação Orientada a Objetos Exemplificadas com Python 04 pilares de Programação Orientada a Objetos Para entendermos exatamente do que se trata a orientação a objetos, vamos entender quais são os requerimentos de uma linguagem para ser considerada nesse paradigma. Para isso, a linguagem precisa atender a quatro tópicos bastante importantes: 1. Abstração A abstração é a habilidade de contextualizar problemas e características do mundo real, transpondo-os para o domínio dos sistemas de software e ignorando aspectos que não façam parte do contexto desenvolvido. A abstração de um objeto em um contexto pode ser diferente de outro. Desse modo, os resultados da abstração dependem do contexto em que ela é utilizada. No processo de abstração, os detalhes que não tem importância no contexto são desprezados. Nessa lógica, apenas os detalhes importantes para a resolução do problema são levados em conta. Vejamos um exemplo de abstração do objeto Paciente: • Para um médico o paciente é caracterizado por histórico familiar e médico, idade, sexo, se usa algum medicamento, etc. • Para o hospital, nome, idade, cpf, cns, telefone pra contato da família etc. • Para o maqueiro, o peso, sexo, se está consciente etc. A abstração requer que analisemos um objeto sob diferentes ângulos. Os atributos e os métodos da loja variam de acordo com o ponto de vista Nesse sentido, a abstração permite constatar que os objetos e os métodos variam de acordo com o referencial. Para que um sistema seja completo, é importante que façamos o exercício da abstração. Desse modo, poderemos prever o maior número de atributos e métodos necessários ao bom funcionamento desse sistema. A abstração consiste em um dos pontos mais importantes dentro de qualquer linguagem Orientada a Objetos. Como estamos lidando com uma representação de um objeto real (o que dá nome ao paradigma), temos que imaginar o que esse objeto irá realizar dentro de nosso sistema. São três pontos que devem ser levados em consideração nessa abstração. O primeiro ponto é darmos uma identidade ao objeto que iremos criar. Essa identidade deve ser única dentro do sistema para que não haja conflito. Na maior parte das linguagens, há o conceito de pacotes (ou namespaces). Nessas linguagens, a identidade do objeto não pode ser repetida dentro do pacote, e não necessariamente no sistema inteiro. Nesses casos, a identidade real de cada objeto se dá por <nome_do_pacote>.<nome_do_objeto>.</nome_do_objeto></nome_do_pacote> ex: Medico.pediatra.examinar() A segunda parte diz respeito a características do objeto. Como sabemos, no mundo real qualquer objeto possui elementos que o definem. Dentro da programação orientada a objetos, essas características são nomeadas propriedades. Por exemplo, as propriedades de um objeto “Cachorro” poderiam ser “Tamanho”, “Raça” e “Idade”. class Pessoa: def __init__(self, nome, sexo, cpf, ativo): self.nome = nome self.sexo = sexo self.cpf = cpf self.ativo = ativo Por fim, a terceira parte é definirmos as ações que o objeto irá executar. Essas ações, ou eventos, são chamados métodos. Esses métodos podem ser extremamente variáveis, desde “Acender()” em um objeto lâmpada até “Latir()” em um objeto cachorro. class Pessoa: def __init__(self, nome, sexo, cpf, ativo): self.nome = nome self.sexo = sexo self.cpf = cpf self.ativo = ativo def desativar(self): self.ativo = False print("A pessoa foi desativada com sucesso") if __name__ == "__main__": pessoa1 = Pessoa("João", "M", "123456", True) pessoa1.desativar() 2. Encapsulamento O encapsulamento é uma das principais técnicas que define a programação orientada a objetos. Se trata de um dos elementos que adicionam segurança à aplicação em uma programação orientada a objetos pelo fato de esconder as propriedades, criando uma espécie de caixa preta. A maior parte das linguagens orientadas a objetos implementam o encapsulamento baseado em propriedades privadas, ligadas a métodos especiais chamados getters e setters, que irão retornar e setar o valor da propriedade, respectivamente. Essa atitude evita o acesso direto à propriedade do objeto, adicionando uma outra camada de segurança à aplicação. Para fazermos um paralelo com o que vemos no mundo real, temos o encapsulamento em outros elementos. Por exemplo, quando clicamos no botão ligar da televisão, não sabemos o que está acontecendo internamente. Podemos então dizer que os métodos que ligam a televisão estão encapsulados. Basicamente, o encapsulamento visa definir o que pode ou não ser acessado de forma externa da classe. Existem três tipos de atributos de visibilidade nas linguagens orientadas a objetos, que são: Public: Atributos e métodos definidos como públicos poderão ser invocados, acessados e modificados através de qualquer lugar do projeto; Private: Atributos e métodos definidos como privados só poderão ser invocados, acessados e modificados somente por seu próprio objeto. Protected: Atributos e métodos definidos como protegidos só poderão ser invocados, acessados e modificados por classes que herdam de outras classes através do conceito de Herança, visto na última aula. Sendo assim, apenas classes “filhas” poderão acessar métodos e atributos protegidos. No Python, diferente das linguagens completamente voltadas ao paradigma da orientação à objetos (Java, C#, etc.), estes atributos existem, mas não da forma “convencional”. Para definir um atributo público, não há necessidade de realizar nenhuma alteração, por padrão, todos os atributos e métodos criados no Python são definidos com este nível de visibilidade. Já se precisarmos definir um atributo como privado, adicionamos dois underlines (__) antes do nome do atributo ou método: class Pessoa: def __init__(self, nome, sexo, cpf, ativo): self.nome = nome self.sexo = sexo self.cpf = cpf self.__ativo = ativo def desativar(self): self.__ativo = False print("A pessoa foi desativada com sucesso") if __name__ == "__main__": pessoa1 = Pessoa("João", "M", "123456", True) pessoa1.desativar() pessoa1.ativo = True print(pessoa1.ativo) Porém, isso é apenas uma “convenção” do Python, ou seja, mesmo definindo o atributo com visibilidade privada (utilizando dois underlines antes de seu nome), ele poderá ser acessado de fora da classe: Isso ocorre porquê estamos falando de “convenções”, ou seja, padrões que devem ser seguidos por desenvolvedores Python. Porém, caso precisemos acessar os atributos privados de uma classe, o Python oferece um mecanismo para construção de propriedades em uma classe e, dessa forma, melhorar a forma de encapsulamento dos atributos presentes. É comum que, quando queremos obter ou alterar os valores de um atributo, criamos métodos getters e setters para este atributo: class Pessoa: #classe construtora def desativar(self): #metodo desativar def get_nome(self): return self.__nome def set_nome(self, nome): self.__nome = nome if __name__ == "__main__": pessoa1 = Pessoa("João", "M", "123456", True) pessoa1.desativar() pessoa1.ativo = True print(pessoa1.ativo) # Utilizando geters e setters pessoa1.set_nome("José") print(pessoa1.get_nome()) Porém, ao tentar acessar o valor do atributo nome presente na classe, fica evidente que estamos obtendo esse dado através de um método. Pensando nisso, o time de desenvolvimento criou as Properties para prover um meio mais “elegante” para obter e enviar novos dados aos atributos de uma classe: class Pessoa: def __init__(self, nome, sexo, cpf, ativo):self.__nome = nome self.__sexo = sexo self.__cpf = cpf self.__ativo = ativo def desativar(self): self.__ativo = False print("A pessoa foi desativada com sucesso") def get_nome(self): return self.__nome def set_nome(self, nome): self.__nome = nome @property def nome(self): return self.__nome @nome.setter def nome(self, nome): self.__nome = nome if __name__ == "__main__": pessoa1 = Pessoa("João", "M", "123456", True) pessoa1.desativar() pessoa1.ativo = True print(pessoa1.ativo) # Utilizando geters e setters pessoa1.set_nome("José") print(pessoa1.get_nome()) # Utilizando properties pessoa1.nome = "José" print(pessoa1.nome) Com isso, podemos ver o quão mais “limpo” é o uso das properties para acessar ou alterar o valor de uma propriedade privada das classes no Python. 3. Herança Herança Vimos que o polimorfismo é complementado também pelo conceito de herança – o quarto e último pilar da POO. A palavra "herança" significa aquilo que se herda, aquilo que é transmitido por hereditariedade. Podemos entender a herança por meio da genética. Por exemplo, um filho herda as características genética dos pais e possui suas próprias características. Por sua vez, repassa suas características a seus filhos. Desse modo, os filhos do filho podem ter características dos avós e as próprias características também. A notação de uma herança é representada por um triângulo com o vértice apontado para a classe base. Outra notação muito aceita é a seguinte: coloca-se o nome da classe filha mais o símbolo de doispontos (:) e, em seguida, coloca-se o nome da classe herdada O reuso de código é uma das grandes vantagens da programação orientada a objetos. Muito disso se dá por uma questão que é conhecida como herança. Essa característica otimiza a produção da aplicação em tempo e linhas de código. Para entendermos essa característica, vamos imaginar uma família: a criança, por exemplo, está herdando características de seus pais. Os pais, por sua vez, herdam algo dos avós, o que faz com que a criança também o faça, e assim sucessivamente. Na orientação a objetos, a questão é exatamente assim, como mostra a figura. O objeto abaixo na hierarquia irá herdar características de todos os objetos acima dele, seus “ancestrais”. A herança a partir das características do objeto mais acima é considerada herança direta, enquanto as demais são consideradas heranças indiretas. Por exemplo, na família, a criança herda diretamente do pai e indiretamente do avô e do bisavô. Figura 1 Herança na orientação a objetos A questão da herança varia bastante de linguagem para linguagem. Em algumas delas, como C++, há a questão da herança múltipla. Isso, essencialmente, significa que o objeto pode herdar características de vários “ancestrais” ao mesmo tempo diretamente. Em outras palavras, cada objeto pode possuir quantos pais for necessário. Devido a problemas, essa prática não foi difundida em linguagens mais modernas, que utilizam outras artimanhas para criar uma espécie de herança múltipla. Outras linguagens orientadas a objetos, como C#, trazem um objeto base para todos os demais. A classe object fornece características para todos os objetos em C#, sejam criados pelo usuário ou não. Para utilizar a herança no Python é bem simples. Vamos criar quatro classes para representar as entidades Animal, Gato, Cachorro e Coelho. class Animal(): def __init__(self, nome, cor): self.__nome = nome self.__cor = cor def comer(self): print(f"O {self.__nome} está comendo") No código acima definimos a classe pai que irá possuir todos os atributos e métodos comuns às classes filhas (Gato, Cachorro e Coelho). Nela, criamos apenas o construtor que irá receber o nome e a cor do animal, além do método comer que vai exibir a mensagem com o nome do animal que está comendo. Após isso, criamos as três classes “filhas” da classe Animal. Para definir que estas classes são herdeiras da classe Animal, declaramos o nome da classe pai nos parenteses logo após definir o nome da classe, como podemos ver abaixo: import animal class Gato(animal.Animal): def __init__(self, nome, cor): super().__init__(nome, cor) import animal class Cachorro(animal.Animal): def __init__(self, nome, cor): super().__init__(nome, cor) import animal class Coelho(animal.Animal): def __init__(self, nome, cor): super().__init__(nome, cor) Note que as classes filhas só estão repassando seus dados de nome e cor para a classe Pai através do super() e que nenhum método foi implementado dentro dessas classes. Agora, por herdar da classe Animal, as classes Gato, Cachorro e Coelho podem, sem nenhuma alteração, utilizar o método comer(), definido na classe Animal pois elas herdam dessa classe, logo elas possuem a permissão de invocar este método: import gato, cachorro, coelho gato = gato.Gato("Bichano", "Branco") cachorro = cachorro.Cachorro("Totó", "Preto") coelho = coelho.Coelho("Pernalonga", "Cinza") gato.comer() cachorro.comer() coelho.comer() Ao executar o código acima, obtemos o seguinte retorno no terminal: O Bichano está comendo O Totó está comendo O Pernalonga está comendo E é dessa forma que é implementado o conceito de herança no Python. 4. Polimorfismo Na POO, o polimorfismo denota uma situação em que um objeto pode comportar-se de maneiras diferentes ao receber uma mensagem. O comportamento do objeto vai depender do modo como ele foi concebido. O polimorfismo é complementado pelos conceitos de herança e sobrecarga de métodos. A sobrecarga consiste em escrever métodos de mesmo nome com assinaturas diferentes. Em outras palavras, podemos criar vários métodos de mesmo nome com diferentes passagens de parâmetros. Sabemos que uma impressora a laser tem um mecanismo de impressão totalmente diferente de uma impressora a jato de tinta. No entanto, para o aplicativo, isso não importa, pois o envio da mensagem à impressora para que ela possa imprimir é feito por meio de códigos ou instruções. Como vimos, a maneira como a impressora imprime o relatório varia de acordo com o tipo do equipamento. Nesse caso, a impressão ocorre de formas diferentes para a mesma mensagem de impressão. Outro exemplo de sobrecarga de método ocorre quando enviamos o comando imprimir a partir de um documento do Word. Nessa situação, abre-se uma caixa de diálogo onde o usuário deve selecionar para qual impressora será dada a função de imprimir. Ao abrir um documento do Excel e solicitar sua impressão, a mesma caixa de diálogo aparecerá. Você sabe como o módulo de impressão sabe a diferença entre um documento do Word, uma planilha do Excel, uma imagem e uma fotografia? Para que um documento visualizado na tela do computador se torne um documento impresso, muitos métodos, classes, interfaces e códigos foram escritos. A partir daí, a operação imprimir se torna extremamente simples. Um exemplo de polimorfismo para o método comunicar() Observe abaixo como diferentes objetos respondem ao método (ação) serHumano.comunicar(): Olá! Tudo bem? cao. comunicar(): Au, au! gato. comunicar(): Miau, miau! passaro. comunicar(): Piu, piu! Note que os diferentes objetos – criança, cão, gato e pássaro – comunicam-se de diferentes formas. Desse modo, as respostas do método comunicar() são diferentes para cada um dos objetos. Isso significa que classes comuns podem conter mensagens diferentes se os objetos utilizarem essas classes para um método comum. Em outras palavras, o polimorfismo consiste na alteração do funcionamento interno de um método herdado de um objetopai. Em outro exemplo, temos um objeto genérico “Eletrodoméstico”. Esse objeto possui um método, ou ação, “Ligar()”. Temos dois objetos, “Televisão” e “Geladeira”, que não irão ser ligados da mesma forma. Assim, precisamos, para cada uma das classes filhas, reescrever o método “Ligar()”. Com relação ao polimorfismo, como se trata de um assunto que está intimamente conectado à herança, entender os dois juntamente é uma boa ideia. Para implementar polimorfismo, é essencial que objetos de classes distintas implementem uma mesma interface. Para obter isso usando herança, podemos fazer o seguinte: • Criar uma classe base com uma interface específica. • Criar classes derivadas que implementam essa interface de formas diferentes. Vamos usar um exemplo para ilustrar este processo. Suponha que tenhamos uma classe Animal, cuja interface consiste de um único método, chamado locomove, que indica como o animal se move. Podemos implementar essa classe como no exemplo abaixo: class Animal: def __init__(self): pass def locomove(self): pass Agora, desejamos criar classes derivadas da classe Animal que implementam o método locomove de formas diferentes. Um modo de fazer isso é o mostrado no exemplo abaixo: class Peixe(Animal): def locomove(self): print("Um peixe nada.") class Elefante(Animal): def locomove(self): print("Um elefante anda.") class Passaro(Animal): def locomove(self): print("Um pássaro voa.") Com isso, dependendo do tipo de animal com o qual estivermos lidando, o método locomove apresentará um comportamento diferente. Por exemplo, considere o código abaixo, no qual invocamos o método locomove para diferentes tipos de animais: peixe = Peixe() elefante = Elefante() passaro = Passaro() peixe.locomove() elefante.locomove() passaro.locomove() Saída: Um peixe nada. Um elefante anda. Um pássaro voa. Referencias: https://www.treinaweb.com.br/blog/orientacao-a-objetos-em-python https://algoritmosempython.com.br/cursos/programacao-python/polimorfismo/ https://www.treinaweb.com.br/blog/utilizando-heranca-no-python Departamento de Educação Profissional e Educação de Jovens e Adultos, Informação e comunicação Ead, Fundação Bradesco, Programação Orientada a Objetos, 2017. https://www.treinaweb.com.br/blog/orientacao-a-objetos-em-python https://algoritmosempython.com.br/cursos/programacao-python/polimorfismo/ https://www.treinaweb.com.br/blog/utilizando-heranca-no-python
Compartilhar