Перейти к основному содержимому

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

· 5 мин. чтения

1. Введение

В этом уроке мы обсудим принцип разделения интерфейса, один из принципов SOLID . Представляя «I» в «SOLID», разделение интерфейсов просто означает, что мы должны разбивать большие интерфейсы на более мелкие.

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

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

Этот принцип был впервые определен Робертом С. Мартином как: « Клиенты не должны зависеть от интерфейсов, которые они не используют ».

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

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

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

3. Пример интерфейса и реализация

Давайте рассмотрим ситуацию, когда у нас есть интерфейс Payment , используемый реализацией BankPayment :

public interface Payment { 
void initiatePayments();
Object status();
List<Object> getPayments();
}

И реализация:

public class BankPayment implements Payment {

@Override
public void initiatePayments() {
// ...
}

@Override
public Object status() {
// ...
}

@Override
public List<Object> getPayments() {
// ...
}
}

Для простоты давайте проигнорируем реальную бизнес-реализацию этих методов.

Это очень понятно — пока реализующему классу BankPayment нужны все методы в интерфейсе Payment . Таким образом, это не нарушает принцип.

4. Загрязнение интерфейса

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

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

public interface Payment {

// original methods
...
void intiateLoanSettlement();
void initiateRePayment();
}

Далее у нас будет реализация LoanPayment :

public class LoanPayment implements Payment {

@Override
public void initiatePayments() {
throw new UnsupportedOperationException("This is not a bank payment");
}

@Override
public Object status() {
// ...
}

@Override
public List<Object> getPayments() {
// ...
}

@Override
public void intiateLoanSettlement() {
// ...
}

@Override
public void initiateRePayment() {
// ...
}
}

Теперь, когда интерфейс Payment изменился и было добавлено больше методов, все реализующие классы теперь должны реализовывать новые методы. Проблема в том, что их реализация нежелательна и может привести ко многим побочным эффектам. Здесь класс реализации LoanPayment должен реализовывать метод initialPayments () без какой-либо реальной необходимости в этом. А значит, принцип нарушен.

Итак, что происходит с нашим классом BankPayment :

public class BankPayment implements Payment {

@Override
public void initiatePayments() {
// ...
}

@Override
public Object status() {
// ...
}

@Override
public List<Object> getPayments() {
// ...
}

@Override
public void intiateLoanSettlement() {
throw new UnsupportedOperationException("This is not a loan payment");
}

@Override
public void initiateRePayment() {
throw new UnsupportedOperationException("This is not a loan payment");
}
}

Обратите внимание, что в реализации BankPayment теперь реализованы новые методы. А поскольку они ему не нужны и для них нет логики, он просто выдает исключение UnsupportedOperationException . Здесь мы начинаем нарушать принцип.

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

5. Применение принципа

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

Разберем интерфейс для каждого типа оплаты. Текущая ситуация:

./ca2cbf281a118b4353112510e88c4005.png

Обратите внимание на диаграмму классов и на интерфейсы в предыдущем разделе, что методы status() и getPayments() необходимы в обеих реализациях. С другой стороны, initialPayments () требуется только в BankPayment , а методы initialLoanSettlement () и инициироватьRePayment() — только в LoanPayment .

Разобравшись с этим, давайте разделим интерфейсы и применим Принцип разделения интерфейсов. Таким образом, теперь у нас есть общий интерфейс:

public interface Payment {
Object status();
List<Object> getPayments();
}

И еще два интерфейса для двух видов платежей:

public interface Bank extends Payment {
void initiatePayments();
}
public interface Loan extends Payment {
void intiateLoanSettlement();
void initiateRePayment();
}

И соответствующие реализации, начиная с BankPayment :

public class BankPayment implements Bank {

@Override
public void initiatePayments() {
// ...
}

@Override
public Object status() {
// ...
}

@Override
public List<Object> getPayments() {
// ...
}
}

И, наконец, наша пересмотренная реализация LoanPayment :

public class LoanPayment implements Loan {

@Override
public void intiateLoanSettlement() {
// ...
}

@Override
public void initiateRePayment() {
// ...
}

@Override
public Object status() {
// ...
}

@Override
public List<Object> getPayments() {
// ...
}
}

Теперь давайте рассмотрим новую диаграмму классов:

./ec1a88410ef8e7140d70dce92b63443c.png

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

6. Заключение

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

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

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

Как всегда, код доступен на GitHub .