TLDR; Направете реализациите пакетни частни вместо публични, за да ограничите разработчиците да зависят от тези реализации, а не от интерфейсите на ниво компилатор.

През повечето време в Java модификаторът за достъп по подразбиране в IDE за новосъздадения клас или метод е публичендокато е частенза полета. Когато добавим поле към клас в Java, много обичайно е да го направим частно. Ние обаче правим класовете публичнипо подразбиране.

Първо, нека си припомним защо използваме модификатор за личендостъп за полета.

да В името на капсулирането. Искаме да ограничим достъпа, като скриемнякои от данните, за да осигурим последователен интерфейс, без да разкриваме начина, по който е внедрен (Енкапсулиране в Wikipedia).

Ние, като разработчици, (трябва) да скрием други неща, за да създадем еволюиращ софтуер. Скриваме извикванията на ядрото зад OS API или SQL зад ORM. Ние също така създаваме абстракционни слоеве, като използваме интерфейси в Java, за да скрием реализациите.

Но почакай! Наистина ли е скрито, ако използваме модификатор за обществен достъп в класа за изпълнение?

Да кажем, че имаме компонент за генератор на идентификатори и неговото внедряване:

// Note that they are in the same package
package com.gorkemgok.component.generator;

public interface IdGenerator {
  String generate();
}
// Note that they are in the same package
package com.gorkemgok.component.generator;

public class UUIDGenerator implements IdGenerator {
  
  public String generate() {
    return UUID.randomUUID().toString();
  }
  
}

В идеалния случай други потребителски класове трябва да зависят от интерфейса на IdGenerator. Внедряването (UUIDGenerator) трябва да е неизвестно за потребителските класове и да бъде инжектирано в тези класове (в идеалния случай от DI Framework като Spring, Guice и т.н.).

Да кажем, че SomeOtherClassе всеки потребителски клас, който зависи от IdGenerator:

//Note that this is in a different package than IdGenerator and its implementation
package com.gorkemgok.example.repository;

public class SomeOtherClass{
  
  private IdGenerator idGenerator;
  
  ...

}

Както е показано в кода, SomeOtherClassзависи от интерфейса на IdGenerator.

Ако SomeOtherClassзависи от класа на изпълнение (UUIDGenerator), това би нарушило абстракцията и това би било анти-модел. (Интерфейсно базирано програмиране). Въпреки това е много лесно да се наруши този принцип в Java, защото това е публичен клас. Разработчикът може да създаде клас, който зависи от внедряването (UUIDGeenrator) и също така е много вероятно да пропусне това при прегледите на кода. Можем/трябва да ограничим в зависимост от изпълнението, като го направим частен пакет.

Нека премахнем модификатора за обществендостъп от класа:

package com.gorkemgok.component.generator;

//We made it package private by deleting the public access modifier
class UUIDGenerator implements IdGenerator {
  
  public String generate() {
    return UUID.randomUUID().toString();
  }
  
}

Ето! С едно малко докосване нашият код е по-устойчив на бъдещи миризми на код. Нито един клас извън пакета com.gorkemgok.example.generator не може да зависи от класа на изпълнение.

Сега той е буквално скрит от другите пакети. Ако поддържаме нашите пакети сплотени, можем да постигнем много висока абстракция за нашите реализации. Можете да мислите за интерфейсните класове като API на нашите пакети. Потребителят може да вижда само API и не знае нищо за подробностите, така че подробностите могат да се променят, без да нарушават потребителските класове.

Проблем с пакетно частни интерфейси

Можем също така да създадем частни интерфейси за пакети, но има проблем!

Да кажем, че създаваме друга реализация на интерфейса IdGeneratorиимаме нужда от генератор на произволни символи за това.

package com.gorkemgok.component.generator;

class RandomIdGenerator implements IdGenerator {
  
  private final RandomChar randomChar;

  ...
  
}
package com.gorkemgok.component.generator;

//This interface is package-private 
//because it meant to be used only within IdGenerator component
interface RandomChar {
  
   char random();
  
}

В случай, че направим интерфейс частен пакет, имплементациите на този интерфейс трябва да бъдат в същия пакет. Ако обаче направим това, RandomIdGenerator (потребителският клас)може да зависи от внедряването на RandomChar. Това би нарушило капсулирането, което искаме да постигнем.

Не можем да внедрим частен интерфейс на пакета и във вложен пакет (като package com.gorkemgok.companent.generator.random), защото внедряването и интерфейсът RandomCharще бъдат в различни пакети.

За съжаление, нямаме модификатори за достъп за вложени пакети в Java. Ние също нямаме семантични вложени пакети в Java. Семантично всеки пакет е на едно и също ниво.

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

Така че ще създадем package com.gorkemgok.companent.generator.random и ще поставим публичния интерфейс RandomCharи пакетните частни реализации в този пакет.

За да постигнете по-сложна архитектура, съдържаща вложени пакети, можете да принудите разработчиците да се подчиняват на конкретни правила, като използвате тестовата библиотека ArchUnit („връзка“). Това не е ограничение на ниво компилатор, но прави вашите модулни тестове неуспешни в случай на нарушение на архитектурата.

Инжектиране на зависимост

Нека се върнем към пакетно частни имплементации.

Не искаме друг клас да зависи от имплементацията, но това води до клас, който не може да се създаде извън този пакет. За съжаление, Java не дава много опции за това. Имате нужда от фабричен класв същия пакет, освен ако не използвате рамка за инжектиране на зависимости като Spring или Guice.

Пролетна рамка

Spring може да сканира и регистрира пакетни частни класове като бобове. Добавете стереотипна анотация (@Component, @Service и т.н.) към вашия клас и ако използвате @SpringBootApplication или @ComponentScan анотация, Spring ще се погрижи за останалото.

Guice

В Guice трябва да създадете своя модул Guiceв същия пакет, както правите с фабричен клас.

Резюме

Ние познаваме и прилагаме капсулирането в класове много добре, но защо да не го използваме в пакети? По-лесно е да променитечастите, които са скрити зад абстракциите.

Колкото по-скрит, толкова по-еволюиращ софтуер.

Принуждаването на разработчиците да използват абстракции вместо реализации на ниво на компилаторще попречи на вашата кодова база да се развали с времето.

Опитът да постигнете тази абстракция също ще ви накара да видите по-добре недостатъците на дизайна. Когато трябва да зависиш от внедряване, ще спреш и ще се опиташ да разбереш защо се нуждаеш от тази зависимост, която не трябва да ти трябва.

Кодиране на ниво нагоре

Благодарим ви, че сте част от нашата общност! Преди да тръгнеш:

🚀👉 Присъединете се към колектива за таланти Level Up и намерете невероятна работа