Статьи

Почему абстракция действительно важна

абстракция

Абстракция является одним из ключевых элементов хорошего дизайна программного обеспечения. Это помогает инкапсулировать поведение. Это помогает отделить программные элементы. Это помогает иметь больше автономных модулей. И многое другое.

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

Генеральная

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

Затем я кратко опишу, как мы можем избежать такой ситуации.

Описание тематического исследования

Давайте предположим, что у нас есть объект домена с именем RawItem .

01
02
03
04
05
06
07
08
09
10
11
12
13
public class RawItem {
    private final String originator;
    private final String department;
    private final String division;
    private final Object[] moreParameters;
 
    public RawItem(String originator, String department, String division, Object... moreParameters) {
        this.originator = originator;
        this.department = department;
        this.division = division;
        this.moreParameters = moreParameters;
    }
}

Три первых параметра представляют ключ элемента. Т.е. предмет исходит от создателя, отдела и подразделения. «MoreParameters» просто для того, чтобы подчеркнуть, что у элемента больше параметров.

Этот триплет имеет два основных использования:

  1. Как хранить ключ в БД
  2. Как ключ в картах (ключ к RawItem)

Хранение в БД на основе ключа

Таблицы БД ограждаются для равномерного распределения предметов. Разделение осуществляется с помощью хеш-ключа по модулю. Эта функция работает со строкой.

Предположим, у нас есть N таблиц шардов: (RAW_ITEM_REPOSITORY_00, RAW_ITEM_REPOSITORY_01, .., RAW_ITEM_REPOSITORY_NN),
тогда мы будем распределять элементы на основе некоторой функции и по модулю:

1
2
3
4
String rawKey = originator + "_"  + department + "_" + division;
// func is String -> Integer function, N = # of shards
// Representation of the key is described below
int shard = func(key)%N;

Использование ключа в картах

Второе использование для триплета — отображение элементов для быстрого поиска. Таким образом, когда НЕ используется абстракция, карты обычно будут выглядеть так:

1
2
Map<String, RawItem> mapOfItems = new HashMap<>();
// Fill the map...

«Улучшение» класса

Мы видим, что у нас есть общее использование ключа как строки, поэтому мы решили поместить строковое представление в RawItem.

01
02
03
04
05
06
07
08
09
10
// new member
private final String key;
 
// in the constructor:
this.key = this.originator + "_" + this.department + "_"  + this.division;
 
// and a getter
public String getKey() {
  return key;
}

Оценка дизайна

Здесь есть два потока:

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

И тогда приходит новое требование

До сих пор триплет: создатель, отдел и отдел составляли ключ предмета. Но теперь приходит новое требование. У подразделения может быть подразделение . Это означает, что, в отличие от ранее, мы можем иметь два разных предмета из одного и того же триплета. Элементы будут отличаться по атрибуту подразделения.

Сложно изменить

Что касается распределения БД, нам нужно сохранить составной ключ триплета. Мы должны оставить функцию по модулю такой же. Таким образом, распределение останется с использованием триплетов, но схема изменится, и столбец «подразделение» также будет иметь место. Мы изменим запросы, чтобы использовать подразделение вместе с оригинальным ключом.

Что касается сопоставления, нам нужно выполнить масштабный рефакторинг и передать ItemKey (см. Ниже) вместо просто String.

Абстракция ключа

Давайте создадим ItemKey

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class ItemKey {
    private final String originator;
    private final String department;
    private final String division;
    private final String subdivision;
 
    public ItemKey(String originator, String department, String division, String subdivision) {
        this.originator = originator;
        this.department = department;
        this.division = division;
        this.subdivision = subdivision;
    }
 
    public String asDistribution() {
        return this.originator + "_" + this.department + "_"  + this.division;
    }
}

И,

1
2
Map<ItemKey, RawItem> mapOfItems = new HashMap<>();
// Fill the map...
1
2
3
4
// new constructor for RawItem
public RawItem(ItemKey itemKey, Object... moreParameters) {
    // fill the fields
}

Извлеченный урок и заключение

Я хотел показать, как простое решение действительно может повредить.

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

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

Эта ситуация была реальной

У нас действительно был этот поток в нашем дизайне. Все было хорошо в течение двух лет , пока нам не пришлось сменить ключ (добавить подразделение). К счастью, весь наш код протестирован, чтобы мы могли увидеть, что сломалось, и исправить его. Но это было больно.

Есть две абстракции, которые мы могли бы изначально реализовать:

  1. Более очевидным является использование класса KEY (как описано выше). Даже если оно имеет только одно строковое поле
  2. Любое использование карты должно быть проверено, выиграем ли мы, скрыв ее с помощью абстракции

Вторая абстракция сложнее понять, полностью понять и реализовать.

Итак, делайте абстракцию, рассказывайте историю и используйте интерфейсы и не вдавайтесь в подробности, рассказывая об этом.