Executando verificação de segurança...
2

Modo fácil dos Princípios SOLID

SOLID é um acrônimo para cinco princípios do POO (Programação Orientada a Objetos), que tem como objetivo de facilitar o desenvolvimento de software, tornando mais fácil a manutenção e o entendimento do código.

A Programação Orientada a Objetos pode ser definida por quatro pilares Herança, encapsulamento, abstração e polimorfismo, um dos principais objetivos é facilitar o entendimento e a modelagem de software complexo através da organização do código em unidades lógicas que representam entidades ou conceitos.

A Abstração é o processo de ocultar detalhes complexos e mostrar apenas as características essenciais de um objeto ou sistema. Por exemplo, uma classe “Carro” pode incluir atributos como "marca”, "modelo” e “cor”, e métodos como "ligar” e "acelerar”, sem necessariamente detalhar os mecanismo interno para realizar essas funções.

O Encapsulamento é a habilidade de esconder as características intrínsecas (características ou qualidades que fazem parte da natureza ou essência de algo ou alguém) de um dado objeto de outros objetos. É frequentemente implementado usando modificadores de acesso, como "privado”, "público” e "protegido”, que definem a visibilidade desses membros da classe. Ele fortalece a segurança do código e evita que o estado do objeto seja modificado inadvertidamente.

Um exemplo simples de público, protegido e privado é quando se tem uma classe banco, o teu nome é acessível por todos, o ID da tua conta é protegido, mas outras pessoas conseguem ver, e a tua senha é privada, transformando isso em código ficaria assim:


class Bank: 
	def __init__(self):
	

		# Público
		self.name = 'Álex P Carvalho'
	
		# Protegido
		self._account = '123456-7'
	
		# Privado
		self.__password = '987654321'
		
bank = Bank()
print(bank.name)
print(bank._account)
print(bank.__password)  



Rodando esse código, ele irá pegar o nome e a conta e surgirá um erro quando ele tentar pegar a senha.

encapsulamento.py" Álex P Carvalho 123456-7 Traceback (most recent call last): File "/Users/user/Documents/SOLID/encapsulamento.py", line 20, in <module> print(bank. password) AttributeError: 'Bank' object has no attribute '_ password'. Did you mean: 'get_password'?

Como o atributo está no modo privado, o python altera o nome interno aplicando o name mangling, isso significa que o atributo não pode ser acessado diretamente fora da classe. Uma forma de acessar a senha é criando um método público para retornar o valor de __password.


class Bank: 
	def __init__(self):
	
		# Público
		self.name = 'Álex P Carvalho'
	
		# Protegido
		self._account = '123456-7'
	
		# Privado
		self.__password = '987654321'
		
	def get_password(self):
		return self.__password
    
bank = Bank()
print(bank.name)
print(bank._account)
print(bank.get_password())

Rodando dessa maneira ela retorna tudo como o esperado.

encapsulamento.py" Álex P Carvalho 123456-7 987654321

A Herança permite a criação de classes derivadas (subclasses) que herdam atributos e métodos de uma classe base (superclasse). Por exemplo, uma classe "Veículos” pode ser especializada em subclasses como "Carro”, "Moto” e "caminhão”, cada um herdando atributos e métodos comuns da classe "Veículos”, enquanto adiciona seus próprios atributos e métodos específicos.



		# Superclasse
class Veiculo:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo

    def descrever(self):
        return f"Veículo {self.marca} {self.modelo}"

# Subclasse
class Carro(Veiculo):
    def __init__(self, marca, modelo, portas):
        super().__init__(marca, modelo)  # Chama o construtor da superclasse
        self.portas = portas

    def descrever(self):  # Sobrescrita do método
        return f"Carro {self.marca} {self.modelo} com {self.portas} portas"

# Subclasse 
class Moto(Veiculo):
    def __init__(self, marca, modelo, cilindradas):
        super().__init__(marca, modelo)
        self.cilindradas = cilindradas

    def descrever(self):
        return f"Moto {self.marca} {self.modelo} com {self.cilindradas}cc"

