Классы, реализующие интерфейс, имеют больше методов

#java #design-patterns

#java #шаблоны проектирования

Вопрос:

У меня есть интерфейс:

 public interface IUser {
   public boolean login();
}
  

и два класса, которые реализуют этот интерфейс:

 public class UserA implements IUser{

   public boolean login() {
       System.out.println("login");
   }

  public void update() {
      System.out.println("update");
  }
}


public class UserB implements IUser {

   public boolean login() {
       System.out.println("login");
   }

   public void delete() {
       System.out.println("delete");
   }
}
  

Мой вопрос в том, создаю ли я пользователя:

 IUser user = new UserA();
  

пользовательский объект не имеет доступа к update () однако рекомендуется создавать объект с помощью интерфейса, мне было интересно, есть ли лучшая практика или шаблон проектирования, который я могу использовать (вместо того, чтобы использовать его)?

Комментарии:

1. Могу ли я спросить причину отказа от использования приведения?

2. «Почему бы не выполнить кастинг»: потому что это может привести к сбою. Если вызывающий объект точно не знает тип. И если он знает (и должен знать) тип, почему он вообще получает интерфейс? (Общие) вопросы здесь заключаются в том, кто должен создавать экземпляры, кто должен знать экземпляры под каким типом и что он делает с этими экземплярами…

3. Да, это может произойти, но в вопросе упоминается OP if I create a user... , поэтому я бы предположил, что Op является пользователем и знает о его типах 🙂

4. Менее аккуратным решением, но также заслуживающим упоминания, может быть an Adapter . В противном случае также существует возможность абстрактного базового класса с пустыми реализациями для update и delete . Однако безопасное приведение с if instanceof... может быть проще всего.

Ответ №1:

Хорошей практикой является «кодирование интерфейса».

Однако это работает только в том случае, если в этом интерфейсе есть все необходимые вам методы.

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

Комментарии:

1. Пожалуйста, предоставьте представление, поскольку я не эксперт, is casting considered as bad practice ? Я использую его без вины, если требуется, потому что, как бы тщательно мы ни разрабатывали, могут быть случаи, когда интерфейсы / суперклассы могут включать не все методы, и для доступа к этим методам необходимо использовать переменные Ref.

2. В вашем конкретном примере интерфейс (или подинтерфейс, или несвязанный интерфейс) может иметь update delete методы and . Эмпирическое правило: если у вас более одного класса, и люди, отличные от тех, кто создал экземпляр, должны вызывать для них методы, вам, вероятно, нужен интерфейс.

3. @Mustafasabir: да, приведение является признаком проблемы проектирования. Если метод принимает IUser в качестве аргумента, но завершается ошибкой с ClassCastException каждый раз, когда IUser не является пользователем, тогда метод должен принимать UserA в качестве аргумента.

Ответ №2:

Рекомендуется программировать на интерфейсе, когда коду нужен только тип интерфейса для работы. Если UserA для работы кода требуется объект типа, то вы должны объявить свою переменную как UserA:

 UserA user = new UserA();

...

// this methods does something applicable to all kinds of IUser
public void doSomething(IUser user) {
    ...
}

// this methods does something applicable only to UserA
public void doSomethingElse(UserA user) {
    ...
}
  

Ответ №3:

Это зависит от контекста вашего кода. Если ваша переменная user всегда указывает на UserA объект, и вам нужны эти операции, я думаю, что можно объявить переменную как UserA . Если это не так, я бы провел рефакторинг, чтобы гарантировать, что в области видимости переменной используется только один из конкретных типов (аналогично тому, что написал JB в своем ответе).

Кроме того, вы могли бы ввести еще два интерфейса UpdatableUser и DeleteableUser расширить IUser их.

Ответ №4:

Если вы знаете, что вам нужно использовать update метод, измените

     IUser user = new UserA();
  

Для:

     UserA user = new UserA();
  

Совет «код для интерфейса» не применяется, если вашему приложению необходимо использовать методы, которые предоставляются только классами.

Если вам нужно объявить тип переменной, как IUser и по другим причинам (например, для полиморфизма), то приведение типа является единственным реальным вариантом. Но имейте в виду, что вашему коду нужно будет иметь дело со случаем, когда IUser это НЕ UserA ТАК.


На самом деле, есть другой подход. Вы могли бы добавить update и delete к IUser , а в тех случаях, когда они не имеют смысла, реализовать их как:

     throw new UnsupportedOperationException("...");
  

Однако это просто замена одной проблемы (исключения приведения типов) на другую (исключения неподдерживаемых операций)… с дополнительным недостатком, заключающимся в том, что в точке, где вам нужно выполнить вызов, нет ничего, чтобы синтаксически указать, что существует потенциальная проблема.


Если вас интересуют «лучшие способы» для этого, взгляните на то, как Ceylon позволяет избежать проблемы исключений приведения типов, используя форму switch оператора для переключения типа объекта.

Ответ №5:

Обычно вы должны «кодировать интерфейс». Но вы можете использовать следующий оператор с вашим методом:

 UserA user = new UserA();
  

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

Ответ №6:

Возможно, вам следует переосмыслить, является ли метод update() и delete() методом, который должен использовать другой объект.

если они не будут использоваться другими, update() и delete() должны быть частными методами. Поэтому вам не следует изменять IUser.

Если update() и delete() будут вызываться в other , вы можете создать два интерфейса, которые можно обновлять и удалять. Затем в том месте, которое вы хотите вызвать, вы должны привести IUser к интерфейсу, который можно обновлять или удалять.