Чи застосовується принцип поділу інтерфейсу до конкретних методів?


10

Оскільки принцип сегрегації інтерфейсів передбачає, що жоден клієнт не повинен бути змушений залежати від методів, які він не використовує, тому клієнт не повинен впроваджувати порожній метод для своїх методів інтерфейсу, інакше цей метод інтерфейсу повинен бути переведений в інший інтерфейс.

Але як щодо конкретних методів? Чи слід відокремлювати методи, якими користувався би не кожен клієнт? Розглянемо наступний клас:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

У наведеному вище коді CarShop взагалі не використовує метод isQualityPass () в автомобілі, чи слід розділяти isQualityPass () на новий клас:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

щоб зменшити зв’язок CarShop? Тому що я думаю, що один раз, якщо isQualityPass () потребує додаткової залежності, наприклад:

public boolean isQualityPass(){
    HttpClient client=...
}

CarShop залежатиме від HttpClient, навіть він ніколи фактично не використовує HttpClient. Отож, моє запитання: за принципом розбиття інтерфейсу, чи слід відокремлювати конкретні методи, якими користуються не всі клієнти, щоб ці методи залежали від клієнта лише тоді, коли клієнт насправді використовує, щоб зменшити зв'язок?


2
Зазвичай автомобіль знає, коли проходить «якість»? Або це, можливо, бізнес-правило, яке може бути інкапсульоване власним?
Лаїв

2
Оскільки слово інтерфейсу в ISP передбачає, що мова йде про інтерфейси . Отже, якщо у вашому Carкласі є метод, про який ви не хочете (усі) користувачів знати, тоді створіть (більше одного) інтерфейс, який Carклас реалізує, який оголошує лише методи корисними у контексті інтерфейсів.
Тімоті Тріклерл

@Laiv Я впевнений, що ми незабаром побачимо транспортні засоби, які знають набагато більше, ніж це. ;)
уніфікований моделювання сендвіч

1
Автомобіль буде знати, що його виробник хоче його знати. Volkswagen знаю, про що я говорю :-)
Laiv

1
Ви згадуєте інтерфейс, але інтерфейсу у вашому прикладі немає. Ми говоримо про перетворення автомобіля в інтерфейс і які методи включити до цього інтерфейсу?
Ніл

Відповіді:


6

У вашому прикладі CarShopце не залежить isQualityPass, і він не змушений робити пусту реалізацію для методу. Навіть не задіяний інтерфейс. Тож термін "ISP" тут просто не відповідає. І поки такий метод, як isQualityPassметод, добре вписується в Carоб'єкт, не перевантажуючи його додатковими обов'язками чи залежностями, це добре. Немає необхідності переробляти публічний метод класу в інше місце лише тому, що існує один клієнт, який не використовує метод.

Однак створення доменного класу, як Carбезпосередньо залежного від чогось подібного HttpClient, ймовірно, також не є хорошою ідеєю, незалежно від того, які клієнти використовують чи не використовують метод. Переміщення логіки в окремий клас CheckCarQualityPassпросто не називається "ISP", це називається "розділенням проблем" . Занепокоєння автомобільного багаторазового об'єкта, мабуть, не повинно полягати у здійсненні зовнішніх HTTP-дзвінків, принаймні, безпосередньо, це обмежує можливість повторного використання та, тим більше, тестабельність занадто сильно.

Якщо isQualityPassне може бути легко переміщені в інший клас, альтернатива була б зробити Httpдзвінки через абстрактний інтерфейс , IHttpClientякий впорскується в Carпід час будівництва, або шляхом введення всього «QualityPass» Перевірка стратегії (із запитом Http інкапсульованим) в Carоб'єкт . Але це IMHO лише друге найкраще рішення, оскільки воно збільшує загальну складність, а не зменшує його.


як щодо структури стратегії вирішення методу isQualityPass?
Лаїв

@Laiv: технічно це буде працювати, звичайно (див. Мою редакцію), але це призведе до більш складного Carоб'єкта. Це був би не мій перший вибір рішення (принаймні, не в контексті цього надуманого прикладу). Однак це може мати більше сенсу в "реальному" коді, я не знаю.
Док Браун

6

Отож, моє запитання: за принципом розбиття інтерфейсу, чи слід відокремлювати конкретні методи, якими користуються не всі клієнти, щоб ці методи залежали від клієнта лише тоді, коли клієнт насправді використовує, щоб зменшити зв'язок?

