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