SOLID

Como você nunca viu

PRINCÍPIOS

Por: Kilderson Sena

Quem é tu mermo?

Kilderson Sena

🤓 Sênior Fullstack Developer

👨‍🎓 Graduado em Análise e Desenv. Sistemas

🤘 Movido a café e Rockn'roll

👨‍💻 Programador há mais de 10 anos

👨‍👩‍👦 Esposo da Dayanny Pai do Kauan Lucas

fb.com/kilderson.sena

@derson_sena

@derson_sena

dersonsena

Quem é tu mermo?

Quem é tu mermo?

youtube.com/yiiacademybr

@yiiacademy

yiiacademy.com.br

Quem é tu mermo?

Quem é tu mermo?

fb.com/devtubebr

@devtubebr

Quem é tu mermo?

8 Motivos para

usar o Yii2

yiiacademy.com.br/8-motivos-para-usar-o-yii-2

Aprenda Orientação a Objetos com Forma de Gelo

devtube.com.br/ebook-oo1.html

Agradecimentos

Vinícius Dias

(PHP RIO)

Junior Grossi

(PHP MG)

Willian Correa
(PHP BR)

Marcel dos Santos
(PHP SP)

História

1980
Início do debate dos princípios

com outros engenheiros de software.

Robert C. Martin
(Uncle Bob)

2000

2004

História

1980
Início do debate dos princípios

com outros engenheiros de software.

Robert C. Martin
(Uncle Bob)

2000
Depois de adicionar, mesclar e remover princípios, aqui foi estabilizado só que numa ordem aleatória.

2004

História

1980
Início do debate dos princípios

com outros engenheiros de software.

Robert C. Martin
(Uncle Bob)

2000
Depois de adicionar, mesclar e remover princípios, aqui foi estabilizado só que numa ordem aleatória.

2004
Michael Feathers ajudou a organizar os princípios como é apresentado hoje em dia.

Qual foi motivação?

Os softwares estavam

dando errado né?!

E algo precisava ser feito.

Foco na manutenibilidade a longo prazo

Objetivo dos Princípios

Criação de estruturas de software de nível médio que sejam:

Fonte: Livro Clean Architecture

Robert C. Martin

Objetivo dos Princípios

Tolerantes e resilientes a mudanças

Objetivo dos Princípios

Fáceis de entender

Objetivo dos Princípios

Sejam base de componentes para serem reutilizados em muitos sistemas

Objetivo dos Princípios

Totalmente independente de

linguagem de programação

Mão na Orelha!!!

SOLID serve somente para softwares orientado a objetos?

Mão na Orelha!!!

Para aqueles(as) que acham

que SIM...

Sobre o SOLID

"Os princípios nos dizem como organizar as funções e estruturas de dados em classes e como devem ser interconectadas."

Fonte: Livro Clean Architecture

Robert C. Martin

Sobre o SOLID

"O uso da palavra classe não implica que esses princípios sejam aplicadas apenas a softwares orientado a objetos."

Fonte: Livro Clean Architecture

Robert C. Martin

Classe

"Uma classe é apenas um agrupamento acoplado de funções e dados. Cada software tem esses agrupamentos, chamados OU NÃO de classes."

Fonte: Livro Clean Architecture

Robert C. Martin

Todos os exemplos serão feitos em PHP

SRP

"De todos os princípios, a SRP provavelmente é o menos compreendido.

Em geral, os programadores imaginam logo que todos os módulos devem fazer apenas uma coisa."

Fonte: Livro Clean Architecture

Robert C. Martin

SRP

"Não se engane, saiba que há um princípio como esse: uma função deve
fazer uma, e apenas uma, coisa (...)

mas isso não é o SRP."

Fonte: Livro Clean Architecture

Robert C. Martin

SRP

"Historicamente, o SRP tem sido descrito como:
Um módulo deve ter uma, e apenas uma,
 razão para mudar."

Fonte: Livro Clean Architecture

Robert C. Martin

(Um módulo deve ser responsável por um, e apenas um, ator.)

SRP

A idéia é que você não marrete tudo numa única classe, criando uma classe Deus

SRP

