Uso Prático de Herança

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá como usar a herança para escrever código extensível e criar uma aplicação prática que gera dados em múltiplos formatos. Você também entenderá como usar classes base abstratas e suas implementações concretas.

Além disso, você implementará um padrão de fábrica (factory pattern) para simplificar a seleção de classes. O arquivo que você modificará é tableformat.py.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 95%. Recebeu uma taxa de avaliações positivas de 95% dos estudantes.

Compreendendo o Problema

Neste laboratório, vamos aprender sobre herança em Python e como ela pode nos ajudar a criar código que seja tanto extensível quanto adaptável. Herança é um conceito poderoso em programação orientada a objetos, onde uma classe pode herdar atributos e métodos de outra classe. Isso nos permite reutilizar código e construir funcionalidades mais complexas com base no código existente.

Vamos começar analisando a função print_table() existente. Esta função é o que vamos aprimorar para torná-la mais flexível em termos de formatos de saída.

Primeiro, você precisa abrir o arquivo tableformat.py no editor WebIDE. O caminho para este arquivo é o seguinte:

/home/labex/project/tableformat.py

Depois de abrir o arquivo, você verá a implementação atual da função print_table(). Esta função foi projetada para formatar e imprimir dados tabulares. Ela recebe duas entradas principais: uma lista de registros (que são objetos) e uma lista de nomes de campos. Com base nessas entradas, ela imprime uma tabela formatada de forma agradável.

Agora, vamos testar esta função para ver como ela funciona. Abra um terminal no WebIDE e execute os seguintes comandos Python. Esses comandos importam os módulos necessários, leem dados de um arquivo CSV e, em seguida, usam a função print_table() para exibir os dados.

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
tableformat.print_table(portfolio, ['name', 'shares', 'price'])

Após executar esses comandos, você deve ver a seguinte saída:

      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

A saída parece boa, mas há uma limitação nesta função. Atualmente, ela suporta apenas um formato de saída, que é texto simples. Em cenários do mundo real, você pode querer gerar seus dados em diferentes formatos, como CSV, HTML ou outros.

Em vez de fazer alterações na função print_table() toda vez que quisermos suportar um novo formato de saída, podemos usar herança para criar uma solução mais flexível. Veja como faremos isso:

  1. Definiremos uma classe base TableFormatter. Esta classe terá métodos que são usados para formatar dados. A classe base fornece uma estrutura e funcionalidade comuns sobre as quais todas as subclasses podem ser construídas.
  2. Criaremos várias subclasses. Cada subclasse será projetada para um formato de saída diferente. Por exemplo, uma subclasse pode ser para saída CSV, outra para saída HTML e assim por diante. Essas subclasses herdarão os métodos da classe base e também poderão adicionar sua própria funcionalidade específica.
  3. Modificaremos a função print_table() para que ela possa trabalhar com qualquer formatador. Isso significa que podemos passar diferentes subclasses da classe TableFormatter para a função print_table(), e ela poderá usar os métodos de formatação apropriados.

Essa abordagem tem uma grande vantagem. Ela nos permite adicionar novos formatos de saída sem alterar a funcionalidade principal da função print_table(). Portanto, à medida que seus requisitos mudam e você precisa suportar mais formatos de saída, você pode fazê-lo facilmente criando novas subclasses.

Criando uma Classe Base e Modificando a Função de Impressão

Em programação, herança é um conceito poderoso que nos permite criar uma hierarquia de classes. Para começar a usar herança para gerar dados em diferentes formatos, primeiro precisamos criar uma classe base. Uma classe base serve como um modelo para outras classes, definindo um conjunto comum de métodos que suas subclasses podem herdar e substituir.

Agora, vamos criar uma classe base que definirá a interface para todos os formatadores de tabela. Abra o arquivo tableformat.py no WebIDE e adicione o seguinte código no topo do arquivo:

class TableFormatter:
    """
    Base class for all table formatters.
    This class defines the interface that all formatters must implement.
    """
    def headings(self, headers):
        """
        Generate the table headings.
        """
        raise NotImplementedError()

    def row(self, rowdata):
        """
        Generate a single row of table data.
        """
        raise NotImplementedError()

