One place for hosting & domains

      Design

      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

      S.O.L.I.D: The First 5 Principles of Object Oriented Design


      S.O.L.I.D is an acronym for the first five object-oriented design(OOD)** principles** by Robert C. Martin, popularly known as Uncle Bob.

      These principles, when combined together, make it easy for a programmer to develop software that are easy to maintain and extend. They also make it easy for developers to avoid code smells, easily refactor code, and are also a part of the agile or adaptive software development.

      S.O.L.I.D stands for:

      Let’s look at each principle individually to understand why S.O.L.I.D can help make us better developers.

      Single-responsibility Principle

      S.R.P for short – this principle states that:

      A class should have one and only one reason to change, meaning that a class should have only one job.

      For example, say we have some shapes and we wanted to sum all the areas of the shapes. Well this is pretty simple right?

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

      First, we create our shapes classes and have the constructors setup the required parameters. Next, we move on by creating the AreaCalculator class and then write up our logic to sum up the areas of all provided shapes.

      class AreaCalculator {
      
          protected $shapes;
      
          public function __construct($shapes = array()) {
              $this->shapes = $shapes;
          }
      
          public function sum() {
              // logic to sum the areas
          }
      
          public function output() {
              return implode('', array(
                  "",
                      "Sum of the areas of provided shapes: ",
                      $this->sum(),
                  ""
              ));
          }
      }
      

      To use the AreaCalculator class, we simply instantiate the class and pass in an array of shapes, and display the output at the bottom of the page.

      $shapes = array(
          new Circle(2),
          new Square(5),
          new Square(6)
      );
      
      $areas = new AreaCalculator($shapes);
      
      echo $areas->output();
      

      The problem with the output method is that the AreaCalculator handles the logic to output the data. Therefore, what if the user wanted to output the data as json or something else?

      All of that logic would be handled by the AreaCalculator class, this is what SRP frowns against; the AreaCalculator class should only sum the areas of provided shapes, it should not care whether the user wants json or HTML.

      So, to fix this you can create an SumCalculatorOutputter class and use this to handle whatever logic you need to handle how the sum areas of all provided shapes are displayed.

      The SumCalculatorOutputter class would work like this:

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

      Now, whatever logic you need to output the data to the user is now handled by the SumCalculatorOutputter class.

      Open-closed Principle

      Objects or entities should be open for extension, but closed for modification.

      This simply means that a class should be easily extendable without modifying the class itself. Let’s take a look at the AreaCalculator class, especially it’s sum method.

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

      If we wanted the sum method to be able to sum the areas of more shapes, we would have to add more if/else blocks and that goes against the Open-closed principle.

      A way we can make this sum method better is to remove the logic to calculate the area of each shape out of the sum method and attach it to the shape’s class.

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

      The same thing should be done for the Circle class, an area method should be added. Now, to calculate the sum of any shape provided should be as simple as:

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

      Now we can create another shape class and pass it in when calculating the sum without breaking our code. However, now another problem arises, how do we know that the object passed into the AreaCalculator is actually a shape or if the shape has a method named area?

      Coding to an interface is an integral part of S.O.L.I.D, a quick example is we create an interface, that every shape implements:

      interface ShapeInterface {
          public function area();
      }
      
      class Circle implements ShapeInterface {
          public $radius;
      
          public function __construct($radius) {
              $this->radius = $radius;
          }
      
          public function area() {
              return pi() * pow($this->radius, 2);
          }
      }
      

      In our AreaCalculator sum method we can check if the shapes provided are actually instances of the ShapeInterface, otherwise we throw an exception:

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

      Liskov substitution principle

      Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

      All this is stating is that every subclass/derived class should be substitutable for their base/parent class.

      Still making use of out AreaCalculator class, say we have a VolumeCalculator class that extends the AreaCalculator class:

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

      In the SumCalculatorOutputter class:

      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(),
                  ''
              ));
          }
      }
      

      If we tried to run an example like this:

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

      The program does not squawk, but when we call the HTML method on the $output2 object we get an E_NOTICE error informing us of an array to string conversion.

      To fix this, instead of returning an array from the VolumeCalculator class sum method, you should simply:

      public function sum() {
          // logic to calculate the volumes and then return and array of output
          return $summedData;
      }
      

      The summed data as a float, double or integer.

      Interface segregation principle

      A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

      Still using our shapes example, we know that we also have solid shapes, so since we would also want to calculate the volume of the shape, we can add another contract to the ShapeInterface:

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

      Any shape we create must implement the volume method, but we know that squares are flat shapes and that they do not have volumes, so this interface would force the Square class to implement a method that it has no use of.

      ISP says no to this, instead you could create another interface called SolidShapeInterface that has the volume contract and solid shapes like cubes e.t.c can implement this interface:

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

      This is a much better approach, but a pitfall to watch out for is when type-hinting these interfaces, instead of using a ShapeInterface or a SolidShapeInterface.

      You can create another interface, maybe ManageShapeInterface, and implement it on both the flat and solid shapes, this way you can easily see that it has a single API for managing the shapes. For example:

      interface ManageShapeInterface {
          public function calculate();
      }
      
      class Square implements ShapeInterface, ManageShapeInterface {
          public function area() { /Do stuff here/ }
      
          public function calculate() {
              return $this->area();
          }
      }
      
      class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {
          public function area() { /Do stuff here/ }
          public function volume() { /Do stuff here/ }
      
          public function calculate() {
              return $this->area() + $this->volume();
          }
      }
      

      Now in AreaCalculator class, we can easily replace the call to the area method with calculate and also check if the object is an instance of the ManageShapeInterface and not the ShapeInterface.

      Dependency Inversion principle

      The last, but definitely not the least states that:

      Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.

      This might sound bloated, but it is really easy to understand. This principle allows for decoupling, an example that seems like the best way to explain this principle:

      class PasswordReminder {
          private $dbConnection;
      
          public function __construct(MySQLConnection $dbConnection) {
              $this->dbConnection = $dbConnection;
          }
      }
      

      First the MySQLConnection is the low level module while the PasswordReminder is high level, but according to the definition of D in S.O.L.I.D. which states that Depend on Abstraction not on concretions, this snippet above violates this principle as the PasswordReminder class is being forced to depend on the MySQLConnection class.

      Later if you were to change the database engine, you would also have to edit the PasswordReminder class and thus violates Open-close principle.

      The PasswordReminder class should not care what database your application uses, to fix this again we “code to an interface”, since high level and low level modules should depend on abstraction, we can create an interface:

      interface DBConnectionInterface {
          public function connect();
      }
      

      The interface has a connect method and the MySQLConnection class implements this interface, also instead of directly type-hinting MySQLConnection class in the constructor of the PasswordReminder, we instead type-hint the interface and no matter the type of database your application uses, the PasswordReminder class can easily connect to the database without any problems and OCP is not violated.

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

      According to the little snippet above, you can now see that both the high level and low level modules depend on abstraction.

      Conclusion

      S.O.L.I.D might seem to be a bit too abstract at first, but with each real-world application of S.O.L.I.D. principles, the benefits of adherence to its guidelines will become more apparent. Code that follows S.O.L.I.D. principles can more easily be shared with collaborators, extended, modified, tested, and refactored without any problems.



      Source link

      Module Design Pattern in JavaScript


      While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or
      edited it to ensure you have an error-free learning experience. It’s on our list, and we’re working on it!
      You can help us out by using the “report an issue” button at the bottom of the tutorial.


      Part of the Series:
      JavaScript Design Patterns

      Every developer strives to write maintainable, readable, and reusable code. Code structuring becomes more important as applications become larger. Design patterns prove crucial to solving this challenge – providing an organization structure for common issues in a particular circumstance.

      The design pattern below is only one of many useful patterns that can help you level up as a JavaScript developer. For the full set, see JavaScript Design Patterns.

      JavaScript modules are the most prevalently used design patterns for keeping particular pieces of code independent of other components. This provides loose coupling to support well-structured code.

      For those that are familiar with object-oriented languages, modules are JavaScript “classes”. One of the many advantages of classes is encapsulation – protecting states and behaviors from being accessed from other classes. The module pattern allows for public and private (plus the lesser-know protected and privileged) access levels.

      Modules should be Immediately-Invoked-Function-Expressions (IIFE) to allow for private scopes – that is, a closure that protect variables and methods (however, it will return an object instead of a function). This is what it looks like:

      (function() {
      
          // declare private variables and/or functions
      
          return {
              // declare public variables and/or functions
          }
      
      })();
      

      Here we instantiate the private variables and/or functions before returning our object that we want to return. Code outside of our closure is unable to access these private variables since it is not in the same scope. Let’s take a more concrete implementation:

      var HTMLChanger = (function() {
          var contents="contents"
      
          var changeHTML = function() {
          var element = document.getElementById('attribute-to-change');
          element.innerHTML = contents;
          }
      
          return {
          callChangeHTML: function() {
              changeHTML();
              console.log(contents);
          }
          };
      
      })();
      
      HTMLChanger.callChangeHTML();       // Outputs: 'contents'
      console.log(HTMLChanger.contents);  // undefined
      

      Notice that callChangeHTML binds to the returned object and can be referenced within the HTMLChanger namespace. However, when outside the module, contents are unable to be referenced.

      Revealing Module Pattern

      A variation of the module pattern is called the Revealing Module Pattern. The purpose is to maintain encapsulation and reveal certain variables and methods returned in an object literal. The direct implementation looks like this:

      var Exposer = (function() {
          var privateVariable = 10;
      
          var privateMethod = function() {
          console.log('Inside a private method!');
          privateVariable++;
          }
      
          var methodToExpose = function() {
          console.log('This is a method I want to expose!');
          }
      
          var otherMethodIWantToExpose = function() {
          privateMethod();
          }
      
          return {
              first: methodToExpose,
              second: otherMethodIWantToExpose
          };
      })();
      
      Exposer.first();        // Output: This is a method I want to expose!
      Exposer.second();       // Output: Inside a private method!
      Exposer.methodToExpose; // undefined
      

      Although this looks much cleaner, an obvious disadvantage is unable to reference the private methods. This can pose unit testing challenges. Similarly, the public behaviors are non-overridable.



      Source link