Для данных подкласса и суперкласса можно свободно подставлять экземпляр подкласса там, где требуется экземпляр суперкласса.

Например, класс UIButton является подклассом UlControl, который является подклассом UlView. Поэтому вполне корректным является следующий исходный текст:

UIButton* b “ [UIButton buttonWithType:UIButtonTypeSystem];
UlView* v = b;

Переменная Ъ объявлена как класс UIButton, но я присваиваю ее переменной, объявленной как класс UlView. Это вполне законно и приемлемо, поскольку класс UlView является предком (находится выше по цепочке суперклассов) класса UIButton. Говоря иначе, я веду себя так, как будто класс UIButton является классом UlView, и компилятор с этим согласен — именно потому, что класс UIButton действительно является классом UlView.

Однако при работе приложения важен не объявленный класс переменной, а фактический класс объекта, на который ссылается эта переменная. После того, как я присвоил экземпляр класса UIButton переменной v, объект, на который указывает переменная v, представляет собой класс UIButton. Так что, несмотря на то, что переменная v имеет тип данных, отличный от класса UIButton, не будет никаких проблем в отправке ей сообщений, соответствующих классу UIButton, поскольку фактически она представляет экземпляр класса UIButton. Например:

UIButton* b = [UIButton buttonWithType:UIButtonTypeSystem];
UlView* v = b;
[v setTitle:0"Howdy!" forState:UIControlStateNormal];

Класс UlView не может принимать сообщения setTitle: forState:. Тем не менее приведенный код вполне осмыслен и безопасен; переменная v может быть типизирована как простой класс UlView, но в действительности, при выполнении кода, она будет указывать на экземпляр класса UIButton, который может принимать сообщение setTitle: forState:. Однако компилятор ничего не знает о том, на что в действительности будет указывать переменная v во время выполнения приложения, так что (при наличии механизма ARC) он сообщает об ошибке компиляции. Заставить компилятор замолчать можно с помощью приведения типа получателя сообщения:

UIButton* b = [UIButton buttonWithType:UIButtonTypeSystem];
UlView* v = b;
[(UIButton*)v setTitle:@"Howdy!" forState:UIControlStateNormal];

Приведение успокаивает компилятор и он благополучно компилирует код, который отлично работает. Он работает не потому, что я выполнил приведение переменной v к типу UIButton (приведение типов не выполняет никаких реальных преобразований; это просто намек компилятору), а потому, что объект v действительно является экземпляром класса UIButton. Я выполнил приведение типа получателя сообщения, которое компилятор всегда принимает к сведению как истину в последней инстанции; но, кроме того, мое приведение говорит правду, поэтому, когда сообщение setTitle: forState: достигает объекта, на который указывает v> все отлично работает. С другой стороны, если бы v был на самом деле экземпляром класса UlView, но не класса UIButton, то программа аварийно завершилась бы.

Теперь давайте развернем ситуацию на 180 градусов. Мы назвали объект класс UIButton объектом класса UlView и отправили ему сообщение для объекта класса UIButton. Оставим теперь объект класса UIButton как объект класса UIButton, но отправим ему сообщение для объекта класса UlView.

То, чем в действительности является объект, зависит не только от его класса, но и от наследования этого класса. Сообщение является приемлемым, даже если класс объекта сам не реализует соответствующий метод, но при этом метод реализован где-то выше по цепочке суперклассов. Например:

UIButton* b = [UIButton buttonWithType:UIButtonTypeSystem];
[b setFrame: CGRectMake(100,100, 52, 30)];

Этот код нормально работает. Но вы не найдете метод setFrame: в документации класса UIButton. Это потому, что вы ищете в неверном месте. Класс UIButton является подклассом класса UlControl, а класс UlControl является подклассом класса UlView. Чтобы узнать о методе setFrame:, обратитесь к документации класса UlView. (Ну, если честно, все еще сложнее, и вы не найдете setFrame: и там. Но вы найдете термин frame, который является “свойством”, а это по сути то же самое, как я поясню позже в этой главе.) Так что сообщение setFrame: отправляется классом UIButton, но оно соответствует методу, определенному в классе UlView. Тем не менее все нормально работает, потому что объект класса UIButton является объектом класса UlView.

Еще одна распространенная ошибка начинающего программиста — обратиться к документации, но не пройтись при этом по цепочке суперклассов. Если вы хотите узнать, какие сообщения можно посылать UIButton, не ограничивайтесь документацией только класса UIButton: просмотрите также документацию класса UlControl, класса UlView и т.д.

Мы рассматривали объект UIButton как UlView, но (с помощью приведения типов) были в состоянии отправить ему сообщение UIButton. Мы рассматриваем объект UIButton как UIButton, но (по наследству) мы в состоянии отправить ему сообщение UlView.

Объект отвечает на отправленное ему сообщение не потому, что что-то в коде говорит, чем он является; он отвечает исходя из того, чем на самом деле он является при работе программы, и того, что сообщение в действительности отправляется именно этому объекту. Когда сообщение отправляется объекту, важно не то, как объявлена или приведена переменная, указывающая на этот объект, а то, объектом какого класса он в действительности является. То, чем в действительности является объект, зависит от его класса, а также цепочки наследования этого класса от суперклассов. Эти факты внутренне присущи объекту и не зависят от того, как ваш код характеризует переменную, указывающую на этот объект. Такое независимое поддержание целостности типа объекта является основой того, что называется полиморфизмом.

Но это не все, что касается полиморфизма. Чтобы понять полиморфизм полностью, мы должны пойти дальше и разобраться с динамикой отправки сообщения.


 

 

 

Добавить комментарий