У меня сегодня был довольно интересный случай, когда кто-то задал мне вопрос по поводу этого кода:

interface Role { 
  canReadPayments(); 
  canCreatePayments();
}
class TechnicalUserRole implements Role {} 
class NoRightsRole implements Role {}

NoRightsRole - это нулевой объект, в котором для всех прав по умолчанию установлено значение false.

Мне задали вопрос - почему интерфейс? Почему бы не сделать так, чтобы NoRightsRole просто переопределял методы из TechnicalUserRole? Разве NoRightsRole не является конкретной ролью TechnicalUserRole, где все методы возвращают логическое значение false?

Точно нет.

Самый простой способ разрушить эту аналогию, согласно которой NoRightsRole является TechnicalUserRole, - это ввести другой вид роли. Скажем, например - EmployeeRole.

Если бы мы использовали ту же аналогию, разве NoRightsRole не является также конкретной реализацией EmployeeRole?

Теперь у вас есть иерархия наследования, которая больше не имеет смысла.

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

NoRightsRole, TechnicalUserRole и EmployeeRole реализуют Role, где Role описывает возможности , которые гарантированно предоставляет реализация.

Поэтому в этом случае лучше использовать интерфейсы. Используйте иерархию наследования только тогда, когда вы действительно видите связь is-a.

И не заставляйте меня начинать с использования иерархий наследования для повторного использования кода.