A classe TableFormatter é uma classe base abstrata. Uma classe base abstrata é uma classe que define métodos, mas não fornece implementações para eles. Em vez disso, ela espera que suas subclasses forneçam essas implementações. As exceções NotImplementedError são usadas para indicar que esses métodos devem ser substituídos pelas subclasses. Se uma subclasse não substituir esses métodos e tentarmos usá-los, um erro será gerado.

Em seguida, precisamos modificar a função print_table() para usar a classe TableFormatter. A função print_table() é usada para imprimir uma tabela de dados de uma lista de objetos. Ao modificá-la para usar a classe TableFormatter, podemos tornar a função mais flexível e capaz de trabalhar com diferentes formatos de tabela.

Substitua a função print_table() existente pelo seguinte código:

def print_table(records, fields, formatter):
    """
    Print a table of data from a list of objects using the specified formatter.

    Args:
        records: A list of objects
        fields: A list of field names
        formatter: A TableFormatter object
    """
    formatter.headings(fields)
    for r in records:
        rowdata = [getattr(r, fieldname) for fieldname in fields]
        formatter.row(rowdata)

A principal mudança aqui é que print_table() agora recebe um parâmetro formatter, que deve ser uma instância de TableFormatter ou uma subclasse. Isso significa que podemos passar diferentes formatadores de tabela para a função print_table(), e ela usará o formatador apropriado para imprimir a tabela. A função delega a responsabilidade de formatação ao objeto formatador, chamando seus métodos headings() e row().

Vamos testar nossas alterações tentando usar a classe base TableFormatter:

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Quando você executar este código, você deve ver um erro:

Traceback (most recent call last):
...
NotImplementedError

Este erro ocorre porque estamos tentando usar a classe base abstrata diretamente, mas ela não fornece implementações para seus métodos. Como os métodos headings() e row() na classe TableFormatter lançam NotImplementedError, o Python não sabe o que fazer quando esses métodos são chamados. Na próxima etapa, criaremos uma subclasse concreta que fornece essas implementações.

Implementando um Formatador Concreto

Agora que definimos nossa classe base abstrata e atualizamos a função print_table(), é hora de criar uma classe formatadora concreta. Uma classe formatadora concreta é aquela que fornece implementações reais para os métodos definidos na classe base abstrata. Em nosso caso, criaremos uma classe que pode formatar dados em uma tabela de texto simples.

Vamos adicionar a seguinte classe ao seu arquivo tableformat.py. Esta classe herdará da classe base abstrata TableFormatter e implementará os métodos headings() e row().

class TextTableFormatter(TableFormatter):
    """
    Formatter that generates a plain - text table.
    """
    def headings(self, headers):
        """
        Generate plain - text table headings.
        """
        print(' '.join('%10s' % h for h in headers))
        print(('-'*10 + ' ')*len(headers))

    def row(self, rowdata):
        """
        Generate a plain - text table row.
        """
        print(' '.join('%10s' % d for d in rowdata))

A classe TextTableFormatter herda de TableFormatter. Isso significa que ela obtém todas as propriedades e métodos da classe TableFormatter, mas também fornece suas próprias implementações para os métodos headings() e row(). Esses métodos são responsáveis por formatar os cabeçalhos e linhas da tabela, respectivamente. O método headings() imprime os cabeçalhos de uma maneira bem formatada, seguido por uma linha de traços para separar os cabeçalhos dos dados. O método row() formata cada linha de dados de maneira semelhante.

Agora, vamos testar nosso novo formatador. Usaremos os módulos stock, reader e tableformat para ler dados de um arquivo CSV e imprimi-los usando nosso novo formatador.

import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TextTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Quando você executar este código, você deve ver a mesma saída de antes. Isso ocorre porque nosso novo formatador foi projetado para produzir a mesma tabela de texto simples que a função print_table() original.

      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

Esta saída confirma que nosso TextTableFormatter está funcionando corretamente. A vantagem de usar essa abordagem é que tornamos nosso código mais modular e extensível. Ao separar a lógica de formatação em uma hierarquia de classes separada, podemos facilmente adicionar novos formatos de saída. Tudo o que precisamos fazer é criar novas subclasses de TableFormatter sem modificar a função print_table(). Dessa forma, podemos suportar diferentes formatos de saída, como CSV ou HTML, no futuro.

Criando Formatadores Adicionais

