O livro “Abordagem Orientada a Objetos. 5ª int. ed. "

imagemA Programação Orientada a Objetos (OOP) está no centro das linguagens C ++, Java, C #, Visual Basic .NET, Ruby, Objective-C e até Swift. Eles não podem prescindir de objetos de tecnologia da web, porque usam JavaScript, Python e PHP.

É por isso que Matt Weissfeld aconselha o desenvolvimento de uma maneira de pensar orientada a objetos e só então prossegue com o desenvolvimento orientado a objetos em uma linguagem de programação específica.

Este livro foi escrito pelo desenvolvedor para desenvolvedores e permite que você escolha as melhores abordagens para resolver problemas específicos. Você aprenderá como aplicar corretamente a herança e a composição, entender a diferença entre agregação e associação e parar de confundir a interface e a implementação.

As tecnologias de programação estão constantemente mudando e se desenvolvendo, mas os conceitos orientados a objetos são independentes de plataforma e permanecem consistentemente eficazes. Esta publicação foca nos fundamentos fundamentais do OOP: padrões de design, dependências e princípios do SOLID que tornarão seu código compreensível, flexível e bem mantido.

Princípios de projeto orientado a objeto do SOLID


1. SRP: princípio de responsabilidade exclusiva


O princípio da responsabilidade exclusiva afirma que apenas um motivo é necessário para fazer alterações em uma classe. Cada módulo de classe e programa deve ter uma tarefa em prioridade. Portanto, você não deve introduzir métodos que possam causar alterações na classe por mais de um motivo. Se a descrição da classe contiver a palavra "e", o princípio do SRP poderá ser violado. Em outras palavras, cada módulo ou classe deve ser responsável por uma parte da funcionalidade do software e essa responsabilidade deve ser totalmente encapsulada na classe.

Criar uma hierarquia de figuras é um dos exemplos clássicos que ilustram a herança.Este exemplo é freqüentemente encontrado no ensino, e eu a uso ao longo deste capítulo (assim como ao longo do livro). Neste exemplo, a classe Circle herda atributos da classe Shape. A classe Shape fornece o método abstrato calcArea () como um contrato para uma subclasse. Cada classe que herda de Shape deve ter sua própria implementação do método calcArea ():

abstract class Shape{
     protected String name;
     protected double area;
     public abstract double calcArea();
}

Neste exemplo, a classe Circle, que herda da classe Shape, fornece sua implementação do método calcArea (), se necessário:

class Circle extends Shape{
     private double radius;

     public Circle(double r) {
           radius = r;
     }
     public double calcArea() {
           area = 3.14*(radius*radius) ;
           return (area);
     };
}

A terceira classe, CalculateAreas, calcula a área das várias formas contidas na matriz Shape. A matriz Shape tem tamanho ilimitado e pode conter várias formas, como quadrados e triângulos.

class CalculateAreas {
     Shape[] shapes;
     double sumTotal=0;
     public CalculateAreas(Shape[] sh) {
           this.shapes = sh;
     }
     public double sumAreas() {
           sumTotal=0;
           for (inti=0; i<shapes.length; i++) {
           sumTotal = sumTotal + shapes[i].calcArea() ;
           }
           return sumTotal ;
     }
     public void output() {
           System.out.printIn("Total of all areas = " + sumTotal);
     }
}

Observe que a classe CalculateAreas também lida com a saída do aplicativo, o que pode causar problemas. O comportamento de contagem de área e o comportamento de saída estão relacionados porque estão contidos na mesma classe.

Podemos verificar a funcionalidade desse código usando o aplicativo de teste apropriado TestShape:

public class TestShape {
      public static void main(String args[]) {

            System.out.printin("Hello World!");

            Circle circle = new Circle(1);

            Shape[] shapeArray = new Shape[1];
            shapeArray[0] = circle;

            CalculateAreas ca = new CalculateAreas(shapeArray) ;

            ca.sumAreas() ;
            ca.output();
      }
}

