Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial Internet of Things >> Computação em Nuvem

SOLID:Princípios de Design Orientado a Objetos


SOLID é um acrônimo mnemônico para design de classe em programação orientada a objetos. Os princípios instituem práticas que ajudam a desenvolver bons hábitos de programação e código sustentável.

Ao considerar a manutenção e a extensibilidade do código a longo prazo, os princípios SOLID enriquecem o ambiente de desenvolvimento de código Agile. A contabilização e a otimização das dependências de código ajudam a criar um ciclo de vida de desenvolvimento de software mais direto e organizado.

O que são princípios SOLID?


SOLID representa um conjunto de princípios para projetar classes. Robert C. Martin (Tio Bob) introduziu a maioria dos princípios de design e cunhou a sigla.
SÓLIDO significa:

Os princípios SOLID representam uma coleção de práticas recomendadas para design de software. Cada ideia representa uma estrutura de design, levando a melhores hábitos de programação, design de código aprimorado e menos erros.

SÓLIDO:5 princípios explicados


A melhor maneira de entender como os princípios SOLID funcionam é por meio de exemplos. Todos os princípios são complementares e se aplicam a casos de uso individuais. A ordem em que os princípios são aplicados não é importante, e nem todos os princípios são aplicáveis ​​em todas as situações.

Cada seção abaixo fornece uma visão geral de cada princípio SOLID na linguagem de programação Python. As ideias gerais do SOLID se aplicam a qualquer linguagem orientada a objetos, como PHP, Java ou C#. A generalização das regras as torna aplicáveis ​​a abordagens de programação modernas, como microsserviços.

Princípio da Responsabilidade Única (SRP)


O princípio de responsabilidade única (SRP) afirma:"Nunca deve haver mais de um motivo para uma classe mudar."

Ao alterar uma classe, devemos alterar apenas uma única funcionalidade, o que implica que cada objeto deve ter apenas um trabalho.

