До сих пор интерфейсы рассматривались с технической точки зрения — как их объявлять, какие конфликты могут возникать, как их разрешать. Однако важно понимать, как применяются интерфейсы с концептуальной точки зрения.
Распространенное мнение, что интерфейс — это полностью абстрактный класс, в целом верно, но оно не отражает всех преимуществ, которые дают интерфейсы объектной модели. Как уже отмечалось, множественное наследование порождает ряд конфликтов, но отказ от него, хоть и делает язык проще, но не устраняет ситуации, в которых требуются подобные подходы.
Возьмем в качестве примера дерева наследования классификацию живых организмов. Известно, что растения и животные принадлежат к разным царствам. Основным различием между ними является то, что растения поглощают неорганические элементы, а животные питаются органическими веществами. Животные делятся на две большие группы — птицы имлекопитающие. Предположим, что на основе этой классификации построено дерево наследования, в каждом классе определены элементы с учетом наследования от родительских классов.
Рассмотрим такое свойство живого организма, как способность питаться насекомыми. Очевидно, что это свойство нельзя приписать всей группе птиц, или млекопитающих, а тем более растений. Но существуют представители каждой из названных групп, которые этим свойством обладают, - для растений это росянка, для птиц, например, ласточки, а для млекопитающих - муравьеды. Причем, очевидно, ''реализовано" это свойство у каждого вида совсем по-разному.
Можно было бы объявить соответствующий метод (скажем, consumelnsect(lnsect)) у каждого представителя независимо. Но если задача состоит в моделировании, например, зоопарка, то однотипную процедуру кормление насекомыми — пришлось бы описывать для каждого рода отдельно, что существенно осложнило бы код, причем без какой-либо пользы.
Java предлагает другое решение. Объявляется интерфейс InsectConsumer:
public interface InsectConsumer { void consumelnsect(lnsect i);}
Его реализуют все подходящие животные и растения:
// росянка расширяет класс растение
public class Sundew extends Plant implements InsectConsumer { public void consumelnsect(lnsect i) {}}
// ласточка расширяет класс птица
public class Swallow extends Bird implements InsectConsumer { public void consumelnsect(lnsect i) {} }
// муравьед расширяет класс млекопитающее public class AntEater extends Mammal implements InsectConsumer {public void consumelnsect(lnsect i) {} }
врезультате в классе, моделирующем служащего зоопарка, можно объявить соответствующий метод:
// служащий, отвечающий за кормление, расширяет класс служащий class FeedWorker extends Worker {// с помощью этого метода можно накормить
// и росянку, и ласточку, и муравьеда
public void feedOnlnsects(lnsectConsumer consumer) {consumer.consumelnsect(insect);}}
Врезультате удалось свести работу с одним свойством трех разнородных классов в одно место, сделать код более универсальным. Обратите внимание, что при добавлении еще одного насекомоядного такая модель зоопарка не потребует никаких изменений, чтобы обслуживать новый вид, в отличие от первоначального громоздкого решения. Благодаря введению интерфейса удалось отделить классы, реализующие его (живые организмы) и использующие его (служащий зоопарка). После любых изменений этих классов при условии сохранения интерфейса их взаимодействие не нарушится.
Данный пример иллюстрирует, как интерфейсы предоставляют альтернативный, более строгий и гибкий подход вместо множественного наследования.