Agora que temos uma aplicação de teste à nossa disposição, podemos nos concentrar no problema do princípio de responsabilidade exclusiva. Novamente, o problema está na classe CalculateAreas e no fato de que essa classe contém comportamentos para adicionar áreas de várias formas e também para gerar dados.

A questão fundamental (e, de fato, o problema) é que, se você precisar alterar a funcionalidade do método output (), será necessário fazer alterações na classe CalculateAreas, independentemente de o método para calcular a área das formas ser alterado. Por exemplo, se queremos repentinamente enviar dados para o console HTML, e não para texto sem formatação, precisaremos recompilar e incorporar novamente o código, o que adiciona a área das figuras. Tudo porque a responsabilidade está relacionada.

De acordo com o princípio de responsabilidade exclusiva, a tarefa é alterar um método que não afeta os outros métodos e não precisa ser recompilado. "A turma deve ter um, apenas um, motivo de mudança - a única responsabilidade que precisa ser mudada."

Para resolver esse problema, você pode colocar dois métodos em classes separadas, uma para a saída do console original e outra para a saída HTML:

class CaiculateAreas {;
     Shape[] shapes;
     double sumTotal=0;

     public CalculateAreas(Shape[] sh) {
           this.shapes = sh;
     }

     public double sumAreas() {
           sumTotal=0;

           for (inti=0; i<shapes.length; i++) {

                sumTotal = sumTotal + shapes[i].calcArea();

           }

                return sumTotal;
           }
}
class OutputAreas {
     double areas=0;
     public OutputAreas (double a) {
           this.areas = a;
     }

           public void console() {
           System.out.printin("Total of all areas = " + areas);
     }
     public void HTML() {
           System.out.printIn("<HTML>") ;
           System.out.printin("Total of all areas = " + areas);
           System.out.printin("</HTML>") ;
     }
}

A conclusão aqui é que agora você pode enviar uma conclusão em diferentes direções, dependendo da necessidade. Se você desejar adicionar a possibilidade de outro método de saída, por exemplo, JSON, poderá adicioná-lo à classe OutputAreas sem precisar fazer alterações na classe CalculateAreas. Como resultado, você pode redistribuir a classe CalculateAreas sem afetar outras classes de forma alguma.

2. OCP: princípio aberto / fechado


O princípio de abertura / proximidade afirma que você pode estender o comportamento de uma classe sem fazer alterações.

Vamos novamente prestar atenção ao exemplo com figuras. No código abaixo, há uma classe ShapeCalculator que pega um objeto Rectangle, calcula a área desse objeto e retorna valores. Esta é uma aplicação simples, mas funciona apenas com retângulos.

class Rectangle{
     protected double length;
     protected double width;

     public Rectangle(double 1, double w) {
           length = 1;
           width = w;
     };
}
class CalculateAreas {
     private double area;

     public double calcArea(Rectangle r) {

           area = r.length * r.width;

           return area;
     }
}
public class OpenClosed {
      public static void main(String args[]) {

            System.out.printin("Hello World");

            Rectangle r = new Rectangle(1,2);

            CalculateAreas ca = new CalculateAreas ();

            System.out.printin("Area = "+ ca.calcArea(r));
      }
}

O fato de esse aplicativo funcionar apenas no caso de retângulos leva a uma limitação que explica claramente o princípio de abertura / fechamento: se queremos adicionar a classe Circle à classe CalculateArea (altere o que faz), precisamos fazer alterações no próprio módulo. Obviamente, isso entra em conflito com o princípio de abertura / proximidade, que afirma que não devemos fazer alterações no módulo para alterar o que ele faz.

Para cumprir o princípio de abertura / proximidade, podemos retornar ao exemplo já testado com figuras, onde uma classe abstrata Shape é criada e diretamente as figuras herdadas da classe Shape, que possui um método abstrato getArea ().

No momento, você pode adicionar quantas classes diferentes forem necessárias, sem a necessidade de fazer alterações diretamente na classe Shape (por exemplo, na classe Circle). Agora podemos dizer que a classe Shape está fechada.