# Subclasse 
class Caminhao(Veiculo):
    def __init__(self, marca, modelo, capacidade):
        super().__init__(marca, modelo)
        self.capacidade = capacidade

    def descrever(self):
        return f"Caminhão {self.marca} {self.modelo} com capacidade de {self.capacidade} toneladas"

# Criando objetos das subclasses
carro = Carro("BMW", "X6", 4)
moto = Moto("Honda", "CBR 600", 600)
caminhao = Caminhao("Volvo", "FH16", 25)


print(carro.descrever())   
print(moto.descrever())    
print(caminhao.descrever()) 		
		
		
		
		

O Polimorfismo é a propriedade de duas ou mais classes derivadas de uma mesma superclasse responderem a mesma mensagem, mas cada subclasse responde de uma forma diferente da outra classe derivada

Os princípios solid facilitam a manutenção, pois as classes implementam apenas métodos que realmente precisam, com classes com apenas uma funcionalidade.

O entendimento do código é importante pois facilita a colaboração entre os membros da equipe, ou se é um serviço onde se desenvolve com apenas uma pessoa, caso tenhas que dar manutenção futura no código, vai conseguir entender perfeitamente o que cada função faz, gerando assim menos bugs e melhor entendimento.

A importância de reduzir o acoplamento é o gerenciamento do sistema, pois se estiver classes fortemente acopladas e precisarmos fazer alteração em uma das classes, precisamos alterar o código das outras classes também.

O acoplamento se refere ao nível de dependência entre módulos de um sistema. Um baixo acoplamento é desejável, pois torna a aplicação mais flexível, reusável e organizada. Isso permite substituir partes do código sem grandes impactos, facilitando manutenção e evolução do software.

Já um alto acoplamento cria dependências fortes entre os módulos, tornando mudanças mais complexas e arriscadas. Quando um módulo altamente acoplado é alterado, outros módulos também precisam ser modificados, aumentando a complexidade do sistema.

Um exemplo de acoplamento:


// utils.ts
const formatZipCode = (zipcode: string): string => {};

const formatPhoneNumber = (phoneNumber: string): string => {};

//shipping.ts

import { formatZipCode } from "./utils.ts";

const calculateShipping = (fromZipCode: string, toZipcode: string): number => {
	fromZipCode = formatZipcode(fromZipCode)
	toZipCode = formatZipCode(toZipCode)

};

//contact.ts

import { formatZipCode } from "./util.ts"

const addressFromZipCode = (zipcode: string) => {
	zipcode = formatZipCode(zipcode)
};

Neste exemplo se o formato do CEP pra cálculo do frete (calculateShipping) precisasse ser diferente, ao modificar o formatZipCode no arquivo utils.ts, a outra função será alterada e pode ser que pare de funcionar.

Este é um exemplo chamado de Common Coupling que ocorre quando vários componentes compartilham um estado global.

S - Single Responsiblity Principle (Princípio da responsabilidade única)

O princípio SRP diz que uma classe, um componente, entidade ou uma função deve ser especializada em um único assunto.

Por exemplo: em vez de ter um funcionário em no restaurante que serve para atender, cozinhar, limpar e ainda fazer a parte o financeiro, contrata mais funcionários cada um para uma função especifica.

Com entidades independentes e isoladas você consegue reaproveitar o código mais facilmente, refatorar melhor o código, facilita a criação de teste automatizados e gerar menos bugs.

Esse código em ruby é um exemplo onde uma função tem 3 responsabilidades.

  • Buscar clientes no Banco de dados
  • Filtrar clientes ativos
  • Enviar emails

def email_clients(clients)
    clients.each do |client|
        client_record = DB.find(client)
        email(client) if client_record.active?
    end
end

Aplicando o SRP nós conseguimos isolar as responsabilidades para cada função.


def email_clients(clients)
    active_clients = active_clients(clients)
    active_clients.each { |client| email(client)}
end

def active_clients(clients)
    clients.select { |client | is_client_active?(client) }
end

def is_client_active(client)
    client_record = DB.find(client)
    client_record.active?
end

O SRP nos da uma fácil manutenção no código, se quisermos mudar a regra de ativação do cliente, só alteramos is_client_active?, e uma legibilidade melhor do código

O - Open/closed principle (Princípio do Aberto/Fechado)