Talvez seja uma das mais difíceis de aplicar de prima,

pois você ainda não tem noção dos agrupamentos de classes e contextos

Minha opinião:

SRP

A sugestão que dou é:

primeiro crie a ideia das coisas e em um segundo momento refatore e distribua as responsabilidades

Mas como definir responsabilidade?

Como muitas coisas na Engenharia de Software:

DEPENDE!!!

 

É algo muito subjetivo e torna muito difícil dá uma definição concreta

SRP

Mas existem alguns "indicadores" como tamanho de classes, tamanho de métodos que vai te dar um norte.

SRP

class Client
{
    private string $name;

    private string $email;

    private DateTimeInterface $birthDate;

    public function __construct(
        string $name,
        string $email,
        DateTimeInterface $birthDate
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->birthDate = $birthDate;
    }
	
    // hidden getters and setters methods
}
class BirthdayLayout implements EmailLayoutInterface
{
    public function getContent(Client $client): string
    {
        return "
            <h1>{$client->getName()}</h1>
            <h3>Hoje é {$client->getBirthDate()->format('d/m/Y')}!
            É o seu dia, que dia mais Feliz! =D</h3>
        ";
    }
}
class EmailSender
{
    private array $clients = [];

    private EmailLayoutInterface $layout;

    public function __construct(EmailLayoutInterface $layout)
    {
        $this->layout = $layout;
        $this->loadClientsFromStorage();
    }

    public function getClients(): array
    {
        return $this->clients;
    }

    public function sendMails()
    {
    	// hidden implementation
    }

    private function loadClientsFromStorage()
    {
        // hidden implementation
    }

    private function registerLog(string $content)
    {
        // hidden implementation
    }
}
class EmailSender
{
    // hidden implementation
	
    public function getClients(): array
    {
        return $this->clients;
    }
    
    public function sendMails()
    {
        foreach ($this->clients as $client) {
            $content = $this->layout->getContent($client);
            $this->registerLog($content);
        }
    }

    private function loadClientsFromStorage()
    {
        $filePath = realpath(__DIR__) . '/data/birthday-list.xml';
        $xmlHandler = simplexml_load_file($filePath);

        foreach ($xmlHandler->aniversariante as $row) {
            $birth = new DateTimeImmutable($row->data_nascimento);
            $this->clients[] = new Client($row->nome, $row->email, $birth);
        }
    }

    private function registerLog(string $content)
    {
        $filePath = realpath(__DIR__) . '/logs/email-sender.txt';
        $content = date('d/m/Y H:i:s') . "\n" . $content . "\n\n";

        file_put_contents($filePath, $content, FILE_APPEND);
    }
}
class TextFileLogger implements LoggerInterface
{
    public function registerLog(string $content)
    {
        $filePath = realpath(__DIR__) . '/logs/email-sender.txt';
        $content = date('d/m/Y H:i:s') . "\n" . $content . "\n\n";

        file_put_contents($filePath, $content, FILE_APPEND);
    }
}
class EmailSender
{
    // hidden implementation
	
    public function getClients(): array
    {
        return $this->clients;
    }
    
    public function sendMails()
    {
        foreach ($this->clients as $client) {
            $content = $this->layout->getContent($client);
            $this->registerLog($content);
        }
    }

    private function loadClientsFromStorage()
    {
        $filePath = realpath(__DIR__) . '/data/birthday-list.xml';
        $xmlHandler = simplexml_load_file($filePath);

        foreach ($xmlHandler->aniversariante as $row) {
            $birth = new DateTimeImmutable($row->data_nascimento);
            $this->clients[] = new Client($row->nome, $row->email, $birth);
        }
    }
}
abstract class ClientParserAbstract
{
    /**
     * @var Client[]
     */
    protected array $clients = [];

    protected string $dataPath;

    abstract protected function parse();

    public function __construct()
    {
        $this->dataPath = realpath(__DIR__) . '/data';
        $this->parse();
    }