Em programação, herança é um conceito poderoso que nos permite criar novas classes com base nas existentes. Isso ajuda a reutilizar código e tornar nossos programas mais extensíveis. Nesta parte do experimento, usaremos herança para criar dois novos formatadores para diferentes formatos de saída: CSV e HTML. Esses formatadores herdarão de uma classe base, o que significa que compartilharão algum comportamento comum, enquanto terão suas próprias maneiras únicas de formatar dados.

Agora, vamos adicionar as seguintes classes ao seu arquivo tableformat.py. Essas classes definirão como formatar dados nos formatos CSV e HTML, respectivamente.

class CSVTableFormatter(TableFormatter):
    """
    Formatter that generates CSV formatted data.
    """
    def headings(self, headers):
        """
        Generate CSV headers.
        """
        print(','.join(headers))

    def row(self, rowdata):
        """
        Generate a CSV data row.
        """
        print(','.join(str(d) for d in rowdata))

class HTMLTableFormatter(TableFormatter):
    """
    Formatter that generates HTML table code.
    """
    def headings(self, headers):
        """
        Generate HTML table headers.
        """
        print('<tr>', end=' ')
        for header in headers:
            print(f'<th>{header}</th>', end=' ')
        print('</tr>')

    def row(self, rowdata):
        """
        Generate an HTML table row.
        """
        print('<tr>', end=' ')
        for data in rowdata:
            print(f'<td>{data}</td>', end=' ')
        print('</tr>')

A classe CSVTableFormatter foi projetada para formatar dados no formato CSV (Valores Separados por Vírgula). O método headings recebe uma lista de cabeçalhos e os imprime separados por vírgulas. O método row recebe uma lista de dados para uma única linha e também os imprime separados por vírgulas.

A classe HTMLTableFormatter, por outro lado, é usada para gerar código de tabela HTML. O método headings cria os cabeçalhos da tabela usando as tags HTML <th>, e o método row cria uma linha da tabela usando as tags HTML <td>.

Vamos testar esses novos formatadores para ver como eles funcionam.

  1. Primeiro, vamos testar o formatador CSV:
import stock
import reader
import tableformat

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.CSVTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Neste código, primeiro importamos os módulos necessários. Em seguida, lemos dados de um arquivo CSV chamado portfolio.csv e criamos instâncias da classe Stock. Em seguida, criamos uma instância da classe CSVTableFormatter. Finalmente, usamos a função print_table para imprimir os dados do portfólio em formato CSV.

Você deve ver a seguinte saída formatada em CSV:

name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44
  1. Agora, vamos testar o formatador HTML:
formatter = tableformat.HTMLTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)

Aqui, criamos uma instância da classe HTMLTableFormatter e usamos a função print_table novamente para imprimir os dados do portfólio em formato HTML.

Você deve ver a seguinte saída formatada em HTML:

<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>

Como você pode ver, cada formatador produz saída em um formato diferente, mas todos eles compartilham a mesma interface definida pela classe base TableFormatter. Este é um ótimo exemplo do poder da herança e do polimorfismo. Podemos escrever código que funciona com a classe base, e ele funcionará automaticamente com qualquer subclasse.

A função print_table() não precisa saber nada sobre o formatador específico que está sendo usado. Ela apenas chama os métodos definidos na classe base, e a implementação apropriada é selecionada com base no tipo de formatador fornecido. Isso torna nosso código mais flexível e fácil de manter.

Criando uma Função Fábrica

Ao usar herança, um desafio comum é que os usuários precisam se lembrar dos nomes das classes formatadoras específicas. Isso pode ser um incômodo, especialmente à medida que o número de classes aumenta. Para simplificar esse processo, podemos criar uma função fábrica. Uma função fábrica é um tipo especial de função que cria e retorna objetos. Em nosso caso, ela retornará o formatador apropriado com base em um nome de formato simples.

Vamos adicionar a seguinte função ao seu arquivo tableformat.py. Esta função receberá um nome de formato como argumento e retornará o objeto formatador correspondente.

def create_formatter(format_name):
    """
    Create a formatter of the specified type.

    Args:
        format_name: Name of the formatter ('text', 'csv', 'html')

    Returns:
        A TableFormatter object

    Raises:
        ValueError: If format_name is not recognized
    """
    if format_name == 'text':
        return TextTableFormatter()
    elif format_name == 'csv':
        return CSVTableFormatter()
    elif format_name == 'html':
        return HTMLTableFormatter()
    else:
        raise ValueError(f'Unknown format {format_name}')

