One place for hosting & domains

      princípios

      SOLID: Los primeros 5 principios del diseño orientado a objetos


      Introducción

      SOLID es un acrónimo de los primeros cinco principios del diseño orientado a objetos (OOD) de Robert C. Martin (también conocido como el Tío Bob).

      Nota: Aunque estos principios pueden aplicarse a varios lenguajes de programación, el código de muestra que se incluye en este artículo usará PHP.

      Estos principios establecen prácticas que se prestan al desarrollo de software con consideraciones para su mantenimiento y expansión a medida que el proyecto se amplía. Adoptar estas prácticas también puede ayudar a evitar los aromas de código, refactorizar el código y aprender sobre el desarrollo ágil y adaptativo de software.

      SOLID representa:

      En este artículo, se le presentará cada principio por separado para comprender la forma en que SOLID puede ayudarlo a ser un mejor desarrollador.

      Principio de responsabilidad única

      El principio de responsabilidad única (SRP) establece:

      Una clase debe tener una y una sola razón para cambiar, lo que significa que una clase debe tener solo un trabajo.

      Por ejemplo, considere una aplicación que toma una colección de formas, entre círculos y cuadrados, y calcula la suma del área de todas las formas de la colección.

      Primero, cree las clases de forma y haga que los constructores configuren los parámetros requeridos.

      Para las cuadrados, deberá saber la longitud de un lado:

      class Square
      {
          public $length;
      
          public function construct($length)
          {
              $this->length = $length;
          }
      }
      

      Para los círculos, deberá saber el radio:

      class Circle
      {
          public $radius;
      
          public function construct($radius)
          {
              $this->radius = $radius;
          }
      }
      

      A continuación, cree la clase AreaCalculator y luego escriba la lógica para sumar las áreas de todas las formas proporcionadas. El área de un cuadrado se calcula por longitud al cuadrado. El área de un círculo se calcula mediante pi por el radio al cuadrado.

      class AreaCalculator
      {
          protected $shapes;
      
          public function __construct($shapes = [])
          {
              $this->shapes = $shapes;
          }
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'Square')) {
                      $area[] = pow($shape->length, 2);
                  } elseif (is_a($shape, 'Circle')) {
                      $area[] = pi() * pow($shape->radius, 2);
                  }
              }
      
              return array_sum($area);
          }
      
          public function output()
          {
              return implode('', [
                '',
                    'Sum of the areas of provided shapes: ',
                    $this->sum(),
                '',
            ]);
          }
      }
      

      Para usar la clase AreaCalculator, deberá crear una instancia de la clase y pasar una matriz de formas para mostrar el resultado en la parte inferior de la página.

      A continuación, se muestra un ejemplo con una colección de tres formas:

      • un círculo con un radio de 2
      • un cuadrado con una longitud de 5
      • un segundo cuadrado con una longitud de 6
      $shapes = [
        new Circle(2),
        new Square(5),
        new Square(6),
      ];
      
      $areas = new AreaCalculator($shapes);
      
      echo $areas->output();
      

      El problema con el método de salida es que AreaCalculator maneja la lógica para generar los datos.

      Considere un escenario en el que el resultado debe convertirse a otro formato como JSON.

      La clase AreaCalculator manejaría toda la lógica. Esto violaría el principio de responsabilidad única. La clase AreaCalculator solo debe ocuparse de la suma de las áreas de las formas proporcionadas. No debería importar si el usuario desea JSON o HTML.

      Para abordar esto, puede crear una clase SumCalculatorOutputter por separado y usarla para manejar la lógica que necesita para mostrar los datos al usuario:

      class SumCalculatorOutputter
      {
          protected $calculator;
      
          public function __constructor(AreaCalculator $calculator)
          {
              $this->calculator = $calculator;
          }
      
          public function JSON()
          {
              $data = [
                'sum' => $this->calculator->sum(),
            ];
      
              return json_encode($data);
          }
      
          public function HTML()
          {
              return implode('', [
                '',
                    'Sum of the areas of provided shapes: ',
                    $this->calculator->sum(),
                '',
            ]);
          }
      }
      

      La clase SumCalculatorOutputter funcionaría así:

      $shapes = [
        new Circle(2),
        new Square(5),
        new Square(6),
      ];
      
      $areas = new AreaCalculator($shapes);
      $output = new SumCalculatorOutputter($areas);
      
      echo $output->JSON();
      echo $output->HTML();
      

      Ahora, la clase SumCalculatorOutputter maneja cualquier lógica que necesite para enviar los datos al usuario.

      Eso cumple con el principio de responsabilidad única.

      Principio abierto-cerrado

      Principio abierto-cerrado (S.R.P.) establece:

      Los objetos o entidades deben estar abiertos por extensión, pero cerrados por modificación.

      Esto significa que una clase debe ser ampliable sin modificar la clase en sí.

      Volvamos a ver la clase AreaCalculator y enfoquémonos en el método sum:

      class AreaCalculator
      {
          protected $shapes;
      
          public function __construct($shapes = [])
          {
              $this->shapes = $shapes;
          }
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'Square')) {
                      $area[] = pow($shape->length, 2);
                  } elseif (is_a($shape, 'Circle')) {
                      $area[] = pi() * pow($shape->radius, 2);
                  }
              }
      
              return array_sum($area);
          }
      }
      

      Considere un escenario en el que el usuario desea la sum de formas adicionales como triángulos, pentágonos, hexágonos, etc. Tendría que editar constantemente este archivo y añadir más bloques if/else. Eso violaría el principio abierto-cerrado.

      Una forma de mejorar el método sum es eliminar la lógica para calcular el área de cada forma fuera del método de la clase AreaCalculator y adjuntarlo a la clase de cada forma.

      A continuación, se muestra area definido en Square:

      class Square
      {
          public $length;
      
          public function __construct($length)
          {
              $this->length = $length;
          }
      
          public function area()
          {
              return pow($this->length, 2);
          }
      }
      

      Y aquí es el método area definido en Circle:

      class Circle
      {
          public $radius;
      
          public function construct($radius)
          {
              $this->radius = $radius;
          }
      
          public function area()
          {
              return pi() * pow($shape->radius, 2);
          }
      }
      

      El método sum para AreaCalculator puede reescribirse así:

      class AreaCalculator
      {
          // ...
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  $area[] = $shape->area();
              }
      
              return array_sum($area);
          }
      }
      

      Ahora, puede crear otra clase de forma y pasarla al calcular la suma sin romper el código.

      Sin embargo, ahora surge otro problema: ¿Cómo sabe que el objeto pasado a AreaCalculator es realmente una forma o si la forma tiene un método llamado area?

      La codificación de una interface es una parte integral de SOLID.

      Cree un ShapeInterface que sea compatible con area:

      interface ShapeInterface
      {
          public function area();
      }
      

      Modifique sus clases de forma para implement el ShapeInterface.

      A continuación, se muestra la actualización a Square:

      class Square implements ShapeInterface
      {
          // ...
      }
      

      Y aquí está la actualización a Circle:

      class Circle implements ShapeInterface
      {
          // ...
      }
      

      En el método sum para AreaCalculator, puede verificar si las formas proporcionadas son realmente instancias de ShapeInterface; de lo contrario, lanzamos una excepción:

       class AreaCalculator
      {
          // ...
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'ShapeInterface')) {
                      $area[] = $shape->area();
                      continue;
                  }
      
                  throw new AreaCalculatorInvalidShapeException();
              }
      
              return array_sum($area);
          }
      }
      

      Eso cumple con el principio abierto-cerrado.

      Principio de sustitución de Liskov

      El principio de sustitución de Liskov establece:

      Digamos que q(x) sea una propiedad demostrable sobre objetos de x, de tipo T. Entonces, q(y) debe ser demostrable para los objetos y, de tipo S, donde S es un subtipo de T.

      Esto significa que cada subclase o clase derivada debe ser sustituible por su clase base o clase principal.

      A partir de la clase AreaCalculator mostrada como ejemplo, considere una nueva clase VolumeCalculator que extiende la clase AreaCalculator:

      class VolumeCalculator extends AreaCalculator
      {
          public function construct($shapes = [])
          {
              parent::construct($shapes);
          }
      
          public function sum()
          {
              // logic to calculate the volumes and then return an array of output
              return [$summedData];
          }
      }
      

      Recuerde que la clase SumCalculatorOutputter se asemeja a esto:

      class SumCalculatorOutputter {
          protected $calculator;
      
          public function __constructor(AreaCalculator $calculator) {
              $this->calculator = $calculator;
          }
      
          public function JSON() {
              $data = array(
                  'sum' => $this->calculator->sum();
              );
      
              return json_encode($data);
          }
      
          public function HTML() {
              return implode('', array(
                  '',
                      'Sum of the areas of provided shapes: ',
                      $this->calculator->sum(),
                  ''
              ));
          }
      }
      

      Si intenta ejecutar un ejemplo como este:

      $areas = new AreaCalculator($shapes);
      $volumes = new VolumeCalculator($solidShapes);
      
      $output = new SumCalculatorOutputter($areas);
      $output2 = new SumCalculatorOutputter($volumes);
      

      Cuando invoca el método HTML en el objeto $output2, obtendrá un error E_NOTICE que le informará de conversión de matriz a cadena.

      Para solucionar esto, en vez de devolver una matriz desde el método sum de la clase VolumeCalculator, devuelva $summedData:

      class VolumeCalculator extends AreaCalculator
      {
          public function construct($shapes = [])
          {
              parent::construct($shapes);
          }
      
          public function sum()
          {
              // logic to calculate the volumes and then return a value of output
              return $summedData;
          }
      }
      

      $summedData puede ser float, double o integer.

      Eso cumple con el principio de sustitución de Liskov.

      Principio de segregación de interfaz

      El principio de segregación de interfaz establece:

      Un cliente nunca debe ser forzado a implementar una interfaz que no usan ni los clientes no deben ser forzados a depender de métodos que no usan.

      Siguiendo con el ejemplo anterior de ShapeInterface, tendrá que admitir las nuevas formas tridimensionales de Cuboid y Spheroid, y estas formas también tendrán que calcular el volumen.

      Consideraremos lo que sucedería si modificara ShapeInterface para añadir otro contrato:

      interface ShapeInterface
      {
          public function area();
      
          public function volume();
      }
      

      Ahora, cualquier forma que cree debe implementar el método volume, pero sabemos que las cuadrados son formas planas y que no tienen volumen, por lo que esta interfaz forzaría a la clase Square a implementar un método que no usa.

      Esto violaría el principio de segregación de interfaz. En su lugar, podría crear otra interfaz llamada ThreeDimensionalShapeInterface que tiene el contrato de volume y las formas tridimensionales pueden implementar esta interfaz:

      interface ShapeInterface
      {
          public function area();
      }
      
      interface ThreeDimensionalShapeInterface
      {
          public function volume();
      }
      
      class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface
      {
          public function area()
          {
              // calculate the surface area of the cuboid
          }
      
          public function volume()
          {
              // calculate the volume of the cuboid
          }
      }
      

      Este es un enfoque mucho mejor, pero hay que tener cuidado cuando se trata de escribir estas interfaces En vez de usar un ShapeInterface o un ThreeDimensionalShapeInterface, puede crear otra interfaz, quizá ManageShapeInterface e implementarla en las formas planas y en las tridimensionales.

      De esta manera, puede tener una sola API para administrar las formas:

      interface ManageShapeInterface
      {
          public function calculate();
      }
      
      class Square implements ShapeInterface, ManageShapeInterface
      {
          public function area()
          {
              // calculate the area of the square
          }
      
          public function calculate()
          {
              return $this->area();
          }
      }
      
      class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface, ManageShapeInterface
      {
          public function area()
          {
              // calculate the surface area of the cuboid
          }
      
          public function volume()
          {
              // calculate the volume of the cuboid
          }
      
          public function calculate()
          {
              return $this->area();
          }
      }
      

      Ahora en la clase AreaCalculator, puede sustituir la invocación al método area con calculate y también verificar si el objeto es una instancia de ManageShapeInterface y no de ShapeInterface.

      Eso cumple con el principio de segregación de interfaz.

      Principio de inversión de dependencia

      El principio de inversión de dependencia establece:

      Las entidades deben depender de abstracciones, no de concreciones. Indica que el módulo de alto nivel no debe depender del módulo de bajo nivel, sino que deben depender de las abstracciones.

      Este principio permite el desacoplamiento.

      A continuación, se muestra un ejemplo de un PasswordReminder que se conecta a una base de datos de MySQL:

      class MySQLConnection
      {
          public function connect()
          {
              // handle the database connection
              return 'Database connection';
          }
      }
      
      class PasswordReminder
      {
          private $dbConnection;
      
          public function __construct(MySQLConnection $dbConnection)
          {
              $this->dbConnection = $dbConnection;
          }
      }
      

      Primero, MySQLConnection es el módulo de bajo nivel mientras que PasswordReminder es de alto nivel, pero según la definición de D en SOLID, que establece que Depende de la abstracción y no de las concreciones. Este fragmento de código anterior viola este principio, ya que se está forzando a la clasevPasswordReminder a depender de la clase MySQLConnection.

      Si más adelante cambiara el motor de la base de datos, también tendría que editar la clase PasswordReminder, y esto violaría el principio abierto-cerrado.

      A la clase PasswordReminder no le debe importar qué base de datos usa su aplicación. Para solucionar estos problemas, se puede codificar a una interfaz, ya que los módulos de alto nivel y bajo nivel deben depender de la abstracción:

      interface DBConnectionInterface
      {
          public function connect();
      }
      

      La interfaz tiene un método connect y la clase MySQLConnection implementa esta interfaz. Además, en lugar de escribir directamente la clase MySQLConnection en el constructor del PasswordReminder, se indica la clase DBConnectionInterface y, sin importar el tipo de base de datos que utilice su aplicación, la clase PasswordReminder puede conectarse sin ningún problema a la base de datos y no se viola el principio abierto-cerrado.

      class MySQLConnection implements DBConnectionInterface
      {
          public function connect()
          {
              // handle the database connection
              return 'Database connection';
          }
      }
      
      class PasswordReminder
      {
          private $dbConnection;
      
          public function __construct(DBConnectionInterface $dbConnection)
          {
              $this->dbConnection = $dbConnection;
          }
      }
      

      Este código establece que los módulos de alto nivel y los de bajo nivel dependen de la abstracción.

      Conclusión

      En este artículo, se le presentaron los cinco principios del código SOLID. Los proyectos que se adhieren a los principios SOLID pueden compartirse con los colaboradores, ampliarse, modificarse, probarse y refactorizarse con menos complicaciones.

      Continúe aprendiendo leyendo sobre otras prácticas para el desarrollo ágil y adaptativo de software.



      Source link

      SOLID: os primeiros 5 princípios do design orientado a objeto


      Introdução

      SOLID é uma sigla para os primeiros cinco princípios do design orientado a objeto (OOD) criada por Robert C. Martin (também conhecido como Uncle Bob).

      Nota: embora esses princípios sejam aplicáveis a várias linguagens de programação, o código de amostra contido neste artigo usará o PHP.

      Esses princípios estabelecem práticas que contribuem para o desenvolvimento de software com considerações de manutenção e extensão à medida que o projeto cresce. A adoção dessas práticas também pode contribuir para evitar problemas de código, refatoração de código e o desenvolvimento ágil e adaptativo de software.

      SOLID significa:

      Neste artigo, cada princípio será apresentado individualmente para que você compreenda como o SOLID pode ajudá-lo(a) a melhorar como desenvolvedor(a).

      Princípio da responsabilidade única

      O Princípio da responsabilidade única (SRP) declara:

      Uma classe deve ter um e apenas um motivo para mudar, o que significa que uma classe deve ter apenas uma função.

      Por exemplo, considere um aplicativo que recebe uma coleção de formas — círculos e quadrados — e calcula a soma da área de todas as formas na coleção.

      Primeiramente, crie as classes de formas e faça com que os construtores configurem os parâmetros necessários.

      Para quadrados, será necessário saber o length (comprimento) de um lado:

      class Square
      {
          public $length;
      
          public function construct($length)
          {
              $this->length = $length;
          }
      }
      

      Para os círculos, será necessário saber o radius (raio):

      class Circle
      {
          public $radius;
      
          public function construct($radius)
          {
              $this->radius = $radius;
          }
      }
      

      Em seguida, crie a classe AreaCalculator e então escreva a lógica para somar as áreas de todas as formas fornecidas. A área de um quadrado é calculada pelo quadrado do comprimento. A área de um círculo é calculada por pi multiplicado pelo quadrado do raio.

      class AreaCalculator
      {
          protected $shapes;
      
          public function __construct($shapes = [])
          {
              $this->shapes = $shapes;
          }
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'Square')) {
                      $area[] = pow($shape->length, 2);
                  } elseif (is_a($shape, 'Circle')) {
                      $area[] = pi() * pow($shape->radius, 2);
                  }
              }
      
              return array_sum($area);
          }
      
          public function output()
          {
              return implode('', [
                '',
                    'Sum of the areas of provided shapes: ',
                    $this->sum(),
                '',
            ]);
          }
      }
      

      Para usar a classe AreaCalculator, será necessário criar uma instância da classe, passar uma matriz de formas e exibir o resultado no final da página.

      Aqui está um exemplo com uma coleção de três formas:

      • um círculo com um raio de 2
      • um quadrado com um comprimento de 5
      • um segundo quadrado com um comprimento de 6
      $shapes = [
        new Circle(2),
        new Square(5),
        new Square(6),
      ];
      
      $areas = new AreaCalculator($shapes);
      
      echo $areas->output();
      

      O problema com o método de saída é que o AreaCalculator manuseia a lógica para gerar os dados.

      Considere um cenário onde o resultado deve ser convertido em outro formato, como o JSON.

      Toda a lógica seria manuseada pela classe AreaCalculator. Isso violaria o princípio da responsabilidade única. A classe AreaCalculator deve estar preocupada somente com a soma das áreas das formas fornecidas. Ela não deve se importar se o usuário quer JSON ou HTML.

      Para resolver isso, crie uma classe separada chamada SumCalculatorOutputter e use essa nova classe para lidar com a lógica necessária para gerar os dados para o usuário:

      class SumCalculatorOutputter
      {
          protected $calculator;
      
          public function __constructor(AreaCalculator $calculator)
          {
              $this->calculator = $calculator;
          }
      
          public function JSON()
          {
              $data = [
                'sum' => $this->calculator->sum(),
            ];
      
              return json_encode($data);
          }
      
          public function HTML()
          {
              return implode('', [
                '',
                    'Sum of the areas of provided shapes: ',
                    $this->calculator->sum(),
                '',
            ]);
          }
      }
      

      A classe SumCalculatorOutputter funcionaria da seguinte forma:

      $shapes = [
        new Circle(2),
        new Square(5),
        new Square(6),
      ];
      
      $areas = new AreaCalculator($shapes);
      $output = new SumCalculatorOutputter($areas);
      
      echo $output->JSON();
      echo $output->HTML();
      

      Agora, a lógica necessária para gerar os dados para o usuário é manuseada pela classe SumCalculatorOutputter.

      Isso satisfaz o princípio da responsabilidade única.

      Princípio do aberto-fechado

      O Princípio do aberto-fechado (S.R.P.) declara:

      Os objetos ou entidades devem estar abertos para extensão, mas fechados para modificação.

      Isso significa que uma classe deve ser extensível sem que seja modificada.

      Vamos revisitar a classe AreaCalculator e focar no método sum(soma):

      class AreaCalculator
      {
          protected $shapes;
      
          public function __construct($shapes = [])
          {
              $this->shapes = $shapes;
          }
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'Square')) {
                      $area[] = pow($shape->length, 2);
                  } elseif (is_a($shape, 'Circle')) {
                      $area[] = pi() * pow($shape->radius, 2);
                  }
              }
      
              return array_sum($area);
          }
      }
      

      Considere um cenário onde o usuário deseja a sum de formas adicionais, como triângulos, pentágonos, hexágonos, etc. Seria necessário editar constantemente este arquivo e adicionar mais blocos de if/else. Isso violaria o princípio do aberto-fechado.

      Uma maneira de tornar esse método sum melhor é remover a lógica para calcular a área de cada forma do método da classe AreaCalculator e anexá-la à classe de cada forma.

      Aqui está o método area definido em Square:

      class Square
      {
          public $length;
      
          public function __construct($length)
          {
              $this->length = $length;
          }
      
          public function area()
          {
              return pow($this->length, 2);
          }
      }
      

      E aqui está o método area definido em Circle:

      class Circle
      {
          public $radius;
      
          public function construct($radius)
          {
              $this->radius = $radius;
          }
      
          public function area()
          {
              return pi() * pow($shape->radius, 2);
          }
      }
      

      O método sum para AreaCalculator pode então ser reescrito como:

      class AreaCalculator
      {
          // ...
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  $area[] = $shape->area();
              }
      
              return array_sum($area);
          }
      }
      

      Agora, é possível criar outra classe de formas e a passar ao calcular a soma sem quebrar o código.

      No entanto, outro problema surge. Como saber que o objeto passado para o AreaCalculator é na verdade uma forma ou se a forma possui um método chamado area?

      Programar em uma interface é uma parte integral do SOLID.

      Crie uma ShapeInterface que suporte area:

      interface ShapeInterface
      {
          public function area();
      }
      

      Modifique suas classes de formas para implement (implementar) a ShapeInterface.

      Aqui está a atualização para Square:

      class Square implements ShapeInterface
      {
          // ...
      }
      

      E aqui está a atualização para Circle:

      class Circle implements ShapeInterface
      {
          // ...
      }
      

      No método sum para AreaCalculator, verifique se as formas fornecidas são na verdade instâncias de ShapeInterface; caso contrário, lance uma exceção:

       class AreaCalculator
      {
          // ...
      
          public function sum()
          {
              foreach ($this->shapes as $shape) {
                  if (is_a($shape, 'ShapeInterface')) {
                      $area[] = $shape->area();
                      continue;
                  }
      
                  throw new AreaCalculatorInvalidShapeException();
              }
      
              return array_sum($area);
          }
      }
      

      Isso satisfaz o princípio do aberto-fechado.

      Princípio da substituição de Liskov

      O Princípio da substituição de Liskov declara:

      Seja q(x) uma propriedade demonstrável sobre objetos de x do tipo T. Então q(y) deve ser demonstrável para objetos y do tipo S onde S é um subtipo de T.

      Isso significa que cada subclasse ou classe derivada deve ser substituível pela classe sua classe base ou pai.

      Analisando novamente a classe de exemplo AreaCalculator, considere uma nova classe VolumeCalculator que estende a classe AreaCalculator:

      class VolumeCalculator extends AreaCalculator
      {
          public function construct($shapes = [])
          {
              parent::construct($shapes);
          }
      
          public function sum()
          {
              // logic to calculate the volumes and then return an array of output
              return [$summedData];
          }
      }
      

      Lembre-se que a classe SumCalculatorOutputter se assemelha a isto:

      class SumCalculatorOutputter {
          protected $calculator;
      
          public function __constructor(AreaCalculator $calculator) {
              $this->calculator = $calculator;
          }
      
          public function JSON() {
              $data = array(
                  'sum' => $this->calculator->sum();
              );
      
              return json_encode($data);
          }
      
          public function HTML() {
              return implode('', array(
                  '',
                      'Sum of the areas of provided shapes: ',
                      $this->calculator->sum(),
                  ''
              ));
          }
      }
      

      Se você tentar executar um exemplo como este:

      $areas = new AreaCalculator($shapes);
      $volumes = new VolumeCalculator($solidShapes);
      
      $output = new SumCalculatorOutputter($areas);
      $output2 = new SumCalculatorOutputter($volumes);
      

      Quando chamar o método HTML no objeto $output2, você irá obter um erro E_NOTICE informando uma conversão de matriz em string.

      Para corrigir isso, em vez de retornar uma matriz do método de soma de classe VolumeCalculator, retorne $summedData:

      class VolumeCalculator extends AreaCalculator
      {
          public function construct($shapes = [])
          {
              parent::construct($shapes);
          }
      
          public function sum()
          {
              // logic to calculate the volumes and then return a value of output
              return $summedData;
          }
      }
      

      O $summedData pode ser um float, duplo ou inteiro.

      Isso satisfaz o princípio da substituição de Liskov.

      Princípio da segregação de interfaces

      O Princípio da segregação de interfaces declara:

      Um cliente nunca deve ser forçado a implementar uma interface que ele não usa, ou os clientes não devem ser forçados a depender de métodos que não usam.

      Ainda utilizando o exemplo anterior do ShapeInterface, você precisará suportar as novas formas tridimensionais Cuboid e Spheroid, e essas formas também precisarão ter o volume calculado.

      Vamos considerar o que aconteceria se você modificasse a ShapeInterface para adicionar outro contrato:

      interface ShapeInterface
      {
          public function area();
      
          public function volume();
      }
      

      Agora, qualquer forma criada deve implementar o método volume, mas você sabe que os quadrados são formas planas que não têm volume, de modo que essa interface forçaria a classe Square a implementar um método sem utilidade para ela.

      Isso violaria o princípio da segregação de interfaces. Ao invés disso, você poderia criar outra interface chamada ThreeDimensionalShapeInterface que possui o contrato volume e as formas tridimensionais poderiam implementar essa interface:

      interface ShapeInterface
      {
          public function area();
      }
      
      interface ThreeDimensionalShapeInterface
      {
          public function volume();
      }
      
      class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface
      {
          public function area()
          {
              // calculate the surface area of the cuboid
          }
      
          public function volume()
          {
              // calculate the volume of the cuboid
          }
      }
      

      Essa é uma abordagem muito mais vantajosa, mas uma armadilha a ser observada é quando sugerir o tipo dessas interfaces. Ao invés de usar uma ShapeInterface ou uma ThreeDimensionalShapeInterface, você pode criar outra interface, talvez ManageShapeInterface, e implementá-la tanto nas formas planas quanto tridimensionais.

      Dessa forma, é possível ter uma única API para gerenciar todas as formas:

      interface ManageShapeInterface
      {
          public function calculate();
      }
      
      class Square implements ShapeInterface, ManageShapeInterface
      {
          public function area()
          {
              // calculate the area of the square
          }
      
          public function calculate()
          {
              return $this->area();
          }
      }
      
      class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface, ManageShapeInterface
      {
          public function area()
          {
              // calculate the surface area of the cuboid
          }
      
          public function volume()
          {
              // calculate the volume of the cuboid
          }
      
          public function calculate()
          {
              return $this->area();
          }
      }
      

      Agora, na classe AreaCalculator, substitua a chamada do método area por calculate e verifique se o objeto é uma instância da ManageShapeInterface e não da ShapeInterface.

      Isso satisfaz o princípio da segregação de interfaces.

      Princípio da inversão de dependência

      O princípio da inversão de dependência declara:

      As entidades devem depender de abstrações, não de implementações. Ele declara que o módulo de alto nível não deve depender do módulo de baixo nível, mas devem depender de abstrações.

      Esse princípio permite a desestruturação.

      Aqui está um exemplo de um PasswordReminder que se conecta a um banco de dados MySQL:

      class MySQLConnection
      {
          public function connect()
          {
              // handle the database connection
              return 'Database connection';
          }
      }
      
      class PasswordReminder
      {
          private $dbConnection;
      
          public function __construct(MySQLConnection $dbConnection)
          {
              $this->dbConnection = $dbConnection;
          }
      }
      

      Primeiramente, o MySQLConnection é o módulo de baixo nível, enquanto o PasswordReminder é de alto nível. No entanto, de acordo com a definição de D em SOLID, que declara Dependa de abstrações e não de implementações, Esse trecho de código acima viola esse princípio, uma vez que a classe PasswordReminder está sendo forçada a depender da classe MySQLConnection.

      Mais tarde, se você alterasse o mecanismo do banco de dados, também teria que editar a classe PasswordReminder e isso violaria o princípio do aberto-fechado.

      A classe PasswordReminder não deve se importar com qual banco de dados seu aplicativo usa. Para resolver esses problemas, programe em uma interface, uma vez que os módulos de alto e baixo nível devem depender de abstrações:

      interface DBConnectionInterface
      {
          public function connect();
      }
      

      A interface possui um método de conexão e a classe MySQLConnection implementa essa interface. Além disso, em vez de sugerir o tipo diretamente da classe MySQLConnection no construtor do PasswordReminder, você sugere o tipo de DBConnectionInterface. Sendo assim, independentemente do tipo de banco de dados que seu aplicativo usa, a classe PasswordReminder poderá se conectar ao banco de dados sem problemas e o princípio do aberto-fechado não será violado.

      class MySQLConnection implements DBConnectionInterface
      {
          public function connect()
          {
              // handle the database connection
              return 'Database connection';
          }
      }
      
      class PasswordReminder
      {
          private $dbConnection;
      
          public function __construct(DBConnectionInterface $dbConnection)
          {
              $this->dbConnection = $dbConnection;
          }
      }
      

      Esse código estabelece que tanto os módulos de alto quanto de baixo nível dependem de abstrações.

      Conclusão

      Neste artigo, os cinco princípios do Código SOLID foram-lhe apresentados. Projetos que aderem aos princípios SOLID podem ser compartilhados com colaboradores, estendidos, modificados, testados e refatorados com menos complicações.

      Continue seu aprendizado lendo sobre outras práticas para o desenvolvimento de software ágil e adaptativo.



      Source link