Рубрики
Без рубрики

Давайте закодируем: графики на java

Я был одержим графиками уже несколько лет. Эта одержимость привела к появлению трех проектов:… С тегами java, tdd, учебник, графики.

Я был одержим графиками уже несколько лет. Эта одержимость привела к появлению трех проектов: graph-dsl , graphs и graph-wm . Графики – это отличные структуры данных, которые помогли мне в нескольких ситуациях на работе. В этом руководстве мы разработаем класс mutableDirectedGraph на Java с использованием Test Driven Development (TDD).

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

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

Граф инкапсулирует вершины и ребра. Вершина – это значение, хранящееся в графе, в то время как ребро – это связь между этими значениями. Эта реализация будет содержать набор вершин и набор ребер.

Использование Set подразумевает уникальность. В графе разрешены только уникальные вершины и ребра. Эти объекты должны корректно реализовывать equals и hashCode .

Идентификация ребер может быть сложной задачей. Это зависит от того, является ли график направленным или неориентированным. В неориентированном графе только одно ребро может находиться между любыми двумя вершинами. В ориентированном графе одно или два ребра могут находиться между любыми двумя вершинами. Если между двумя вершинами есть два ребра, то эти ребра должны быть в противоположных направлениях. Эти свойства повлияют на реализацию equals и hashCode для ребер.

На данный момент у нас достаточно информации, чтобы начать кодирование, но я хочу объяснить TDD.

Вот три закона TDD (от Clean Coder Роберта К. Мартина):

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

Эти законы подразумевают рабочий процесс, который состоит из:

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

рефакторинг 🔄 измените код таким образом, чтобы тесты не проваливались и не изменяли api или функциональность.

Чтобы облегчить выполнение теста TDD, статус теста будет отображаться как ❌ при сбое и ✔️ при прохождении.

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

Давайте начнем.

Как и многие учебные пособия, этот раздел разбит на этапы. Шаги могут быть связаны с использованием формы “шаг-1 “, например.