    public function getClients(): array
    {
        return $this->clients;
    }
}
class ClientJSONParser extends ClientParserAbstract
{
    protected function parse()
    {
        $filePath = $this->dataPath . '/birthday-list.json';
        $jsonData = json_decode(file_get_contents($filePath));

        foreach ($jsonData->aniversariantes as $row) {
            $birth = new DateTimeImmutable($row->data_nascimento);
            $client = new Client($row->nome, $row->email, $birth);
            $this->clients[] = $client;
        }
    }
}
class ClientXMLParser extends ClientParserAbstract
{
    protected function parse()
    {
        $filePath = $this->dataPath . '/birthday-list.xml';
        $xmlHandler = simplexml_load_file($filePath);

        foreach ($xmlHandler->aniversariante as $row) {
            $birth = new DateTimeImmutable($row->data_nascimento);
            $client = new Client($row->nome, $row->email, $birth);
            $this->clients[] = $client;
        }
    }
}
class EmailSender
{
    // hidden implementation
	
    public function sendMails()
    {
        foreach ($this->clients as $client) {
            $content = $this->layout->getContent($client);
            $this->registerLog($content);
        }
    }
}
class EmailSender
{
    private EmailLayoutInterface $layout;

    private ClientParserAbstract $parser;
    
    private LoggerInterface $logger;

    public function __construct(
        EmailLayoutInterface $layout,
        ClientParserAbstract $parser,
        LoggerInterface $logger
    ) {
        $this->layout = $layout;
        $this->parser = $parser;
        $this->logger = $logger;
    }

    public function sendMails()
    {
        // send mail implementation ...

        foreach ($this->parser->getClients() as $client) {
            $content = $this->layout->getContent($client);
            $this->logger->registerLog($content);
        }
    }
}

OCP

"Um artefato de software deve ser

aberto para extensão, mas

fechado para modificação."

Fonte: Livro Clean Architecture

Robert C. Martin

OCP

"Em outras palavras, o comportamento de um artefato de software deve ser extensível sem que isso modifique esse artefato."

Fonte: Livro Clean Architecture

Robert C. Martin

OCP

"Seu objetivo consiste em fazer com que o sistema seja fácil de estender sem que a mudança cause um alto impacto"

Fonte: Livro Clean Architecture

Robert C. Martin

class Employee
{
    private string $name;

    private float $salary;

    private float $commission = 0;
    
    private string $department;

    public function __construct(
        string $name,
        float $salary,
        string $department
    ) {
        $this->name = $name;
        $this->salary = $salary;
        $this->department = $department;
    }

    public function getSalary(int $salesInMonth): float
    {
        if ($this->department === 'seller') {
            $this->commission = ($salesInMonth * 0.2);
        } else if ($this->department === 'supervisor') {
            $this->commission = ($salesInMonth * 0.3);
        }

        return $this->salary + $this->commission;
    }
}

Seguinte time: deve ser adicionado uma comissão de 25% para os coordenadores.

Deve ser adicionado também uma comissão de 45% para os gerentes, porque eu também sou filho de Deus.

JOÃO PAULO

Gerente de Projetos Auto Avaliar

class Employee
{
    private string $name;

    private float $salary;

    private float $commission = 0;
    
    private string $department;

    public function __construct(
        string $name,
        float $salary,
        string $department
    ) {
        $this->name = $name;
        $this->salary = $salary;
        $this->department = $department;
    }

    public function getSalary(int $salesInMonth): float
    {
        if ($this->department === 'seller') {
            $this->commission = ($salesInMonth * 0.2);
        } else if ($this->department === 'coordinator') {
            $this->commission = ($salesInMonth * 0.25);
        } else if ($this->department === 'supervisor') {
            $this->commission = ($salesInMonth * 0.3);
        } else if ($this->department === 'manager') {
            $this->commission = ($salesInMonth * 0.45);
        }

        return $this->salary + $this->commission;
    }
}
class Seller implements DepartmentInterface
{
    public function calculateCommission(int $salesInMonth): float
    {
        return $salesInMonth * 0.2;
    }
}
class Coordinator implements DepartmentInterface
{
    public function calculateCommission(int $salesInMonth): float
    {
        return $salesInMonth * 0.25;
    }
}
class Supervisor implements DepartmentInterface
{
    public function calculateCommission(int $salesInMonth): float
    {
        return $salesInMonth * 0.3;
    }
}
class Manager implements DepartmentInterface
{
    public function calculateCommission(int $salesInMonth): float
    {
        return $salesInMonth * 0.45;
    }
}
class Employee
{
    private string $name;