Принцип сегрегації інтерфейсу - це не заборона доступу до того, що вам не потрібно. Йдеться про не наполягати на доступі до того, що вам не потрібно.

Інтерфейси не належать класу, який їх реалізує. Вони належать об'єктам, які ними користуються.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Що тут використовується, так getTax()і є getCost(). Що бути наполягла на це все доступно через Car. Проблема полягає в тому, щоб наполягати на тому, що Carвона наполягає на доступі до isQualityPass()яких не потрібен.

Це можна виправити. Ви запитуєте, чи можна це конкретно виправити. Це може.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Жоден із цього коду навіть не знає, чи CarLiabilityце інтерфейс чи конкретний клас. Це гарна річ. Це не хоче знати.

Якщо це інтерфейс, він Carможе його реалізувати. Це не буде порушувати ISP , тому що навіть isQuality()в Car CarShopне наполягає на цьому. Це добре.

Якщо це конкретно, можливо, що isQuality()або не існує, або переїхав кудись кудись. Це добре.

Можливо, CarLiabilityце і конкретна обгортка, Carяка делегує їй роботу. До тих пір поки CarLiabilityне розкриває , isQuality()то CarShopце добре. Звичайно, це просто вибиває балончик по дорозі, і CarLiabilityвін повинен з'ясувати, як слідкувати за провайдером так Carсамо, як CarShopце потрібно було зробити.

Коротше кажучи, isQuality()його не потрібно видаляти з- Carза ISP. Мається на увазі потреба в isQuality()потребі, яку потрібно усунути, CarShopтому що CarShopвона не потрібна, тому вона не повинна просити її.


4

Чи застосовується принцип поділу інтерфейсу до конкретних методів?

Але як щодо конкретних методів? Чи слід відокремлювати методи, якими користувався би не кожен клієнт?

Не зовсім. Існують різні способи сховатися Car.isQualityPassвід CarShop.

1. Доступ до модифікаторів

З точки зору Закону Деметра , ми могли б вважати, Carа CardShopне бути друзями . Це легітимує нам робити наступне.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Будьте в курсі, що обидва компоненти знаходяться в різних пакетах. Зараз CarShopнемає видимості щодо Car захищеної поведінки. (Вибачте мене заздалегідь, якщо приклад вище виглядає так спрощено).

2. Інтерфейсна сегрегація

ISP працює з того , що ми працюємо з абстракціями, а ні з конкретними класами. Я вважаю, що ви вже знайомі з впровадженням провайдера і з інтерфейсами ролей .

Незважаючи на фактичну Carреалізацію, ніщо не заважає нам практикувати Інтернет.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

Що я тут зробив. Я звузив взаємодії Carі CarShopчерез роль інтерфейсу оплачуваної . Будьте в курсі змін на getPriceпідписі. Я навмисно змінив аргумент. Я хотів зробити очевидним, що CarShopлише "прив'язаний / прив'язаний" до одного з доступних інтерфейсів ролей . Я міг би стежити за реальною реалізацією, але я не знаю реальних деталей реалізації, і боюся, що фактичний getPrice(String carId)має доступ (видимість) для конкретного класу. Якщо вона є, вся робота, що виконується з провайдером послуг, стає марною, оскільки в розробниках належить робити кастинг та працювати лише з інтерфейсом Billable . Як би ми не були методичними, спокуса завжди буде там.

3. Єдина відповідальність

Боюся, я не в змозі сказати, чи залежність між Carі HttpClientдостатня, але я згоден з @DocBrown, це викликає деякі попередження, що варто переглянути проект. Ні Закон Деметера, ні Інтернет-провайдер не дозволять зробити ваш дизайн "кращим" на даний момент. Вони просто замаскують проблему, а не виправлять її.

Я запропонував DocBrown Стратегічну схему як можливе рішення. Я погодився з ним, що модель додає складності, але я також думаю, що будь - який перепроект буде. Це компроміс, чим більше розв’язки ми хочемо, тим більше рухомих деталей у нас (як правило). У будь-якому разі, я думаю, що обидва погоджуються з перепроектуванням вкрай доцільно.

Підводячи підсумки

Ні, вам не потрібно переміщувати конкретні методи до зовнішніх класів, щоб не зробити їх доступними. Тут може бути незліченна кількість споживачів. Чи могли б ви перенести всі конкретні методи на зовнішні класи кожного разу, коли новий споживач вступає в гру? Я сподіваюся, що ви цього не зробите.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.