Принцип на сегрегация на интерфейса в Java

1. Въведение

В този урок ще обсъдим принципа за разделяне на интерфейса, един от принципите на SOLID. Представяйки „I“ в „SOLID“, разделянето на интерфейса просто означава, че трябва да разбием по-големи интерфейси на по-малки.

По този начин се гарантира, че внедряването на класове не трябва да прилага нежелани методи.

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

Този принцип беше дефиниран за първи път от Робърт С. Мартин като: „ Клиентите не трябва да бъдат принуждавани да разчитат на интерфейси, които те не използват “.

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

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

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

3. Примерен интерфейс и изпълнение

Нека да разгледаме ситуация, при която имаме интерфейс за плащане, използван от внедряване BankPayment :

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

И изпълнението:

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

За простота, нека игнорираме действителното бизнес изпълнение на тези методи.

Това е много ясно - досега внедряващият клас BankPayment се нуждае от всички методи в интерфейса за плащане . По този начин това не нарушава принципа.

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 getPayments() { // ... } @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } }

Сега, тъй като интерфейсът за плащане се промени и бяха добавени повече методи, всички изпълняващи класове сега трябва да внедрят новите методи. Проблемът е, че прилагането им е нежелано и може да доведе до много странични ефекти. Тук класът за изпълнение на LoanPayment трябва да реализира InitiatePayments () без действителна нужда от това. И така, принципът е нарушен.

И така, какво се случва с нашия клас BankPayment :

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List 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. Прилагане на принципа

В последния раздел умишлено замърсихме интерфейса и нарушихме принципа. В този раздел ще разгледаме как да добавим новата функция за плащане на заем, без да нарушаваме принципа.

Нека разбием интерфейса за всеки вид плащане. Настоящата ситуация:

Забележете в диаграмата на класа и позовавайки се на интерфейсите в по-ранния раздел, че методите status () и getPayments () се изискват и в двете реализации. От друга страна, InitiatePayments () се изисква само в BankPayment , а методите InitiateLoanSettlement () и InitiateRePayment () са само за LoanPayment .

С това сортирано, нека разбием интерфейсите и приложим принципа за разделяне на интерфейса. По този начин вече имаме общ интерфейс:

public interface Payment { Object status(); List 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 getPayments() { // ... } }

И накрая, нашето ревизирано изпълнение на LoanPayment :

public class LoanPayment implements Loan { @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Сега, нека прегледаме новата диаграма на класа:

Както виждаме, интерфейсите не нарушават принципа. Реализациите не трябва да предоставят празни методи. Това поддържа кода чист и намалява вероятността от грешки.

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

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

В случай, че имаме работа със замърсени стари интерфейси, които не можем да модифицираме, моделът на адаптера може да бъде полезен.

Принципът за разделяне на интерфейса е важна концепция при проектирането и разработването на приложения. Спазването на този принцип помага да се избегнат раздути интерфейси с множество отговорности. Това в крайна сметка ни помага да следваме и принципа на единната отговорност.

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