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

Итак, что именно является тесно связанным и низко связанным кодом? Почему тесно связанный код с низким уровнем связности — это плохо? И как вы можете написать лучший код?

Чтобы ответить на этот вопрос, давайте посмотрим на сцепление и сцепление.

Связь

В объектно-ориентированном дизайне связь относится к степени прямого знания одного элемента о другом. Другими словами, как часто изменения в классе А вызывают соответствующие изменения в классе Б.

Давайте посмотрим на этот фрагмент кода:

public class Order {
  private CashPayment payment = new CashPayment();
  
  public void processPayment() {
    payment.process();
  }
}

public class CashPayment {
  public void process() {
    // Process logic
  }
}

Класс Order напрямую зависит от класса CashPayment, что затрудняет изменение класса CashPayment без воздействия на класс Order. В этом случае мы говорим, что класс Order тесно связан с классом CashPayment.

Чтобы сделать код слабо связанным, мы можем ввести абстракции в класс CashPayment. Вот обновленная версия кода:

public class Order {
  private PaymentType payment;
  
  public Order(PaymentType paymentType) {
    payment = paymentType;
  }

  public void processPayment() {
    payment.process();
  }
}

public interface PaymentType {
  public void process();
}

public class CashPayment implements PaymentType {
  public void process() {
    // Process logic
  }
}

В обновленном коде мы представили интерфейс PaymentType, который определяет метод process(). Класс Order теперь зависит от интерфейса PaymentType, а не от конкретного класса CashPayment. Используя интерфейс, мы отделяем класс Order от конкретной реализации класса CashPayment.

Это изменение обеспечивает большую гибкость. Это также позволяет классу Order работать с любым классом, реализующим интерфейс PaymentType. В нашем случае мы можем легко добавить класс CreditCardPayement, не изменяя класс Order.

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

Сплоченность

В объектно-ориентированном проектировании сплоченность относится к тому, как проектируется отдельный класс. Сплоченность тесно связана с обеспечением того, чтобы класс был разработан с единственной, четко сфокусированной целью. Другими словами, если материалы, сгруппированные в класс, имеют тенденцию быть схожими во многих аспектах, то говорят, что класс имеет высокую сплоченность.

Давайте посмотрим на этот фрагмент кода:

public class Circle {
  private double radius;

  public Circle(double radius) {
    this.radius = radius;
  }

  public double getArea() {
    return 3.14 * radius * radius;
  }

  public double getPerimeter() {
    return 2 * 3.14 * radius;
  }

  public String toString() {
    return "Circle (radius: " + radius + ")";
  }

  public void printDetails() {
    System.out.println("Shape: " + this.toString());
    System.out.println("Area: " + getArea());
    System.out.println("Perimeter: " + getPerimeter());
  }
}

Класс Circle имеет атрибут radius, а также методы вычисления площади, периметра и печати деталей окружности. Однако метод printDetails() нарушает принцип единой ответственности, совмещая вывод деталей с вычислением площади и периметра. Мы говорим, что наш код имеет низкую связность.

Чтобы улучшить связность, мы можем разделить обязанности, создав новый класс только для отображения деталей круга:

public class Circle {
  private double radius;

  public Circle(double radius) {
    this.radius = radius;
  }

  public double getArea() {
    return 3.14 * radius * radius;
  }

  public double getPerimeter() {
    return 2 * 3.14 * radius;
  }

  public String toString() {
    return "Circle (radius: " + radius + ")";
  }
}

public class CircleDetailsPrinter {
  private Circle circle;

  public CircleDetailsPrinter(Circle circle) {
    this.circle = circle;
  }

  public void printDetails() {
    System.out.println("Shape: " + circle.toString());
    System.out.println("Area: " + circle.getArea());
    System.out.println("Perimeter: " + circle.getPerimeter());
  }
}

В обновленном коде представлен класс CircleDetailsPrinter для обработки печати деталей круга. Класс Circle теперь делегирует ответственность за печать классу CircleDetailsPrinter.

Выделяя функции печати в отдельный класс, мы говорим, что наш код в высшей степени связан. У каждого класса есть одна обязанность, что делает код более понятным и простым в обслуживании.

Обратите внимание, что в этом примере сплоченность улучшается за счет разделения обязанностей между двумя классами. Однако важно установить баланс и избежать чрезмерного разделения классов. Цель состоит в том, чтобы сфокусировать классы с четкими обязанностями, не разбивая их без необходимости на слишком много маленьких блоков.

Хороший код обладает высокой связностью, поскольку изменения будут выполняться в соответствующем классе.

Теперь, когда мы поняли концепцию связности, давайте применим то, что мы узнали о связности, к последнему примеру. Мы видим, что класс CircleDetailsPrinter тесно связан с классом Circle. Давайте отделим класс CircleDetailsPrinter от конкретной реализации класса Circle:

public interface Shape {
  public double getArea();
  public double getPerimeter();
}

public class Circle implements Shape {
  // ...
}

public class Square implements Shape {
  // ...
}

public class Rectangle implements Shape {
  // ...
}

public class ShapeDetailsPrinter {
  private Shape shape;

  public ShapeDetailsPrinter(Shape shape) {
    this.shape = shape;
  }

  public void printDetails() {
    System.out.println("Shape: " + shape.toString());
    System.out.println("Area: " + shape.getArea());
    System.out.println("Perimeter: " + shape.getPerimeter());
  }
}

Внедрив интерфейс Shape, мы отделили класс CircleDetailsPrinter от конкретной реализации класса Circle. Кроме того, мы используем новый класс ShapeDetailsPrinter для обслуживания классов Square и Rectangle без дублирования кода.

Заключение

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

Надеюсь, эта статья оказалась для вас полезной. Спасибо, что прочитали.

Подпишитесь на меня, чтобы получать обновления.

Вы также можете найти меня в другом месте в Интернете.