Как интерфейсы нарушают зависимости классов? - PullRequest
0 голосов
/ 31 марта 2019

Я читаю Чистую архитектуру - Боб Мартин. Он говорит о нарушении зависимостей с помощью интерфейса. Например. Класс B использует класс A. Таким образом, класс B зависит от класса A (B ---> A). Мы можем добавить интерфейс и иметь B, зависящий от интерфейса, а также иметь A, зависящий от интерфейса (A ---> I <---- B). </p>

Чего я не понимаю, так это того, что если у класса A есть закрытые переменные-члены, которые используются в функциях, которые нужны B. Разве мне не пришлось бы переписывать тот же код в B, что и у A? Кроме того, не будет ли это просто дубликатом кода?

Вот пример.

class Car {
    private String color;
    private Integer numberOfTires;
    [...]
    public void printCar()
    {
        System.out.print("Color: " + color);
        System.out.print("Number of tires: " + numberOfTires);
    }
}

class Inventory{
    private Car car;
    private Truck truck; // left out for brevity
    public void printCar()
    {
        car.printCar();
    }

    public void printTruck()
    {
        truck.printTruck();
    }
}

Я не понимаю, как интерфейс может помочь с этой зависимостью.

Ответы [ 5 ]

1 голос
/ 01 апреля 2019

@ Кауэ Сильвейра привел хороший пример, но упустил упомянуть еще один важный аспект.

В общем, смысл интерфейсов состоит не в том, чтобы иметь меньше классов.Я полагаю, вы знакомы с терминами сцепления и сплоченности.Вы всегда хотите иметь код, который свободно связан и очень сплочен.Это означает, что вы не хотите, чтобы классы были зависимы друг от друга (связаны), но чтобы они разделяли некоторую логику в форме наследования, полиморфизма и т. Д. Эти концепции являются одними из основных столпов качественного проектирования ObjectOriented.Обязательно стоит прочитать об этом, если вы не знакомы с этими темами.

Если вернуться к вопросу, то у вас, как правило, будет какой-то дубликат, если вы имеете дело со сложной логикой, которая имеет много особых случаев.Но такие проблемы в большей степени связаны с другими шаблонами и принципами проектирования, которые предназначены исключительно для соблюдения принципа СУХОЙ и разрешения ситуаций таким образом, который обобщает подход к решению.

Основная идея интерфейсов - установить логическую структуру для классов, которая способствует единообразию манипулирования объектами.

Представьте себе систему фондовой биржи.Этот интерфейс имеет единственный метод с именем execute, который будет выполнять некоторую логику приложения.

public interface Order{
     void execute();
}

Другими классами, которые могут реализовать этот интерфейс, могут быть Buy и Sell.Это выглядело бы примерно так:

public class Buy implement Order{
     @Override
     public void execute(){
          //TODO: Some logic
     }
}

Теперь и Buy, и Sell будут иметь похожий код, возможно, даже какой-то дубликат, но что более важно, - когда они реализуют один и тот же интерфейс, вы можете обрабатывать их в Единый путь .Вы можете иметь некоторый класс StockManager, который поместит и ордера на покупку, и ордера на продажу в Quee<Order>.Отсюда можно сделать вывод, что при работе с таким Quee у вас будет возможность вызывать метод execute() для любой реализации интерфейса Order.

Построение поверх предыдущего аргумента с использованием интерфейсов и некоторых другихфреймворки наподобие Spring, вы получаете возможность делать авто-электромонтаж.Это значительно уменьшает зависимость от классов реализации более низкого уровня, давая вам возможность изменять низкоуровневые классы, не влияя на обработчики верхнего уровня.Этот тип разработки приложений является обычной практикой в ​​SOA (сервис-ориентированная архитектура).

0 голосов
/ 01 апреля 2019

Роберт говорит о нарушении зависимостей и, конечно, вы можете даже нарушить зависимость между Inventory и Car, но я думаю, что это не принесет вам большой пользы, потому что вы не пересекаете архитектурную границу.

Было бы лучше разбить статическую зависимость на System.out, потому что IO - это архитектурная граница, и вы можете затем заменить способ печати Car или Inventory, что очень полезно для тестов.

Прерывание статической зависимости

class Car {
    private String color;
    private Integer numberOfTires;

    private PrintStream output = System.out;

    void setOutput(PrintStream output){
        this.output = Objects.requireNotNull(output);
    }

    public void printCar() {
        output.print("Color: " + color);
        output.print("Number of tires: " + numberOfTires);
    }
}

Теперь вы можете заменить вывод в тестах, чтобы получить результат вывода.

Применение принципа разделения интерфейса

public interface Output {
     public void print(String msg);
}


class Car {
    private String color;
    private Integer numberOfTires;

    private Output output = (msg) -> System.out.println(msg);

    void setOutput(Output output){
        this.output = Objects.requireNotNull(output);
    }

    public void printCar() {
        output.print("Color: " + color);
        output.print("Number of tires: " + numberOfTires);
    }
}

Теперь ваша единственная зависимость - это интерфейс Output, который еще проще заменить или смоделировать в тестах.