[step-1](#step-1)

Цель использования шагов – помочь обсуждению в комментариях.

Класс Ориентированный граф – это производственный код. Класс не должен быть записан до тех пор, пока не будет провален тест в соответствии с законом № 1 . Вот этот тест.

public class DirectedGraphTest {
  @Test
  void constructor() {
    DirectedGraph graph = new DirectedGraph();
  }
}

Тестовый результат: ❌ сбой, напишите производственный код

Рабочий процесс: Критерии, изложенные в законе № 1, выполнены, и теперь рабочий процесс меняется на добавление производственного кода. Сбой компиляции считается сбоем теста в соответствии с законом № 2, и поэтому больше не следует писать код модульного тестирования.

Действие: Создайте класс Ориентированный граф . Большинство идей полезны в ситуациях, когда отсутствует класс, метод или переменная. Я использую intellij для создания класса.

public class DirectedGraph {

}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Рабочий процесс: Согласно закону № 3 мы не должны писать больше производственного кода. Единственный допустимый вариант – следовать закону № 1 и написать неудачный модульный тест.

Дизайн теста: Вернемся к первому тесту, он явно неполный. Назначение конструктора – инициализировать объект. Мы уже обсуждали это Ориентированный граф будет иметь два Набор s. Этот тест должен показать, что график инициализируется путем проверки того, что эти два Set s пусты.

Действие: Добавьте неудачную проверку вершин.

@Test
void constructor() {
  DirectedGraph graph = new DirectedGraph();
  assertThat(graph.getVertices()).isEmpty();
}

Статус теста: ❌ сбой , написать производственный код

Рабочий процесс: Метод getVertices() не компилируется, и теперь тест завершается неудачей ( закон №2 ), и можно добавить производственный код ( закон №1 ).

Действие: Метод может быть добавлен с помощью IDE, но intellij не знает, какой тип возвращаемого значения следует использовать. Вот код.

public Set getVertices() {
}

Производственный дизайн: В этом случае IDE не знает, как разработать этот метод, и я добавил возвращаемое значение в качестве универсального. Тест показывает, что эти проектные решения должны быть приняты сейчас. Я решаю, что вершины должны быть переменной-членом и возвращаться с помощью getVertices() . Я также решаю, что вершины являются общими. Придание вершинам универсального характера также подразумевает, что Ориентированный граф теперь является общим.

public class DirectedGraph {
  public Set getVertices() {
    return null;
  }
}

Статус теста: ❌ сбой , написать производственный код

Действие: Тест все равно завершится неудачей, но он компилируется. На этом этапе нам нужно вернуть пустой набор для прохождения теста.

public Set getVertices() {
  return HashSet<>();
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

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

Тест на рефакторинг: 🔄 Тест показывает некоторые предупреждения об использовании необработанных типов. Это можно исправить, добавив параметр типа.

@Test
void constructor() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThat(graph.getVertices()).isEmpty();
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Рабочий процесс: Теперь тест проходит, и требуется еще один неудачный модульный тест (закон № 2). Помните, что текущая цель – проверить конструктор. Существует тест, который проверяет вершины, но как насчет ребер?

Я хочу добавить утверждение, которое выглядит примерно так:

assertThat(graph.getEdges()).isEmpty()

Дизайн: Но что такое край? Я еще не решил, как ребра представлены в графе. На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора. На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора.

На данный момент мне нужен неудачный тест, но это не обязательно должен быть тест конструктора.

A Направленный край класс должен содержать исходную вершину и целевую вершину ребра. Это конечные точки ребра. Направленное ребро также должно использовать общий тип, чтобы граф мог указывать разные типы вершин при создании ребра. Поскольку ребра будут находиться в Набор они должны реализовать равно и хэш-код . Я также решаю, что Направленный край должен быть неизменяемым классом значений, что означает источник и target будут обязательными параметрами конструктора, и класс будет окончательным. Этой информации достаточно для создания тестов.

public class DirectedEdgeTest {
  @Test
  void constructor() {
    DirectedEdge edge = new DirectedEdge<>();
  }
}

Статус теста: ❌ сбой , написать производственный код

Как и в случае с DirectedGraphTest класс Directed Edge может быть сгенерирован в среде IDE.

public final class DirectedEdge {
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Теперь тест пройден, но верен ли конструктор? Давайте добавим проверку исходной вершины.

@Test
void constructor() {
  DirectedEdge edge = new DirectedEdge<>();
  assertThat(edge.getSource()).isEqualTo("A");
}

Статус теста: ❌ сбой , написать производственный код

Метод getSource() не существует, но также обратите внимание, что тест ожидает, что он будет равен “A”. Первый get Source() может быть добавлен в DirectedEdge .

public T getSource() {
  return null;
}

Тест будет скомпилирован, но все равно завершится неудачей, ожидая, что источником будет “A”.

public T getSource() {
  return (T) "A";
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Добавить проверку для целевой вершины.

@Test
void constructor() {
  DirectedEdge edge = new DirectedEdge<>();
  assertThat(edge.getSource()).isEqualTo("A");
  assertThat(edge.getTarget()).isEqualTo("B");
}

Статус теста: ❌ сбой , написать производственный код

Действие: Добавьте метод getTarget() в |/DirectedEdge .

public T getTarget() {
  return (T) "B";
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: замените конструктор по умолчанию на требуемый конструктор args.

@Test
void constructor() {
  DirectedEdge edge = new DirectedEdge<>("A", "B");
  assertThat(edge.getSource()).isEqualTo("A");
  assertThat(edge.getTarget()).isEqualTo("B");
}

Статус теста: ❌ сбой , написать производственный код

Действие: Добавить requiredargsconstructor (удаление конструктора по умолчанию).

public final class DirectedEdge {

  public DirectedEdge(T source, T target) {

  }

  public T getSource() {
    return (T) "A";
  }

  public T getTarget() {
    return (T) "B";
  }
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Добавьте новый тест конструктора с другим источником и целью.

@Test
void constructor_AB() {
  DirectedEdge edge = new DirectedEdge<>("A", "B");
  assertThat(edge.getSource()).isEqualTo("A");
  assertThat(edge.getTarget()).isEqualTo("B");
}

@Test
void constructor_XY() {
  DirectedEdge edge = new DirectedEdge<>("X", "Y");
  assertThat(edge.getSource()).isEqualTo("X");
  assertThat(edge.getTarget()).isEqualTo("Y");
}

Статус теста: ❌ сбой , написать производственный код

Действие: Добавьте переменные-члены и назначьте в конструкторе.

public final class DirectedEdge {
  private final T source;
  private final T target;

  public DirectedEdge(T source, T target) {
    this.source = source;
    this.target = target;
  }

  public T getSource() {
    return "A";
  }

  public T getTarget() {
    return "B";
  }
}

Статус теста: ❌ сбой , написать производственный код

Действие: Возвращает переменные-члены из геттеров.

public T getSource() {
  return source;
}

public T getTarget() {
  return target;
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Добавление нулевых проверок

Не имеет смысла разрешать нулевой источник или цель . Эти параметры должны быть проверены в конструкторе. Хороший способ сделать это – использовать Objects.requireNonNull .

@Test
void constructor_fails_on_null_source() {
  assertThatNullPointerException().isThrownBy(() -> new DirectedEdge<>(null, "B"));
}

Статус теста: ❌ сбой , написать производственный код

, написать производственный код

public DirectedEdge(T source, T target) {
  this.source = requireNonNull(source);
  this.target = target;
}

, написать производственный код

, написать производственный код , написать производственный код

, написать производственный код , написать производственный код . , написать производственный код

assertThatNullPointerException()
  .isThrownBy(() -> new DirectedEdge<>(null, "B"))
  .withMessage("source must not be null");

, написать производственный код , написать производственный код

, написать производственный код

, написать производственный код

this.source = requireNonNull(source, "source must not be null");

, написать производственный код

, написать производственный код

@Test
void constructor_fails_on_null_target() {
  assertThatNullPointerException()
    .isThrownBy(() -> new DirectedEdge<>("A", null))
    .withMessage("target must not be null");
}

, написать производственный код , написать производственный код

, написать производственный код , написать производственный код

public DirectedEdge(T source, T target) {
  this.source = requireNonNull(source, "source must not be null");
  this.target = requireNonNull(target, "target must not be null");
}

, написать производственный код

, написать производственный код , написать производственный код .

, написать производственный код , написать производственный код

, написать производственный код , написать производственный код , написать производственный код и целевой . Вместо того, чтобы писать все эти тесты, есть доступный инструмент, который может выполнять эти тесты за нас. Это будет сделано с помощью equals-verifier .

@Test
void equalsContract() {
    EqualsVerifier.forClass(DirectedEdge.class).verify();
}

Статус теста: ❌ сбой , написать производственный код

Действие: Write equals и/| hashCode методы с использованием IDE.

Чтобы сгенерировать этот код в intellij, я использовал формат java 7+, а исходный и целевой значения не равны нулю.

@Override
public boolean equals(Object o) {
  if (this == o) return true;
  if (o == null || getClass() != o.getClass()) return false;
  DirectedEdge that = (DirectedEdge) o;
  return source.equals(that.source) &&
    target.equals(that.target);
}

@Override
public int hashCode() {
  return Objects.hash(source, target);
}

Статус теста: ❌ сбой , написать производственный код

Тест equals Contract все равно завершится неудачей, поскольку source и target не являются нулевыми.

Действие: Подавите эти предупреждения.

EqualsVerifier.forClass(DirectedEdge.class).suppress(Warning.NULL_FIELDS).verify();

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Действие:

Теперь, когда есть Направленное ребро класс ребер в Ориентированном графе класс конструктора может быть проверен.

@Test
void constructor() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThat(graph.getVertices()).isEmpty();
  assertThat(graph.getEdges()).isEmpty();
}

Статус теста: ❌ сбой , написать производственный код

Действие: Добавить getEdges метод

public Set> getEdges() {
  return new HashSet<>();
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

На этом шаге мы проверяем, что вершины могут быть добавлены в граф.

Действие: Сделать тест для добавления вершин

@Test
void vertex_adds_new_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.vertex("A");
}

Статус теста: ❌ сбой , написать производственный код

Действие: Добавить вершину метод

  public void vertex(T vertex) {

  }

Тестовое задание: ✔️ прохождение, запись неудачного теста

Действие: Добавить проверку на наличие добавленной вершины

@Test
void vertex_adds_new_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.vertex("A");
  assertThat(graph.getVertices()).containsExactly("A");
}

Статус теста: ❌ сбой , написать производственный код

Этот тест доказывает, что Ориентированный граф должен хранить вершины.

Действие: Добавьте элемент vertices и измените метод vertex , чтобы добавить вершину. Измените getVertices() |/| чтобы вернуть вершины.

public class DirectedGraph {
  private final Set vertices;

  public DirectedGraph() {
    vertices = new HashSet<>();
  }

  public Set getVertices() {
    return vertices;
  }

  public Set> getEdges() {
    return new HashSet<>();
  }

  public void vertex(T vertex) {
    vertices.add(vertex);
  }
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Набор, возвращаемый getVertices() , должен быть неизменяемым.

Действие: Добавить тест, гарантирующий неизменяемость вершин.

@Test
void vertices_are_unmodifiable() {
  DirectedGraph graph = new DirectedGraph<>();
  Set vertices = graph.getVertices();
  assertThatThrownBy(() -> vertices.add(10))
    .isInstanceOf(UnsupportedOperationException.class);
}

Статус теста: ❌ сбой , написать производственный код

Действие: возвращает неизменяемый набор в getVertices() .

public Set getVertices() {
  return unmodifiableSet(vertices);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

То же самое можно сделать для get Edges() .

* Действие: * Добавьте тест для getEdges() , гарантирующий неизменяемость набора.

@Test
void edges_are_unmodifiable() {
  DirectedGraph graph = new DirectedGraph<>();
  Set> edges = graph.getEdges();
  assertThatThrownBy(() -> edges.add(new DirectedEdge<>(1, 2)))
    .isInstanceOf(UnsupportedOperationException.class);
}

Статус теста: ❌ сбой , написать производственный код

Действие: возвращает неизменяемый набор в getEdges() .

public Set> getEdges() {
  return unmodifiableSet(new HashSet<>());
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

null вершины не должны быть разрешены.

Действие: вершина сбой при использовании нулевого параметра.

@Test
void vertex_fails_on_null_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() -> graph.vertex(null))
    .withMessage("vertex must not be null");
}

Статус теста: ❌ сбой , написать производственный код

Действие: проверьте наличие нулевых вершин.

public void vertex(T vertex) {
  requireNonNull(vertex, "vertex must not be null");
  vertices.add(vertex);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Теперь, когда вершины могут быть добавлены к графу, ребра должны быть добавлены к графу. Этот метод должен гарантировать, что вершины будут добавлены, если они отсутствуют в графе. На этот раз тесты начинаются с проверки на наличие ошибок. Идея состоит в том, чтобы проверить наличие ошибок перед написанием кода happy path.

* Действие: * Добавьте проверку исходного параметра null в метод edge .

@Test
void edge_fails_on_null_source() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() -> graph.edge(null, "B"))
    .withMessage("source must not be null");
}

Статус теста: ❌ сбой , написать производственный код

Метод edge не существует и должен быть добавлен.

public void edge(T source, T target) {
}

Статус теста: ❌ сбой , написать производственный код

Теперь метод edge должен выдать исключение.

* Действие: * Добавьте NPE в метод edge .

public void edge(T source, T target) {
  throw new NullPointerException("source must not be null");
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Метод edge также должен проверять target на наличие null.

* Действие: * Добавьте тест для нулевого целевого параметра в метод edge .

@Test
void edge_fails_on_null_target() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() -> graph.edge("A", null))
    .withMessage("target must not be null");
}

Статус теста: ❌ сбой , написать производственный код

Метод edge теперь должен фактически проверять источник на наличие нулевого значения.

* Действие: * Добавьте нулевые проверки для источника и цели в методе edge .

public void edge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Метод edge должен добавить исходную вершину, если ее еще нет в графе.

* Действие: * Добавьте тест для исходной вершины в графе после вызова edge .

@Test
void edge_adds_source_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.edge("A", "B");
  assertThat(graph.getVertices()).contains("A");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавьте исходную вершину в граф в методе edge .

public void edge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
  vertex(source);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Целевая вершина также должна быть добавлена к графику.

* Действие: * Добавьте тест для целевой вершины в графе после вызова edge .

@Test
void edge_adds_target_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.edge("A", "B");
  assertThat(graph.getVertices()).contains("B");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавьте исходную вершину в граф в методе edge .

public void edge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
  vertex(source);
  vertex(target);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Фактическое ребро также должно быть добавлено.

* Фактическое ребро также должно быть добавлено. Фактическое ребро также должно быть добавлено.

@Test
void edge_adds_an_edge() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.edge("A", "B");
  assertThat(graph.getEdges()).containsExactly(new DirectedEdge<>("A", "B"));
}

Фактическое ребро также должно быть добавлено. Фактическое ребро также должно быть добавлено.

Тест доказывает, что edges должна быть переменной-членом, которая требует изменений в конструкторе getEdges() и метод edge .

* Действие: * Добавьте переменную-член edges, используйте элемент в getEdges и ребра метод.

public class DirectedGraph {
  private final Set vertices;
  private final Set> edges;

  public DirectedGraph() {
    vertices = new HashSet<>();
    edges = new HashSet<>();
  }

  public Set getVertices() {...}

  public Set> getEdges() {
    return unmodifiableSet(edges);
  }

  public void vertex(T vertex) {...}

  public void edge(T source, T target) {
    requireNonNull(source, "source must not be null");
    requireNonNull(target, "target must not be null");
    vertex(source);
    vertex(target);
    edges.add(new DirectedEdge<>(source, target));
  }
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

На этом этапе можно добавить метод для удаления ребер.

* Действие: * Добавьте тест для removeEdge .

@Test
void removeEdge_fails_on_null_source() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() -> graph.removeEdge(null, "A"))
    .withMessage("source must not be null");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * для компиляции необходимо добавить remove Edge .

public void removeEdge(T source, T target) {
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавьте проверку на наличие нулевого источника.

public void removeEdge(T source, T target) {
  requireNonNull(source, "source must not be null");
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

* Действие: * Напишите неудачный тест, проверяющий нулевую цель.

@Test
void removeEdge_fails_on_null_target() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() -> graph.removeEdge("A", null))
    .withMessage("target must not be null");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавить проверку removeEdge для null target .

public void removeEdge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

удалить ребро должно завершиться неудачей, если ребро не найдено.

* Действие: * Добавьте тест, ожидающий исключения, когда edge не существует.

@Test
void removeEdge_fails_on_missing_edge() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatIllegalArgumentException()
    .isThrownBy(() -> graph.removeEdge("A", "B"))
    .withMessage("edge with source \"A\" and target \"B\" does not exist");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавьте проверку, чтобы увидеть, содержит ли ребро ребро, и выдайте исключение, если оно отсутствует.

public void removeEdge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
  if(!edges.contains(new DirectedEdge<>(source, target))) {
    throw new IllegalArgumentException(String.format("edge with source \"%s\" and target \"%s\" does not exist", source, target));
  }
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

* Действие: * Добавьте тест для удаления и ребра из графика.

@Test
void removeEdge_removes_edge() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.edge("A", "B");
  graph.removeEdge("A", "B");
  assertThat(graph.getEdges()).isEmpty();
}

Статус теста: ❌ сбой , написать производственный код

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

* Действие: * Используйте удалить вместо содержит .

public void removeEdge(T source, T target) {
  requireNonNull(source, "source must not be null");
  requireNonNull(target, "target must not be null");
  if(!edges.remove(new DirectedEdge<>(source, target))) {
    throw new IllegalArgumentException(String.format("edge with source \"%s\" and target \"%s\" does not exist", source, target));
  }
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Теперь необходим метод для удаления вершин. Давайте напишем для этого тест.

* Действие: * Добавьте тест для удаления вершин.

@Test
void removeVertex_removes_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.vertex("A");
  graph.removeVertex("A");
  assertThat(graph.getVertices()).isEmpty();
}

Тест не будет скомпилирован.

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавить удалить вершину метод.

public void removeVertex(T vertex) {
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * удалить вершину.

public void removeVertex(T vertex) {
  vertices.remove(vertex);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

* Действие: * добавьте тест для параметра null vertex в |/removeVertex .

@Test
void removeVertex_fails_on_null_vertex() {
  DirectedGraph graph = new DirectedGraph<>();
  assertThatNullPointerException()
    .isThrownBy(() ->graph.removeVertex(null))
    .withMessage("vertex must not be null");
}

Статус теста: ❌ сбой , написать производственный код

* Действие: * Добавьте нулевую проверку к методу.

public void removeVertex(T vertex) {
  requireNonNull(vertex, "vertex must not be null");
  vertices.remove(vertex);
}

Тестовое задание: ✔️ прохождение, запись неудачного теста

Существует еще одно условие отказа при удалении вершин. Если удаляемая вершина имеет смежные ребра, эти ребра также должны быть удалены.

Действие: Добавьте тест, проверяющий, удаляются ли соседние ребра при удалении вершины.

@Test
void removeVertex_removes_adjacent_edges() {
  DirectedGraph graph = new DirectedGraph<>();
  graph.edge("A", "B");
  graph.removeVertex("A");
  assertThat(graph.getEdges()).isEmpty();
}

Статус теста: ❌ сбой , написать производственный код

Действие: Найдите все смежные ребра и удалите их в removeVertex .

public void removeVertex(T vertex) {
  requireNonNull(vertex, "vertex must not be null");
  Iterator> i = edges.iterator();
  while(i.hasNext()) {
    DirectedEdge next = i.next();
    if(next.getSource().equals(vertex) || next.getTarget().equals(vertex)) {
      i.remove();
    }
  }
  vertices.remove(vertex);
}

Тестовые задания: ✔ passing прохождение, рефакторинг 🔄 изменение кода таким образом, чтобы тесты не проваливались

Сейчас самое время провести рефакторинг. Рефакторинг на самом деле предложен Intellij. Он заменяет итератор вызовом remove If |/с использованием лямбда-выражения.

Действие: Рефакторинг removeVertex для использования удалить, если при удалении смежных ребер.

public void removeVertex(T vertex) {
  requireNonNull(vertex, "vertex must not be null");
  edges.removeIf(next -> next.getSource().equals(vertex) || next.getTarget().equals(vertex));
  vertices.remove(vertex);
}

удалить, если при удалении смежных ребер.

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

Есть еще несколько проблем, которые необходимо решить, но это хорошее начало в представлении графика в виде кода.

Оригинал: “https://dev.to/moaxcp/lets-code-graphs-in-java-1kem”