Como exemplo, observe a seguinte classe:
# A class with multiple responsibilities
class Animal:
    # Property constructor
    def __init__(self, name):
        self.name = name

    # Property representation
    def __repr__(self):
        return f'Animal(name="{self.name}")'

    # Database management
    def save(animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Saving property to a database
    Animal.save(a)

Ao fazer qualquer alteração no save() método, a mudança acontece no Animal classe. Ao fazer alterações de propriedade, as modificações também ocorrem no Animal classe.

A classe tem dois motivos para mudar e viola o princípio da responsabilidade única. Mesmo que o código funcione conforme o esperado, não respeitar o princípio de design torna o código mais difícil de gerenciar a longo prazo.

Para implementar o princípio de responsabilidade única, observe que a classe de exemplo tem dois trabalhos distintos:

Portanto, a melhor maneira de resolver o problema é separar o método de gerenciamento de banco de dados em uma nova classe. Por exemplo:
# A class responsible for property management
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

# A class responsible for database management
class AnimalDB:
    def save(self, animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Database instantiation
    db = AnimalDB()
    # Saving property to a database
    db.save(a)

Alterando o AnimalDB classe não afeta o Animal classe com o princípio da responsabilidade única aplicado. O código é intuitivo e fácil de modificar.

Princípio Aberto-Fechado (OCP)


O princípio aberto-fechado (OCP) afirma:“Entidades de software devem ser abertas para extensão, mas fechadas para modificação.”

Adicionar funcionalidades e casos de uso ao sistema não deve exigir a modificação de entidades existentes. A redação parece contraditória – adicionar novas funcionalidades requer alterar o código existente.

A ideia é simples de entender através do seguinte exemplo:
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

class Storage:
    def save_to_db(self, animal):
        print(f'Saved {animal} to the database')

O Storage classe salva as informações de um Animal instância para um banco de dados. A adição de novas funcionalidades, como salvar em um arquivo CSV, requer a adição de código ao Storage classe:
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

class Storage:
    def save_to_db(self, animal):
        print(f'Saved {animal} to the database')
    def save_to_csv(self,animal):
        printf(f’Saved {animal} to the CSV file’)

O save_to_csv modifica um Storage existente class para adicionar a funcionalidade. Essa abordagem viola o princípio aberto-fechado alterando um elemento existente quando uma nova funcionalidade aparece.

O código requer a remoção do Storage de uso geral class e criar classes individuais para armazenamento em formatos de arquivo específicos.

O código a seguir demonstra a aplicação do princípio aberto-fechado:
class DB():
    def save(self, animal):
        print(f'Saved {animal} to the database')

class CSV():
    def save(self, animal):
        print(f'Saved {animal} to a CSV file')

O código está em conformidade com o princípio aberto-fechado. O código completo agora fica assim:
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'"{self.name}"'

class DB():
    def save(self, animal):
        print(f'Saved {animal} to the database')

class CSV():
    def save(self, animal):
        print(f'Saved {animal} to a CSV file')

if __name__ == '__main__':
    a = Animal('Cat')
    db = DB()
    csv = CSV()
    db.save(a)
    csv.save(a) 

Estender com funcionalidades adicionais (como salvar em um arquivo XML) não modifica as classes existentes.

Princípio de substituição de Liskov (LSP)


O princípio de substituição de Liskov (LSP) afirma:“Funções que usam ponteiros ou referências a classes base devem ser capazes de usar objetos de classes derivadas sem saber.”

O princípio afirma que uma classe pai pode substituir uma classe filha sem nenhuma alteração perceptível na funcionalidade.

Confira o exemplo de escrita de arquivo abaixo:
# Parent class
class FileHandling():
    def write_db(self):
        return f'Handling DB'
    def write_csv(self):
        return f'Handling CSV'

# Child classes
class WriteDB(FileHandling):
    def write_db(self):
        return f'Writing to a DB'
    def write_csv(self):
        return f"Error: Can't write to CSV, wrong file type."

class WriteCSV(FileHandling):
    def write_csv(self):
        return f'Writing to a CSV file'
    def write_db(self):
        return f"Error: Can't write to DB, wrong file type."

if __name__ == "__main__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write_db())
    print(db.write_csv())

    # Children classes instantiations and function calls
    db = WriteDB()
    csv = WriteCSV()
    print(db.write_db())
    print(db.write_csv())
    print(csv.write_db())
    print(csv.write_csv())

A classe pai (FileHandling ) consiste em dois métodos para gravar em um banco de dados e em um arquivo CSV. A classe manipula ambas as funções e retorna uma mensagem.

As duas classes filhas (WriteDB e WriteCSV ) herdam propriedades da classe pai (FileHandling ). No entanto, ambos os filhos lançam um erro ao tentar usar a função de gravação inadequada, o que viola o princípio de substituição de Liskov, pois as funções de substituição não correspondem às funções pai.

O código a seguir resolve os problemas:
# Parent class
class FileHandling():
    def write(self):
        return f'Handling file'

# Child classes
class WriteDB(FileHandling):
    def write(self):
        return f'Writing to a DB'

class WriteCSV(FileHandling):
    def write(self):
        return f'Writing to a CSV file'

if __name__ == "__main__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write())
    print(csv.write())

    # Children classes instantiations and function calls
    db = WriteDB()
    csv = WriteCSV()
    print(db.write())
    print(csv.write())

As classes filhas correspondem corretamente à função pai.

Princípio de Segregação de Interface (ISP)


O princípio de segregação de interface (ISP) afirma:“Muitas interfaces específicas do cliente são melhores do que uma interface de uso geral”.

Em outras palavras, interfaces de interação mais extensas são divididas em menores. O princípio garante que as classes usem apenas os métodos de que precisam, reduzindo a redundância geral.

O exemplo a seguir demonstra uma interface de uso geral:
class Animal():
    def walk(self):
        pass
    def swim(self):
        pass
    
class Cat(Animal):
    def walk(self):
        print("Struts")
    def fly(self):
        raise Exception("Cats don't swim")
    
class Duck(Animal):
    def walk(self):
        print("Waddles")
    def swim(self):
        print("Floats")

As classes filhas herdam da mãe Animal classe, que contém walk e fly métodos. Embora ambas as funções sejam aceitáveis ​​para certos animais, alguns animais têm funcionalidades redundantes.

Para lidar com a situação, divida a interface em seções menores. Por exemplo:
class Walk():
    def walk(self):
        pass

class Swim(Walk):
    def swim(self):
        pass

class Cat(Walk):
    def walk(self):
        print("Struts")
    
class Duck(Swim):
    def walk(self):
        print("Waddles")
    def swim(self):
        print("Floats")

O Fly classe herda do Walk , fornecendo funcionalidade adicional para classes filhas apropriadas. O exemplo satisfaz o princípio de segregação de interface.
Adicionar outro animal, como um peixe, requer atomizar ainda mais a interface, pois os peixes não podem andar.

Princípio de inversão de dependência (DIP)


O princípio de inversão de dependência afirma:“Depende de abstrações, não de concreções.”

O princípio visa reduzir as conexões entre as classes adicionando uma camada de abstração. Mover dependências para abstrações torna o código robusto.

O exemplo a seguir demonstra a dependência de classe sem uma camada de abstração:
class LatinConverter:
    def latin(self, name):
        print(f'{name} = "Felis catus"')
        return "Felis catus"

class Converter:
    def start(self):
        converter = LatinConverter()
        converter.latin('Cat')

if __name__ ==  '__main__':
    converter = Converter()
    converter.start()

O exemplo tem duas classes:

O princípio de inversão de dependência requer a adição de uma camada de interface de abstração entre as duas classes.

Um exemplo de solução se parece com o seguinte:
from abc import ABC

class NameConverter(ABC):
    def convert(self,name):
        pass

class LatinConverter(NameConverter):
    def convert(self, name):
        print('Converting using Latin API')
        print(f'{name} = "Felis catus"')
        return "Felis catus"

class Converter:
    def __init__(self, converter: NameConverter):
        self.converter = converter
    def start(self):
        self.converter.convert('Cat')

if __name__ ==  '__main__':
    latin = LatinConverter()
    converter = Converter(latin)
    converter.start()

O Converter classe agora depende do NameConverter interface em vez de no LatinConverter diretamente. Atualizações futuras permitem definir conversões de nome usando um idioma e uma API diferentes por meio do NameConverter interface.

Por que há a necessidade de princípios SOLID?


Os princípios SOLID ajudam a combater problemas de padrão de projeto. O objetivo geral dos princípios SOLID é reduzir as dependências de código e adicionar um novo recurso ou alterar uma parte do código não interrompe toda a compilação.

Como resultado da aplicação dos princípios SOLID ao design orientado a objetos, o código fica mais fácil de entender, gerenciar, manter e alterar. Como as regras são mais adequadas para grandes projetos, a aplicação dos princípios SOLID aumenta a velocidade e a eficiência geral do ciclo de vida do desenvolvimento.

Os princípios SOLID ainda são relevantes?


Embora os princípios SOLID tenham mais de 20 anos, eles ainda fornecem uma boa base para o projeto de arquitetura de software. O SOLID fornece princípios sólidos de design aplicáveis ​​a programas e ambientes modernos, não apenas à programação orientada a objetos.

Os princípios SOLID se aplicam em situações em que o código é escrito e modificado por pessoas, organizado em módulos e contém elementos internos ou externos.

Conclusão


Os princípios SOLID ajudam a fornecer uma boa estrutura e guia para o projeto de arquitetura de software. Os exemplos deste guia mostram que mesmo uma linguagem tipada dinamicamente, como Python, se beneficia da aplicação dos princípios ao design de código.

Em seguida, leia sobre os 9 princípios do DevOps que ajudarão sua equipe a tirar o máximo proveito do DevOps.

Computação em Nuvem

  1. Palavra-chave estática C#
  2. Classe aninhada C#
  3. Modelos de classe C++
  4. Classe anônima Java
  5. Classe Java ObjectOutputStream
  6. Genéricos Java
  7. Classe de arquivo Java
  8. 5 princípios de design para a aplicação de interconexões robustas para aplicativos com muitos dados
  9. C# - Herança
  10. C# - Polimorfismo