O OCP diz que classes, entidades ou funções devem estar abertas para extensão e fechadas para modificação.

Um exemplo básico de OCP é um navegador de internet, por exemplo o google chrome, quando se instala extensões no navegador, não é necessário ir no binário do navegador para que a extensão funcione é só instalar que a extensão adiciona novas funcionalidades sem modificar o código do navegador.

Este exemplo abaixo é um exemplo errado de código, pois se quisessemos adicionar outro método de notificação, teria que modificar o código que ja está pronto.


class NotificationSender
  def send_email(message)
  end

  def send_whatsapp(message)
  end

  def send_sms(message)
  end
end

A maneira correta de resolver é "separar o comportamento extensível por trás de uma interface e inverter as dependências” isso significa criar uma abstração que define um conjunto de regras, e cada implementação especifica apenas segue esse padrão. Isso permite flexibilidade, facilita a manutenção e evita que partes do sistema fiquem acopladas a detalhes específicos, tornando o código mais modular e reutilizável


class NotificationSender
  def send(message)
    raise NotImplementedError, "Subclasses must implement the 'send' method"
  end
end

class EmailNotification < NotificationSender
  def send(message)
  end
end


class WhatsAppNotification < NotificationSender
  def send(message)
  end
end

class SMSNotification < NotificationSender
  def send(message)
  end
end

class Notification
  def send(notification_sender, message)
    notification_sender.send(message)
  end
end

Com o Principio Aberto/Fechado podemos implementar novas classes sem modificar o código existente. Ou seja, cada classe está fechada para modificação mas podem ter diversas outras notificações sem precisar alterar o código existente.

L - Liskov Substitution Principle (Princípio da substituição de Liskov)

O LSP diz que objetos podem ser substituídos por seus subtipos sem que isso afete a execução correta do programa.

Respeitar o Princípio de Liskov, força o seu programa a ter as abstrações no nível certo e ser mais consistente, sugere que você não faça subclasses terem um comportamento não esperado, no sentido de um método fundamental existir em uma altura da árvore de herança, e depois esse mesmo método gerar uma exceção numa altura mais abaixo.

O código abaixo mostra como uma subclasse pode substituir uma classe base sem lançar uma exceção inesperada.


class A
  def get_name
    puts 'Meu nome é A'
  end
end

class B < A
  def get_name
    puts 'Meu nome é B'
  end
end

objeto1 = A.new
objeto2 = B.new

def print_name(objeto)
  objeto.get_name
end

print_name(objeto1) # Meu nome é A
print_name(objeto2) # Meu nome é B

A classe B extende a classe A e o método get_name, foi instanciado 2 objetos, cada um de um tipo e ao imprimir ambos os objetos a partir da função print_name que recebe o objeto do tipo A cada um chama a sua própria função get_name. Está sendo passado como parâmetro tanto a classe base como a subclasse e o código segue funcionando como esperado.

I - Interface Segregation Principle (Princípio da Segregação da Interface)

O ISP diz que uma classe não deve ser forçada a implementar interface e métodos que não iram utilizar, esse principio diz que é melhor criar interfaces mais especificas ao invés de ter uma única interface genérica.

Por exemplo: se cria interface Animal, onde se tem Dorme, Come, Voa.

O pássaro, consegue dormir, comer e voar.

O elefante consegue dormir e comer, mas não consegue voar


module Animal
  def sleep
    raise NotImplementedError, "Subclasses must implement the 'sleep' method"
  end

  def eat
    raise NotImplementedError, "Subclasses must implement the 'eat' method"
  end

  def fly
    raise NotImplementedError, "Subclasses must implement the 'fly' method"
  end
end


class Bird
  include Animal

  def sleep
    puts "Bird sleeping..."
  end

  def eat
    puts "Bird eating..."
  end

  def fly
    puts "Bird flying..."
  end
end


class Elephant
  include Animal

  def sleep
    puts "Elephant sleeping..."
  end

  def eat
    puts "Elephant eating..."
  end
end

Rodando esse código no terminal gerou um erro, pois a não foi implementado o método voar na classe elefante.

