Я думаю, что интерфейсы работают лучше всего, когда вы используете их для выражения того, что у объекта есть определенное свойство или поведение, которое охватывает несколько деревьев наследования и четко определено только для каждого класса.
Например, подумайте о сопоставимых. Если вы хотите создать класс Comparable, который будет расширен другими классами, он должен быть очень высоким в дереве наследования, возможно сразу после Object, и свойство, которое он выражает, состоит в том, что два объекта этого типа можно сравнивать, но есть нет способа определить это вообще (у вас не может быть реализации CompareTo непосредственно в классе Comparable, это отличается для каждого класса, который его реализует).
Классы работают лучше всего, когда они определяют что-то ясное, вы знаете, какие свойства и поведения у них есть, и имеете реальные реализации для методов, которые вы хотите передать дочерним элементам.
Таким образом, классы работают, когда вам нужно определить конкретный объект, такой как человек или автомобиль, и интерфейсы работают лучше, когда вам нужно более абстрактное поведение, которое слишком общее, чтобы принадлежать к любому дереву наследования, например, возможность сравниваться ) или для запуска (Runnable).