Это небольшое изменение делает ваш Car независимым от конкретной системы вывода или, как сказал бы Роберт a detail. Я также могу представить реализацию JTextAreaOutput, чтобы вывод отображался в графическом интерфейсе.

Чистая архитектура говорит нам, что IO - это деталь, и наш бизнес-код не должен от нее зависеть. Кажется, что Car и Inventory - это ваш бизнес-код, и поэтому я показал вам, как отделить его от конкретной системы вывода - деталь.

    +-----+    uses     +--------+     implements   +--------------+ 
    | Car |  -------->  | Output |  <-------------  | SystemOutput |
    +-----+             +--------+                  +--------------+

             ---------> control flow  ------------->

Мы также применили принцип Dependency Inversion, поскольку зависимость исходного кода указывает на поток управления.

0 голосов
/ 01 апреля 2019

Предположительно, ваш Inventory - это «инвентарь транспортных средств», а не «инвентарь одного автомобиля и одного грузовика».

Имея это в виду, возможно, это поможет:

  • Car является Vehicle
  • Truck является Vehicle
  • Inventory зависит от Vehicle - не от Car или Truck, он ничего не знает об этих типах
  • Vehicle::printDetails реализовано Car и Truck

.

public class Scratch4 {
    public static void main(String args[]) throws Exception {
        Car car = new Car("Blue", 4);
        Truck truck = new Truck();

        Inventory inventory = new Inventory();
        inventory.addVehicle(car);
        inventory.addVehicle(truck);

        inventory.printVehicleDetails();
    }
}

interface Vehicle {
    void printDetails();
}

class Car implements Vehicle {
    private String color;
    private Integer numberOfTires;

    public Car(String color, Integer numberOfTires) {
        this.color = color;
        this.numberOfTires = numberOfTires;
    }

    public void printDetails() {
        System.out.println("Color: " + color);
        System.out.println("Number of tires: " + numberOfTires);
        System.out.println();
    }
}

class Truck implements Vehicle {

    @Override
    public void printDetails() {
        System.out.println("Some kind of truck");
        System.out.println();
    }

}

class Inventory {
    private List<Vehicle> vehicles = new ArrayList<>();;

    public void addVehicle(Vehicle vehicle) {
        vehicles.add(vehicle);
    }

    public void printVehicleDetails() {
        vehicles.forEach(Vehicle::printDetails);
    }
}

Урожайность

Color: Blue
Number of tires: 4

Some kind of truck
0 голосов
/ 01 апреля 2019

Допустим, у нас есть класс Bike and Transportation, подобный этому:

class Bike{
   public void ride(){
      System.out.println("Riding from point A to B");
   }
}

class Transportation{
   public void commute(Bike b){
      b.ride();
   }
}

Теперь класс транспорта здесь тесно связан с байком. Вы не можете добраться на любом другом транспортном средстве, даже если оно доступно. Также любое изменение в классе Bike напрямую повлияет на поведение транспорта.

Вместо приведенного выше кода, скажем, был интерфейс под названием Vehicle, подобный этому:

public interface Vehicle{
    void move();
}

class Bike implements Vehicle{
    public void move(){
        System.out.println("Riding on Bike");
    }
}

class Transportation{
    public void commute(Vehicle v){
        v.move();
    }
}

Здесь интерфейс Vehicle гарантирует, что какой бы класс его ни реализовывал, он будет иметь метод move () с точно такой же сигнатурой, несмотря ни на что. Кроме того, метод commute () теперь принимает объект Vehicle. Это позволяет позже создать объект класса Transportation с другой реализацией класса Vehicle без класса Transportation, даже не подозревая об этом. Пример:

class Car implements Vehicle{
    public void move(){
      System.out.println("Moving in car");
    }
} 

Car c = new Car();
new Transportation(c).commute();

Bike b = new Bike();
new Transportation(b).commute();

Использование интерфейса Vehicle устранило зависимость Transportation от конкретных классов. Кроме того, любой класс, которому необходимо использовать Transportation, должен реализовывать интерфейс Vehicle с методом move (). Таким образом, устанавливается Договор между любым транспортным средством и транспортным средством.

0 голосов
/ 31 марта 2019

Во время выполнения в производстве B будет использоваться реальный класс A (или другой класс, реализующий тот же интерфейс), поэтому нет необходимости дублировать код.

Использование интерфейса позволяет использовать другую реализацию для других целей.например, использование легкого в памяти фальшивого класса A для модульного теста класса B, поэтому избегая тяжелого реального класса A.

Вы (можете только) сломать «статический,построить время "зависимость.Зависимость «динамического времени выполнения» (всегда) сохраняется.

В коде:

public interface AInterface {}
public class A implements AInterface {}
public class B {
  AInterface a;
  public B(AInterface a) {
    this.a = a;
  }
}
public class Main {
  B b = B(A());
}
public class AFake implements AInterface {}
public class BTest {
  B b = B(AFake())
}

В этом примере используется конструкторВнедрение зависимостей: https://en.wikipedia.org/wiki/Dependency_injection#Constructor_injection_comparison.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...