O termo SOLID é um acronimo para cinco famosos princípios. São eles: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation e Dependency Inversion. Vamos ver alguns exemplos na prática.
- Princípio da responsabilidade única
- Princípio do aberto-fechado
- Princípio da substituição de Liskov
- Princípio da segregação por interface
- Princípio da inversão de dependência
Abreviada como SRP, este princípio afirma (de maneira muito intuitiva) que uma classe deve ter apenas uma única razão para mudar. Em outras palavras, você deve criar classes com uma única "responsabilidade" para que sejam mais fáceis de manter e mais difíceis de quebrar. Vejamos no exemplo como seria uma classe responsável por que calcular a área de uma forma (seja ela um quadrado, circulo ou triangulo), além de resolver outras questões como desenhar as formas na tela e etc.
Classe com multiplas responsabilidades
Classe com multiplas responsabilidades
import 'dart:web_gl';
/**
* Single Responsibility Principle (SRP)
*/
class Shapes {
List<String> cache = [];
// Calculations
double squareArea(double l) {
return l * l;
}
double circleArea(double r) {
return (r * r) * 3.1415;
}
double triangleArea(double b, double h) {
return (b * h) / 2;
}
// Paint to the screen
void paintSquare(Canvas c) {
// paint something into screen
}
void paintCircle(Canvas c) {
// paint something into screen
}
void paintTriangle(Canvas c) {
// paint something into screen
}
// GET requests
String wikiArticle(String figure) {
return 'Some supercool article';
}
void _cacheElements(String text) {
// something must occurs inside this class
// and cannot be executed outside
}
}
Aplicando SRP
Agora nossas tarefas estão divididas em diferentes responsabilidades. Vejamos como como ficam as classes que foram geradas a partir daí.
import './circle.dart';
import './square.dart';
import './rectangle.dart';
import './triangle.dart';
void main() {
final Circle circle = Circle(25);
final Triangle triangle = Triangle(300, 200);
final Rectangle rectangle = Rectangle(250, 180);
final Square square = Square(60);
print(circle.area());
print(triangle.area());
print(rectangle.area());
print(square.area());
}
abstract class Shape {
double area();
}
import './shape.dart';
class Rectangle extends Shape {
double width;
double heigth;
Rectangle(this.width, this.heigth);
@override
double area() => width * heigth;
}
import './shape.dart';
class Triangle extends Shape {
double base;
double height;
Triangle(this.base, this.height);
@override
double area() => (base * height) / 2;
}
import './shape.dart';
class Circle extends Shape {
final double radius;
Circle(this.radius);
@override
double area() => 3.1415 * (radius * radius);
}
Este princípio resumidamente diz que: Classes precisam estar abertas para extensão mas fechadas para mudanças. Ou seja, o crescimento da classe acontecer através de extensões, heranças e etc.
Classe implementada para mudar ao invés de ser extendida
Vejamos um exemplo de uma classe onde não se aplica o príncipio de aberto e fechado.
Classe Implementada sem o Princípio
class Rectangle {
final double width;
final double height;
const Rectangle(this.width, this.height);
}
class Circle {
final double radius;
const Circle(this.radius);
double get PI => 3.1415;
}
class AreaCalculator {
double calculate(Object shape) {
if (shape is Rectangle) {
return shape.width * shape.height;
} else {
final c = shape as Circle;
return c.radius * c.radius * c.PI;
}
}
}
Aplicando OCP
import './circle.dart';
import './rectangle.dart';
import './area_calculator.dart';
void main() {
final circle = Circle(30.0);
final rectangle = Rectangle(20.0, 30.5);
final calculator = AreaCalculator();
print(calculator.calculate(circle));
print(calculator.calculate(rectangle));
}
import './area.dart';
class Circle extends Area {
final double radius;
const Circle(this.radius);
@override
double computeArea() => radius * radius * 3.1415;
}
import './area.dart';
class Rectangle extends Area {
final double width;
final double height;
const Rectangle(this.width, this.height);
@override
double computeArea() => width * height;
}
abstract class Area {
const Area();
double computeArea();
}
import './area.dart';
class AreaCalculator {
double calculate(Area shape) => shape.computeArea();
}