O código abaixo fornece uma implementação da solução para retângulos e círculos e permite criar um número ilimitado de formas:

abstract class Shape {
      public abstract double getArea() ;
}
class Rectangle extends Shape
{

      protected double length;
      protected double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      };
      public double getArea() {
            return length*width;
      }

}
class Circle extends Shape
{
      protected double radius;

      public Circle(double r) {
            radius = r;
      };
      public double getArea() {
            return radius*radius*3.14;
      }
}
class CalculateAreas {
      private double area;

      public double calcArea(Shape s) {
            area = s.getArea();
            return area;
      }
}

public class OpenClosed {
      public static void main(String args[]) {

            System.out.printiIn("Hello World") ;

            CalculateAreas ca = new CalculateAreas() ;

            Rectangle r = new Rectangle(1,2);

            System.out.printIn("Area = " + ca.calcArea(r));

            Circle c = new Circle(3);

            System.out.printIn("Area = " + ca.calcArea(c));
}
}

Vale ressaltar que, com essa implementação, o método CalculateAreas () não deve ser modificado ao criar uma nova instância da classe Shape.

Você pode dimensionar o código sem se preocupar com a existência do código anterior. O princípio de abertura / proximidade é que você deve estender o código usando subclasses para que a classe original não exija edições. No entanto, o próprio conceito de “extensão” é controverso em algumas discussões sobre os princípios do SOLID. Para ser franco, se preferimos composição ao invés de herança, como isso afeta o princípio de abertura / proximidade?

Ao cumprir um dos princípios do SOLID, o código pode atender aos critérios de outros princípios do SOLID. Por exemplo, ao projetar de acordo com o princípio de abertura / proximidade, o código pode atender aos requisitos do princípio de responsabilidade exclusiva.

3. LSP: princípio de substituição de Lisk


De acordo com o princípio de substituição de Liskov, o design deve fornecer a possibilidade de substituir qualquer instância da classe pai por uma instância de uma das classes filho. Se a classe pai puder executar qualquer tarefa, a classe filha também deverá poder.

Considere algum código que esteja correto à primeira vista, mas viole o princípio de substituição do Lisk. O código abaixo contém uma classe abstrata Shape genérica. A classe Rectangle, por sua vez, herda atributos da classe Shape e substitui seu método abstrato calcArea (). A classe Square, por sua vez, herda de Rectangle.

abstract class Shape{
      protected double area;

      public abstract double calcArea();
}
class Rectangle extends Shape{
      private double length;
      private double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      }
      public double calcArea() {
            area = length*width;
            return (area) ;
      };
}
class Square extends Rectangle{
      public Square(double s) {
            super(s, Ss);
      }
}

public class LiskovSubstitution {
      public static void main(String args[]) {

            System.out.printIn("Hello World") ;

            Rectangle r = new Rectangle(1,2);

            System.out.printin("Area = " + r.calcArea());

            Square s = new Square(2) ;

            System.out.printin("Area = " + s.calcArea());
      }
}

Até agora, tudo bem: o retângulo é uma instância da figura, então não há com o que se preocupar, pois o quadrado é uma instância do retângulo - e, novamente, tudo está correto, certo?

Agora vamos fazer uma pergunta filosófica: um quadrado ainda é um retângulo? Muitos responderão afirmativamente. Embora se possa supor que um quadrado é um caso especial de um retângulo, suas propriedades serão diferentes. Um retângulo é um paralelogramo (os lados opostos são os mesmos), como um quadrado. Ao mesmo tempo, o quadrado também é um losango (todos os lados são iguais), enquanto o retângulo não é. Portanto, existem diferenças.

Quando se trata de design orientado a objetos, o problema não é geometria. O problema é como exatamente criamos retângulos e quadrados. Aqui está o construtor para a classe Rectangle:

public Rectangle(double 1, double w) {
      length = 1;
      width = w;
}

Obviamente, o construtor requer dois parâmetros. No entanto, o construtor da classe Square requer apenas um, mesmo que a classe pai, Rectangle, exija dois.