    private float $salary;

    private float $commission = 0;
    
    private DepartmentInterface $department;

    public function __construct(
        string $name,
        float $salary,
        DepartmentInterface $department
    ) {
        $this->name = $name;
        $this->salary = $salary;
        $this->department = $department;
    }

    public function getSalary(float $salesInMonth): float
    {
        $this->commission = $this->department->calculateCommission($salesInMonth);

        return $this->salary + $this->commission;
    }
}

LSP

"O que queremos aqui é algo como a seguinte propriedade de substituição: se, para cada objeto o1 de tipo S, houver um objeto o2 de tipo T, de modo que, para todos os programas P definidos em termos de T, o comportamento de P não seja modificado quando o1 for substituído por o2, então S é um subtipo de T. 1"

Fonte: Livro Clean Architecture

Robert C. Martin

LSP

"As classes derivadas devem ser substituíveis por suas classes bases."

abstract class File
{
    protected string $name;

    public function open()
    {
        // hidden implementation
    }
    
    public function close()
    {
        // hidden implementation
    }
    
    // others methods...
}
class WordFile extends File
{
    public function exportFile()
    {
        // hidden implementation
    }
    
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class PDFFile extends File
{
    public function exportFile()
    {
        // hidden implementation
    }
    
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class ShellFile extends File
{
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class CustomerReportGenerator
{
    private File $file;
    
    public function __construct(File $file)
    {
        $this->file = $file;
    }
    
    public function generate()
    {
    	$file->exportFile();
    }
}
$wordFile = new WordFile();
$pdfFile = new PDFFile();
$shellFile = new ShellFile();

// It works =)
$report1 = (new CustomerReportGenerator($wordFile))->generate();

// It works =)
$report2 = (new CustomerReportGenerator($pdfFile))->generate();

// It doesn't works =(
$report3 = (new CustomerReportGenerator($shellFile))->generate();
abstract class File
{
    protected string $name;

    public function open()
    {
        // hidden implementation
    }
    
    public function close()
    {
        // hidden implementation
    }
    
    // others methods...
}
abstract class ExportableFile extends File
{
    public abstract function exportFile();
    
    // others methods...
}
class WordFile extends ExportableFile
{
    public function exportFile()
    {
        // hidden implementation
    }
    
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class PDFFile extends ExportableFile
{
    public function exportFile()
    {
        // hidden implementation
    }
    
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class ShellFile extends File
{
    private function helperMethod1()
    {
        // hidden implementation
    }
    
    private function helperMethod2()
    {
        // hidden implementation
    }
}
class CustomerReportGenerator
{
    private ExportableFile $file;
    
    public function __construct(ExportableFile $file)
    {
        $this->file = $file;
    }
    
    public function generate()
    {
    	$file->exportFile();
    }
}

Fatal error: Uncaught TypeError: Argument 1 passed to CustomerReportGenerator::__construct() must be an instance of ExportableFile, instance of File given

LSP

Como foi visto, a LSP opera nas relações de herança e usabilidade das classes

LSP

Quando a LSP é violada, não é seguro substituir as estratégias das classes.

LSP

a LSP é bastante relacionada a OCP. Como diz o próprio Uncle Bob:

"Uma violação do LSP é uma violação latente ao OCP"

ISP

Nenhum cliente deve ser forçado a depender dos métodos que não usa.  Interfaces maiores devem ser divididas em menores

ISP

Muitas interfaces para clientes específicos é muito melhor do que uma mega interface que atende a múltiplos propósitos.

ISP

Seria a mesma ideia do SRP só que voltado para interfaces

interface EmployeeInterface
{
    public function getDepartment(): string;
    public function calculateSalary(): float;
    public function calculate13Salary(): float;
}
class Manager implements EmployeeInterface
{
    public function getDepartment(): string
    {
    	return 'Manager';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 5000 + 500 + 450;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 5000 + 500 + 450 + 12 + 45;
    }
}
class Coordinator implements EmployeeInterface
{
    public function getDepartment(): string
    {
    	return 'Coordinator';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 6000 + 300 + 350;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 6000 + 450 + 540 + 12 + 45;
    }
}
class Developer implements EmployeeInterface
{
    public function getDepartment(): string
    {
    	return 'Developer';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 6000 + 560 + 850;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 6000 + 560 + 700 + 12 + 45;
    }
}
class Trainee implements EmployeeInterface
{
    public function getDepartment(): string
    {
    	return 'Trainee (Estagiário)';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 1000;
    }
    
    public function calculate13Salary(): float
    {
    	throw new Exception(
            "Trainee has no 13th salary."
        )
    }
}
interface EmployeeInterface
{
    public function getDepartment(): string;
    public function calculateSalary(): float;
}
interface EmployeeWith13thSalaryInterface extends EmployeeInterface
{
    public function calculate13Salary(): float;
}
class Manager implements EmployeeWith13thSalaryInterface
{
    public function getDepartment(): string
    {
    	return 'Manager';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 5000 + 500 + 450;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 5000 + 500 + 450 + 12 + 45;
    }
}
class Coordinator implements EmployeeWith13thSalaryInterface
{
    public function getDepartment(): string
    {
    	return 'Coordinator';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 6000 + 300 + 350;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 6000 + 450 + 540 + 12 + 45;
    }
}
class Developer implements EmployeeWith13thSalaryInterface
{
    public function getDepartment(): string
    {
    	return 'Developer';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 6000 + 560 + 850;
    }
    
    public function calculate13Salary(): float
    {
    	// 13th salary logic
        return 6000 + 560 + 700 + 12 + 45;
    }
}
class Trainee implements EmployeeInterface
{
    public function getDepartment(): string
    {
    	return 'Trainee (Estagiário)';
    }
    
    public function calculateSalary(): float
    {
    	// salary logic
        return 1000;
    }
}

DIP

"Os sistemas mais flexíveis são aqueles em que as dependências de código-fonte se referem apenas a abstrações e não a itens concretos."

Fonte: Livro Clean Architecture

Robert C. Martin

DIP

"É impraticável tratar essa ideia como uma regra, pois os sistemas de software dependem de muitas facilidades concretas."

Fonte: Livro Clean Architecture

Robert C. Martin

DIP

"Por exemplo, a classe String no Java , DOMDocument  e  Array Object do PHP  são concretas e não seria prático forçá-la a ser abstrata"

Fonte: Livro Clean Architecture

Robert C. Martin

class Connection
{
    public function __construct(MySQL $mySQL)
    {
        $mySQL->connect();
        $mySQL->insert();
        $mySQL->update();
    }
}
interface ISGBD
{
    public function getName(): string;
    public function connect();
    public function insert();
    public function update();
    public function delete();
    public function queryAll();
}
class MySQL implements ISGBD
{
    public function getName()
    {
        return 'MYSQL';
    }
    
    public function connect()
    {
        // connection logic...
    }

    public function insert()
    {
        // insert logic...
    }

    public function update()
    {
        // update logic...
    }

    public function delete()
    {
        // delete logic...
    }

    public function queryAll()
    {
        // query all logic...
    }
}
class PostgreSQL implements ISGBD
{
    public function getName()
    {
        return 'PostgreSQL';
    }
    
    public function connect()
    {
        // connection logic...
    }

    public function insert()
    {
        // insert logic...
    }

    public function update()
    {
        // update logic...
    }

    public function delete()
    {
        // delete logic...
    }

    public function queryAll()
    {
        // query all logic...
    }
}
class Connection
{
    public function __construct(ISGBD $sgbd)
    {
        $sgbd->connect();
        $sgbd->insert();
        $sgbd->update();
    }
}

Obrigado! =D

Kilderson Sena

fb.com/kilderson.sena

@derson_sena

@derson_sena

dersonsena

SOLID como você nunca viu

By Kilderson Sena

SOLID como você nunca viu

Nessa apresentação mostro de uma forma direta, prática de como e quando aplicar os princípios SOLID na sua vida.

  • 203