A função create_formatter() é uma função fábrica. Ela verifica o argumento format_name que você fornece. Se for 'text', ela cria e retorna um objeto TextTableFormatter. Se for 'csv', ela retorna um objeto CSVTableFormatter, e se for 'html', ela retorna um objeto HTMLTableFormatter. Se o nome do formato não for reconhecido, ela levanta um ValueError. Dessa forma, os usuários podem selecionar facilmente um formatador apenas fornecendo um nome simples, sem precisar conhecer os nomes específicos das classes.

Agora, vamos testar a função fábrica. Usaremos algumas funções e classes existentes para ler dados de um arquivo CSV e imprimi-los em diferentes formatos.

import stock
import reader
from tableformat import create_formatter, print_table

portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)

## Test with text formatter
formatter = create_formatter('text')
print("\nText Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

## Test with CSV formatter
formatter = create_formatter('csv')
print("\nCSV Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

## Test with HTML formatter
formatter = create_formatter('html')
print("\nHTML Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)

Neste código, primeiro importamos os módulos e funções necessários. Em seguida, lemos dados do arquivo portfolio.csv e criamos um objeto portfolio. Depois disso, testamos a função create_formatter() com diferentes nomes de formato: 'text', 'csv' e 'html'. Para cada formato, criamos um objeto formatador, imprimimos o nome do formato e, em seguida, usamos a função print_table() para imprimir os dados do portfolio no formato especificado.

Quando você executar este código, você deverá ver a saída em todos os três formatos, separados pelo nome do formato:

Text Format:
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

CSV Format:
name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44

HTML Format:
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>

A função fábrica torna o código mais amigável, pois oculta os detalhes da instanciação da classe. Os usuários não precisam saber como criar objetos formatadores; eles só precisam especificar o formato desejado.

Este padrão de usar uma função fábrica para criar objetos é um padrão de projeto comum em programação orientada a objetos, conhecido como Padrão de Fábrica (Factory Pattern). Ele fornece uma camada de abstração entre o código do cliente (o código que usa o formatador) e as classes de implementação reais (as classes formatadoras). Isso torna o código mais modular e fácil de usar.

Revisão dos Conceitos-Chave:

  1. Classe Base Abstrata (Abstract Base Class): A classe TableFormatter serve como uma interface. Uma interface define um conjunto de métodos que todas as classes que a implementam devem ter. Em nosso caso, todas as classes formatadoras devem implementar os métodos definidos na classe TableFormatter.

  2. Herança (Inheritance): As classes formatadoras concretas, como TextTableFormatter, CSVTableFormatter e HTMLTableFormatter, herdam da classe base TableFormatter. Isso significa que elas obtêm a estrutura básica e os métodos da classe base e podem fornecer suas próprias implementações específicas.

  3. Polimorfismo (Polymorphism): A função print_table() pode trabalhar com qualquer formatador que implemente a interface necessária. Isso significa que você pode passar diferentes objetos formatadores para a função print_table(), e ela funcionará corretamente com cada um.

  4. Padrão de Fábrica (Factory Pattern): A função create_formatter() simplifica a criação de objetos formatadores. Ela cuida dos detalhes de criar o objeto certo com base no nome do formato, para que os usuários não precisem se preocupar com isso.

Ao usar esses princípios orientados a objetos, criamos um sistema flexível e extensível para formatar dados tabulares em vários formatos de saída.

Resumo

Neste laboratório, você aprendeu vários conceitos-chave em programação orientada a objetos. Você dominou como usar herança para criar código extensível, definir uma classe base abstrata como uma interface e implementar subclasses concretas que herdam de uma classe base. Além disso, você aprendeu a usar polimorfismo para escrever código que funciona com diferentes tipos e o Padrão de Fábrica (Factory Pattern) para simplificar a criação de objetos.

Esses conceitos são ferramentas poderosas para criar código sustentável e extensível. O sistema de formatação de tabela que você construiu mostra como a herança pode criar um sistema flexível. Você pode aplicar esses princípios a outras tarefas de programação, tornando seu código modular, reutilizável e adaptável às mudanças nos requisitos.