class Square extends Rectangle{
      public Square(double s) {
      super(s, Ss);
}

De fato, a funcionalidade para calcular a área é um pouco diferente no caso de cada uma dessas duas classes. Ou seja, a classe Square imita um retângulo, passando o mesmo parâmetro duas vezes para o construtor. Pode parecer que essa solução alternativa é bastante adequada, mas na verdade pode enganar os desenvolvedores que acompanham o código, o que é bastante cheio de armadilhas quando acompanhado no futuro. Pelo menos isso é um problema e, provavelmente, uma decisão de design duvidosa. Quando um construtor chama outro, é uma boa ideia fazer uma pausa e reconsiderar a construção - talvez a classe filho não seja construída corretamente.

Como encontrar uma saída para esta situação? Simplificando, você não pode substituir uma classe Square por um retângulo. Portanto, Square não deve ser filho da classe Rectangle. Eles devem ser classes separadas.

abstract class Shape {
      protected double area;

      public abstract double calcArea();
}

class Rectangle extends Shape {

      private double length;
      private double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      }

      public double calcArea() {
            area = length*width;
            return (area);
      };
}

class Square extends Shape {
      private double side;

      public Square(double s) {
            side = s;
      }
      public double calcArea() {
            area = side*side;
            return (area);
      };
}

public class LiskovSubstitution {
      public static void main(String args[]) {

             System.out.printIn("Hello World") ;

             Rectangle r = new Rectangle(1,2);

             System.out.printIn("Area = " + r.calcArea());

             Square s = new Square(2) ;

             System.out.printIn("Area = " + s.calcArea());
      }
}

4. ISP: princípio de compartilhamento de interface


O princípio da separação de interfaces afirma que é melhor criar muitas interfaces pequenas do que várias grandes.

Neste exemplo, criamos uma única interface que inclui vários comportamentos para a classe Mammal, a saber eat () e makeNoise ():

interface IMammal {
     public void eat();
     public void makeNoise() ;
}
class Dog implements IMammal {
     public void eat() {
           System.out.printIn("Dog is eating");
     }
     public void makeNoise() {
           System.out.printIn("Dog is making noise");
     }
}
public class MyClass {
      public static void main(String args[]) {

            System.out.printIn("Hello World");

            Dog fido = new Dog();
            fido.eat();
            fido.makeNoise()
      }
}

Em vez de criar uma única interface para a classe Mammal, você precisa criar
interfaces separadas para todos os comportamentos:

interface IEat {
     public void eat();
}
interface IMakeNoise {
     public void makeNoise() ;
}
class Dog implements IEat, IMakeNoise {
     public void eat() {
           System.out.printIn("Dog is eating");
     }
     public void makeNoise() {
           System.out.printIn("Dog is making noise");
     }
}
public class MyClass {
      public static void main(String args[]) {

            System.out.printIn("Hello World") ;

            Dog fido = new Dog();
            fido.eat();
            fido.makeNoise();
      }
}

Separamos comportamentos da classe Mammal. Acontece que, em vez de criar uma única classe Mammal por herança (mais precisamente, interfaces), passamos ao design com base na composição, semelhante à estratégia usada no capítulo anterior.

Em poucas palavras, com essa abordagem, podemos criar instâncias da classe Mammal usando a composição, em vez de sermos forçados a usar comportamentos incorporados em uma única classe Mammal. Por exemplo, suponha que seja descoberto um mamífero que não come, mas absorve nutrientes através da pele. Se herdarmos da classe Mammal que contém o comportamento eat (), esse comportamento será redundante para o novo mamífero. Além disso, se todos os comportamentos forem definidos em interfaces únicas separadas, será elaborado a classe de cada mamífero exatamente como pretendido.

»Mais informações sobre o livro podem ser encontradas no site do editor
» Índice
» Trecho do

Habrozhitelami: desconto de 25% no cupom -POO

Após o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.

Source: https://habr.com/ru/post/undefined/


All Articles