(3.13.1) > ruby isp.rb Bird sleeping... Bird eating... Bird flying... Elephant sleeping... Elephant eating... isp.rb:12:in fly': Subclasses must implement the 'fly' method (NotImplementedError)
from isp.rb:56:in <main>' (3.13.1) > I

Após refatorar o código, foi criado interfaces fragmentadas para que cada classe faça o que ela pode fazer, evitando erro de herança desnecessários.


module Animal
  def sleep
    raise NotImplementedError, "Subclasses must implement the 'sleep' method"
  end

  def eat
    raise NotImplementedError, "Subclasses must implement the 'eat' method"
  end
end

module FlyingAnimal
  include Animal

  def fly
    raise NotImplementedError, "Subclasses must implement the 'fly' method"
  end
end

class Bird
  include FlyingAnimal  

  def sleep
    puts "Bird sleeping..."
  end

  def eat
    puts "Bird eating..."
  end

  def fly
    puts "Bird flying..."
  end
end

class Elephant
  include Animal  

  def sleep
    puts "Elephant sleeping..."
  end

  def eat
    puts "Elephant eating..."
  end
end

(3.13.1) > ruby isp.rb Bird sleeping... Bird eating... Bird flying... Elephant sleeping... Elephant eating... (3.13.1) >

D - Dependency Inversion Principle (Princípio de Inversão de Dependência)

De acordo com Uncle Bob, esse princípio pode ser definido da seguinte forma:

Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração.

Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Você só precisa aplicar consequentemente os princípios Open/Closed e Liskov Substitution à sua base de código. Depois de fazer isso, suas classes também obedecem ao DIP. Isso permite que você altere componentes de nível superior e inferior sem afetar nenhuma outra classe, desde que você não altere nenhuma abstração de interface.


require_relative 'mysql_connection' 

class PasswordReminder
  def initialize
    @db_connection = MySQLConnection.new
  end

end

Neste pequeno trecho de código temos um alto nível de acoplamento, pois a classe PasswordReminder tem a responsabilidade de criar uma instância da classe MySQLConnection.

Criamos uma interface DBConnectionInterface e refatoramos o código para que os módulos de alto e baixo nível dependam da abstração proposta pela interface que acabamos de criar, com isso a classe PasswordReminder não tem a mínima ideia de qual banco de dados a aplicação irá utilizar.


module DBConnectionInterface
  def connect
    raise NotImplementedError, "Subclasses must implement the 'connect' method"
  end
end

class MySQLConnection
  include DBConnectionInterface

  def connect

    puts "Conectando ao MySQL..."
  end
end

class OracleConnection
  include DBConnectionInterface

  def connect
  
    puts "Conectando ao Oracle..."
  end
end

class PasswordReminder
  def initialize(db_connection)
    @db_connection = db_connection
  end

  def remind
    @db_connection.connect
    puts "Lembrete de senha enviado!"
  end
end

Conclusão

Os princípios foram apresentados pela primeira vez pelo Engenheiro de Software Robert C. Martin (Uncle Bob) nos anos 2000, Design Principles and Design Patterns, a sigla SOLID foi inventada por volta de 2004 por Michael Feathers.

O Entendimento dos princípios SOLID é uma forma de aprimorar a qualidade e a eficiência do nosso software, construindo uma base sólida e deixando nosso código escalável e flexível. Facilitando a implementação de novos requisitos e a simplicidade da manutenção do sistema, promovendo um código mais legível e compreensível, facilitando a colaboração entre os membros da equipe, reduzindo as chances de erro e entregando um software com mais qualidade.

Referências

https://medium.com/desenvolvendo-com-paixao/o-que-é-solid-o-guia-completo-para-você-entender-os-5-princípios-da-poo-2b937b3fc530

https://www.youtube.com/watch?v=6SfrO3D4dHM

https://stackify.com/dependency-inversion-principle/

https://blog.grancursosonline.com.br/pilares-da-poo/

https://en.wikipedia.org/wiki/SOLID

https://staff.cs.utu.fi/~jounsmed/doos_06/material/DesignPrinciplesAndPatterns.pdf

https://medium.com/mercos-engineering/acoplamento-de-software-db9d2ba264fd

https://www.youtube.com/watch?v=poJhAnAQd4I

Carregando publicação patrocinada...