Pular para o conteúdo

Como usar o mixin Python corretamente?

[

Herança e Composição: Um Guia de Programação Orientada a Objetos em Python

por Isaac Rodriguez em 15 de Janeiro de 2024

Neste tutorial, você irá explorar a herança e a composição em Python. A herança e a composição são dois conceitos importantes na programação orientada a objetos que modelam a relação entre duas classes. Eles são os blocos de construção do design orientado a objetos e ajudam os programadores a escreverem código reutilizável.

No final deste tutorial, você irá aprender a:

  • Utilizar a herança em Python
  • Modelar hierarquias de classes utilizando herança
  • Utilizar múltipla herança em Python e compreender suas desvantagens
  • Utilizar a composição para criar objetos complexos
  • Reutilizar código existente aplicando a composição
  • Alterar o comportamento da aplicação em tempo de execução através da composição

O que é Herança e Composição?

A herança e a composição são dois conceitos principais na programação orientada a objetos que modelam a relação entre duas classes. Elas impulsionam o design de uma aplicação e determinam como a aplicação deve evoluir à medida que novos recursos são adicionados ou os requisitos são alterados.

Ambas permitem a reutilização de código, mas o fazem de maneiras diferentes.

O que é Herança?

A herança modela o que é chamado de relação “é um” (“is a”). Isso significa que quando você tem uma classe Derivada que herda de uma classe Base, você criou uma relação em que Derivada é uma versão especializada de Base.

A herança é representada utilizando a Linguagem de Modelagem Unificada (UML) da seguinte maneira:

+---------+
| Base |
+---------+
^
|
|
+---------+
| Derivada|
+---------+

Na hierarquia de classes acima, a classe Base é a classe pai e a classe Derivada é a classe filha. A classe Derivada herda todas as características (atributos e métodos) da classe Base e pode adicionar ou sobrescrever essas características conforme necessário.

A herança permite a reutilização de código, pois você pode criar uma classe filha que herda o código da classe mãe e adicionar ou modificar o comportamento conforme necessário. Isso evita a duplicação de código e facilita a manutenção do código, já que as alterações feitas na classe mãe são automaticamente refletidas nas classes filhas.

Você pode usar a herança em Python criando uma classe filha que herda da classe mãe da seguinte forma:

class Base:
# código da classe Base
class Derivada(Base):
# código da classe Derivada

Na classe Derivada, você pode adicionar, modificar ou sobrescrever qualquer atributo ou método da classe Base conforme necessário.

O que é Composição?

A composição modela o que é chamado de relação “tem um” (“has a”). Isso significa que quando uma classe A tem uma instância de uma classe B como um de seus atributos, você criou uma relação em que A tem um objeto B.

A composição é representada na UML da seguinte maneira:

+---------+
+------+ | A |
| | +---------+
v v ^
+---------+ |
| B | |
+---------+ |
|
|
+---------+
| C |
+---------+

Na representação acima, a classe A tem uma instância da classe B como um de seus atributos. A classe C também tem uma instância da classe B como um de seus atributos. Isso permite a criação de objetos complexos, combinando diferentes classes.

A composição permite a reutilização de código, pois você pode criar uma classe que possui objetos de outras classes como atributos. Isso permite que você construa objetos complexos combinando diferentes classes e seus comportamentos. Ao utilizar a composição, você pode acessar os métodos e atributos das classes associadas a partir da classe principal.

Você pode utilizar a composição em Python criando uma classe que possui objetos de outras classes como atributos da seguinte forma:

class B:
# código da classe B
class A:
def __init__(self):
self.b = B()
class C:
def __init__(self):
self.b = B()

Na classe A e C, você pode acessar os atributos e métodos da instância de B utilizando a sintaxe self.b.atributo ou self.b.metodo().

Uma Visão Geral da Herança em Python

Python suporta herança simples, o que significa que uma classe pode herdar de apenas uma classe pai. No entanto, a classe pai pode por sua vez herdar de outra classe, formando assim uma hierarquia de classes.

A Superclasse Objeto

Em Python, todas as classes herdam implicitamente da superclasse object. Isso significa que todas as classes em Python são subclasses de object. Essa superclasse fornece alguns métodos e atributos básicos que são herdados por todas as classes, como o método __init__() para inicialização de objetos e o método __str__() para conversão de objetos em strings.

Quando você define uma classe em Python, ela herda automaticamente da classe object se você não especificar explicitamente a classe pai.

Exceções São uma Exceção

Existem algumas classes em Python que não herdam diretamente da classe object, como as classes de exceções. As classes de exceções em Python são subclasses da classe BaseException, e a classe BaseException é uma exceção a regra de herança da classe object.

Criando Hierarquias de Classes

Você pode criar hierarquias de classes em Python definindo uma classe filha que herda da classe mãe. As classes filhas herdam todos os métodos e atributos da classe mãe e podem adicionar ou sobrescrever esses métodos e atributos conforme necessário.

Exemplo:

class Animal:
def __init__(self, nome):
self.nome = nome
def fazer_barulho(self):
raise NotImplementedError("Método ainda não implementado")
class Cachorro(Animal):
def fazer_barulho(self):
return "Au Au!"
class Gato(Animal):
def fazer_barulho(self):
return "Miau!"
animal1 = Cachorro("Fifi")
animal2 = Gato("Mimi")
print(animal1.fazer_barulho()) # Output: Au Au!
print(animal2.fazer_barulho()) # Output: Miau!

No exemplo acima, a classe Animal é a classe mãe, e as classes Cachorro e Gato são classes filhas. Ambas as classes filhas herdam o método fazer_barulho() da classe mãe, mas cada uma delas o implementa de maneira diferente.

Classes Abstratas em Python

Em Python, você também pode criar classes abstratas que não podem ser instanciadas diretamente. Uma classe abstrata é uma classe que possui pelo menos um método abstrato, que é um método que não possui uma implementação na classe abstrata, mas deve ser implementado nas classes filhas.

Para criar uma classe abstrata em Python, utilize o módulo abc e a classe ABC como base para a sua classe. Em seguida, utilize o decorator @abstractmethod para definir os métodos abstratos na classe abstrata. As classes filhas devem implementar esses métodos abstratos.

Exemplo:

from abc import ABC, abstractmethod
class FiguraGeometrica(ABC):
@abstractmethod
def calcular_area(self):
pass
class Retangulo(FiguraGeometrica):
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def calcular_area(self):
return self.largura * self.altura
class Circulo(FiguraGeometrica):
def __init__(self, raio):
self.raio = raio
def calcular_area(self):
return 3.1415 * self.raio ** 2
retangulo = Retangulo(5, 10)
circulo = Circulo(3)
print(retangulo.calcular_area()) # Output: 50
print(circulo.calcular_area()) # Output: 28.2735

No exemplo acima, a classe FiguraGeometrica é uma classe abstrata que define o método abstrato calcular_area(). As classes filhas Retangulo e Circulo implementam esse método de acordo com suas necessidades. A classe abstrata FiguraGeometrica não pode ser instanciada diretamente, mas as classes filhas podem ser.

Herdando Múltiplas Classes

Python também suporta herança múltipla, o que significa que uma classe pode herdar de várias classes ao mesmo tempo. Com a herança múltipla, uma classe pode herdar os atributos e métodos de várias classes pai.

No entanto, é importante tomar cuidado ao utilizar herança múltipla, pois ela pode levar a problemas de ambiguidade se duas classes pai tiverem métodos ou atributos com o mesmo nome. Nesses casos, é necessário especificar qual classe pai deve ser utilizada como base para o método ou atributo em questão.

Para herdar de múltiplas classes em Python, basta colocar todas as classes pai dentro dos parênteses após o nome da classe filha.

Exemplo:

class A:
def metodo_a(self):
print("Método A")
class B:
def metodo_b(self):
print("Método B")
class C(A, B):
pass
c = C()
c.metodo_a() # Output: Método A
c.metodo_b() # Output: Método B

No exemplo acima, a classe C herda de duas classes, A e B. Portanto, a classe C possui os métodos metodo_a() e metodo_b().

Composição em Python

A composição é outra maneira de reutilizar código em programação orientada a objetos. Ao utilizar a composição, uma classe possui uma ou mais instâncias de outras classes como atributos. Essas instâncias são então utilizadas para adicionar funcionalidades à classe principal.

A composição é representada da seguinte forma:

+---------+
| A |
+---------+
^
|
|
+---------+
| B |
+---------+

Na representação acima, a classe A possui uma instância da classe B como um de seus atributos. A classe A pode utilizar os métodos e atributos da instância de B para adicionar comportamento à classe principal.

Ao utilizar a composição, você pode construir objetos complexos combinando diferentes classes e aproveitando as funcionalidades oferecidas por essas classes. Isso permite uma maior flexibilidade e reutilização de código, já que você pode modificar facilmente a composição do objeto, adicionando ou removendo instâncias de outras classes.

Exemplo:

class Engine:
def start(self):
print("Engine started")
def stop(self):
print("Engine stopped")
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
print("Car started")
self.engine.start()
def stop(self):
print("Car stopped")
self.engine.stop()
car = Car()
car.start() # Output: Car started Engine started
car.stop() # Output: Car stopped Engine stopped

No exemplo acima, a classe Car possui uma instância da classe Engine como seu atributo engine. A classe Car utiliza os métodos da instância de Engine (start, stop) para adicionar comportamento à classe principal.

Ao utilizar a composição, você pode construir objetos complexos combinando diferentes classes e suas funcionalidades. Isso permite uma maior flexibilidade e reutilização de código, já que você pode modificar facilmente a composição do objeto, adicionando ou removendo instâncias de outras classes.

Composição é uma ótima alternativa quando a relação entre as classes é “tem um” (“has a”), onde uma classe possui uma instância de outra classe como um atributo.

Escolhendo Entre Herança e Composição em Python

Na programação orientada a objetos em Python, você pode escolher entre herança e composição para modelar a relação entre classes. A escolha entre herança e composição depende da relação entre as classes e dos requisitos do projeto.

Herança para Modelar a Relação “É um” (“Is a”)

A herança é uma boa opção quando a relação entre as classes é “é um” (“is a”), ou seja, quando uma classe é uma versão especializada de outra classe. Nesses casos, a classe filha herda todas as características (atributos e métodos) da classe mãe e pode adicionar ou sobrescrever essas características, conforme necessário.

A herança é útil quando você quer reutilizar código de uma classe mãe em várias classes filhas. Isso evita a duplicação de código e facilita a manutenção do código, já que as alterações feitas na classe mãe são automaticamente refletidas nas classes filhas.

Exemplo:

class Animal:
def __init__(self, nome):
self.nome = nome
class Cachorro(Animal):
def latir(self):
print("Au Au!")
class Gato(Animal):
def miar(self):
print("Miau!")
cachorro = Cachorro("Fifi")
gato = Gato("Mimi")
print(cachorro.nome) # Output: Fifi
print(gato.nome) # Output: Mimi

No exemplo acima, a classe Animal é a classe mãe e as classes Cachorro e Gato são classes filhas. Ambas as classes filhas herdam o atributo nome da classe mãe. A classe Cachorro adiciona o método latir() e a classe Gato adiciona o método miar().

Misturando Funcionalidades com Classes Mixin

Uma classe mixin é uma classe que possui métodos e atributos que podem ser adicionados a outras classes através da herança múltipla. As classes mixin são utilizadas para adicionar funcionalidades a outras classes sem quebrar a hierarquia de classes ou duplicar código.

Exemplo:

class A:
def metodo_a(self):
print("Método A")
class B:
def metodo_b(self):
print("Método B")
class C(A, B):
pass
c = C()
c.metodo_a() # Output: Método A
c.metodo_b() # Output: Método B

No exemplo acima, as classes A e B são classes mixin. A classe C herda as funcionalidades dessas classes mixin através da herança múltipla. Assim, a classe C possui tanto o método metodo_a() quanto o método metodo_b().

Classes mixin são utilizadas quando você quer adicionar funcionalidades específicas a uma classe sem criar uma complexa hierarquia de classes.

Composição para Modelar a Relação “Tem um” (“Has a”)

A composição é apropriada quando a relação entre as classes é “tem um” (“has a”), ou seja, quando uma classe possui outra classe como um de seus atributos. Nesse caso, a classe principal utiliza os métodos e atributos da instância da classe associada para adicionar funcionalidade à classe principal.

A composição é útil quando você deseja criar objetos complexos combinando diferentes classes e suas funcionalidades. Isso permite uma maior flexibilidade e reutilização de código, já que você pode modificar facilmente a composição do objeto, adicionando ou removendo instâncias de outras classes.

Exemplo:

class Engine:
def start(self):
print("Engine started")
def stop(self):
print("Engine stopped")
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
print("Car started")
self.engine.start()
def stop(self):
print("Car stopped")
self.engine.stop()
car = Car()
car.start() # Output: Car started Engine started
car.stop() # Output: Car stopped Engine stopped

No exemplo acima, a classe Car possui uma instância da classe Engine como seu atributo engine. A classe Car utiliza os métodos da instância de Engine (start, stop) para adicionar comportamento à classe principal.

Composição para Alterar o Comportamento em Tempo de Execução

A composição também pode ser utilizada para alterar o comportamento das classes em tempo de execução. Nesse caso, a classe principal possui um atributo que pode ser trocado por diferentes instâncias de classes associadas, modificando assim o comportamento da classe em diferentes cenários.

Exemplo:

class Behavior1:
def execute(self):
print("Executing Behavior 1")
class Behavior2:
def execute(self):
print("Executing Behavior 2")
class MyClass:
def __init__(self):
self.behavior = Behavior1()
def change_behavior(self, behavior):
self.behavior = behavior
def execute_behavior(self):
self.behavior.execute()
my_object = MyClass()
my_object.execute_behavior() # Output: Executing Behavior 1
my_object.change_behavior(Behavior2())
my_object.execute_behavior() # Output: Executing Behavior 2

No exemplo acima, a classe MyClass possui um atributo behavior que pode ser trocado por diferentes instâncias de classes associadas. O método execute_behavior() utiliza o método execute() da instância de behavior para executar o comportamento da classe.

Escolhendo entre Herança e Composição em Python

Ao escolher entre herança e composição em Python, leve em consideração a relação entre as classes e os requisitos do projeto. Utilize herança quando a relação entre as classes for “é um” (“is a”), ou seja, quando uma classe for uma versão especializada de outra classe. Utilize composição quando a relação entre as classes for “tem um” (“has a”), ou seja, quando uma classe possuir outra classe como um de seus atributos.

Em algumas situações, é possível utilizar herança e composição em conjunto para obter os melhores resultados. Essa escolha deve ser feita com base nas necessidades do projeto e no princípio de design conhecido como Princípio da Composição sobre Herança. Esse princípio defende que a composição geralmente é preferível à herança, pois ela permite uma maior flexibilidade e reutilização de código.

Conclusão

Neste tutorial, você aprendeu sobre herança e composição em Python. A herança e a composição são dois conceitos importantes na programação orientada a objetos que permitem a reutilização de código e o design flexível de classes. A herança modela a relação “é um” (“is a”) entre classes, permitindo a criação de hierarquias de classes e a reutilização de código. A composição modela a relação “tem um” (“has a”) entre classes, permitindo a criação de objetos complexos combinando diferentes classes. Você deve escolher entre herança e composição com base na relação entre as classes e nos requisitos do projeto. Aproveite esses conceitos para criar um código mais organizado, modular e reutilizável em Python.

Leitura Recomendada