Применяется ли принцип разделения интерфейсов к конкретным методам?

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 () в Car, если я должен разделить isQualityPass () на новый класс:

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

чтобы уменьшить сцепление CarShop? Потому что я думаю, что если isQualityPass () нужна дополнительная зависимость, например:

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

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

ggrr
источник
2
Знает ли обычно автомобиль, когда он проходит «качество»? Или это может быть бизнес-правило, которое может быть заключено в себе?
Лайв
2
Поскольку слово интерфейс в ISP подразумевает, что это об интерфейсах . Поэтому, если у вас есть метод в вашем Carклассе, о котором вы не хотите, чтобы (все) пользователи знали, тогда создайте (более одного) интерфейс, который Carкласс реализует, который объявляет только методы, полезные в контексте интерфейсов.
Тимоти Траклле
@Laiv Я уверен, что мы скоро увидим машины, которые знают гораздо больше, чем это. ;)
унифицированный бутерброд моделирования
1
Автомобиль будет знать, что его производитель хочет, чтобы он знал. Volkswagen знает, о чем я говорю :-)
Laiv
1
Вы упоминаете интерфейс, но в вашем примере нет интерфейса. Мы говорим о превращении Car в интерфейс и какие методы включить в этот интерфейс?
Нил

Ответы:

6

В вашем примере CarShopэто не зависит от isQualityPassметода и не заставляет его создавать пустую реализацию. Там даже не задействован интерфейс. Так что термин «провайдер» здесь просто не подходит. И пока такой метод, как isQualityPassметод, который хорошо вписывается в Carобъект, не перегружая его дополнительными обязанностями или зависимостями, это нормально. Нет необходимости реорганизовывать открытый метод класса в другое место только потому, что существует один клиент, не использующий этот метод.

Тем не менее, создание доменного класса наподобие Carпрямой зависимости от чего-то подобного HttpClient, вероятно, также не очень хорошая идея, независимо от того, какие клиенты используют или не используют метод. Перемещение логики в отдельный класс CheckCarQualityPassпросто не называется «ISP», это называется «разделение интересов» . Задача многоразового автомобильного объекта, вероятно, не должна заключаться в том, чтобы делать какие-либо внешние HTTP-вызовы, по крайней мере, не напрямую, это ограничивает возможность повторного использования и, кроме того, слишком сильно проверяемость.

Если isQualityPassне может быть легко перемещен в другой класс, альтернативой было бы сделать Httpвызовы через абстрактный интерфейс, IHttpClientкоторый вводится во Carвремя создания, или путем внедрения всей стратегии проверки «QualityPass» (с инкапсулированным запросом Http) в Carобъект , Но это ИМХО только второе лучшее решение, поскольку оно увеличивает общую сложность, а не уменьшает ее.

Док Браун
источник
как насчет шаблона стратегии для разрешения метода 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за провайдера. Подразумеваемая потребность в isQuality()потребности должна быть удалена, CarShopпотому что CarShopона не нужна, поэтому она не должна просить об этом.

candied_orange
источник
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 работает из того , что мы работаем с абстракциями, а не с конкретными классами. Я предполагаю, что вы уже знакомы с реализацией ISP и с интерфейсами ролей .

Несмотря на фактическую Carреализацию, ничто не мешает нам практиковать ISP.

//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через роль интерфейса Billable . Будьте в курсе изменений на getPriceподписи. Я намеренно изменил аргумент. Я хотел сделать очевидным, что CarShopон привязан только к одному из доступных интерфейсов ролей . Я мог бы следить за фактической реализацией, но я не знаю реальных деталей реализации, и я боюсь, что у фактического getPrice(String carId)есть доступ (видимость) к конкретному классу. Если это так, вся работа, проделанная с провайдером, становится бесполезной, потому что разработчик может выполнять кастинг и работать только с интерфейсом Billable . Неважно, насколько мы методичны, искушение всегда будет.

3. Единственная ответственность

Я боюсь , что я не в состоянии сказать , если зависимость между Carи HttpClientадекватен, но я согласен с @DocBrown, это вызывает некоторые предупреждения , что стоит обзор дизайна. Ни Закон Деметры, ни Интернет-провайдер не сделают ваш дизайн «лучше» на этом этапе. Они просто замаскируют проблему, а не исправят ее.

Я предложил DocBrown шаблон стратегии в качестве возможного решения. Я согласился с ним, что шаблон добавляет сложности, но я также думаю, что любой редизайн будет. Это компромисс: чем больше мы хотим отделить, тем больше у нас движущихся частей (обычно). Во всяком случае, я думаю, что оба согласны с редизайном очень рекомендуется.

Подводя итоги

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

LAIV
источник