Kilderson Sena
Cearense arretado e amante da programação. Full Stack Dev, Pai do Kauan Lucas, viciado em café, futebol e Rock'n Roll
SOLID
Como você nunca viu
PRINCÍPIOS
Por: 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
youtube.com/yiiacademybr
@yiiacademy
yiiacademy.com.br
fb.com/devtubebr
@devtubebr
yiiacademy.com.br/8-motivos-para-usar-o-yii-2
devtube.com.br/ebook-oo1.html
Vinícius Dias
(PHP RIO)
Junior Grossi
(PHP MG)
Willian Correa
(PHP BR)
Marcel dos Santos
(PHP SP)
1980
Início do debate dos princípios
com outros engenheiros de software.
Robert C. Martin
(Uncle Bob)
2000
2004
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
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.
Os softwares estavam
dando errado né?!
E algo precisava ser feito.
Foco na manutenibilidade a longo prazo
Criação de estruturas de software de nível médio que sejam:
Fonte: Livro Clean Architecture
Robert C. Martin
Tolerantes e resilientes a mudanças
Fáceis de entender
Sejam base de componentes para serem reutilizados em muitos sistemas
Totalmente independente de
linguagem de programação
SOLID serve somente para softwares orientado a objetos?
Para aqueles(as) que acham
que SIM...
"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
"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
"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
"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
"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
"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.)
A idéia é que você não marrete tudo numa única classe, criando uma classe Deus
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:
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
Mas existem alguns "indicadores" como tamanho de classes, tamanho de métodos que vai te dar um norte.
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);
}
}
}
"Um artefato de software deve ser
aberto para extensão, mas
fechado para modificação."
Fonte: Livro Clean Architecture
Robert C. Martin
"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
"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;
}
}
"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
"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
Como foi visto, a LSP opera nas relações de herança e usabilidade das classes
Quando a LSP é violada, não é seguro substituir as estratégias das classes.
a LSP é bastante relacionada a OCP. Como diz o próprio Uncle Bob:
"Uma violação do LSP é uma violação latente ao OCP"
Nenhum cliente deve ser forçado a depender dos métodos que não usa. Interfaces maiores devem ser divididas em menores
Muitas interfaces para clientes específicos é muito melhor do que uma mega interface que atende a múltiplos propósitos.
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;
}
}
"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
"É 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
"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();
}
}
fb.com/kilderson.sena
@derson_sena
@derson_sena
dersonsena
By Kilderson Sena
Nessa apresentação mostro de uma forma direta, prática de como e quando aplicar os princípios SOLID na sua vida.
Cearense arretado e amante da programação. Full Stack Dev, Pai do Kauan Lucas, viciado em café, futebol e Rock'n Roll