[Qt][С++] Как вызвать protected метод в экземпляре класса?
Есть QWizard, созданный в дизайнере. Необходимо сделать некоторые поля в его страницах обязательными для заполнения. Но метод QWizardPage::registerField(), который это делает — protected. В хидере ui-файла страница, на которой нужные поля, создается так:
Инициализируется в функции setupUi:
cfdefinition = new QWizardPage();
При попытке написать что-нибудь вроде переопределения виртуальной функции QWizardPage для этого экземпляра
Ругается, что cfdefinition не является именем класса. Может быть, нужно эту функцию где-нибудь в другом месте переопределить(сейчас пытался в хидере родительского класса — QWizard`a)? Или единственный возможный метод — отдельно проектировать каждую страницу, а потом их в присоединять не в дизайнере, а кодом? Правда, так слишком много придется переделывать
wingear ★★★★
06.09.09 12:33:10 MSD
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
это вам не скриптовые языки с открытыми классами 😉
> Или единственный возможный метод — отдельно проектировать каждую страницу,
да. или одну универсальную страницу, но всё равно свою, унаследовав от QWizardPage.
> а потом их в присоединять не в дизайнере, а кодом?
зачем кодом? можно и дизайнером.
arsi ★★★★★
( 06.09.09 12:41:51 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
> QWIzardPage *cfdefinition;
> cfdefinition::initializePage() < this->registerField(«name*»,m_ui->lname); >
> Ругается, что cfdefinition не является именем класса.
cfdefinition действительно не является именем класса. Это указатель на объект класса QWIzardPage.
xintrea ★
( 06.09.09 14:25:19 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
открыл для себя строку контекстного меню «promote to» — оказывается, виджеты можно наследовать от собственных классов:) Правда, QtCreator после обновления ubuntu до кармической коалы постоянно виснет(ставил на старую систему не из репов), сейчас новый накачу и проверю
wingear ★★★★
( 06.09.09 16:27:28 MSD ) автор топика
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
Компилировать с опцией -Dprotected=public
Reset ★★★★★
( 07.09.09 01:33:25 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
> Компилировать с опцией -Dprotected=public
tmp_tmp
( 07.09.09 01:50:11 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
юные программисты на си крест-накрест, бессмысленные и беспощадные =)
nuffsaid
( 07.09.09 03:27:34 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
Каков вопрос таков и ответ 🙂
Reset ★★★★★
( 07.09.09 11:34:03 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
собралось, правда все равно поле не выделяется как обязательное. Видимо,в qmake все-таки встроена защита от дурака)
wingear ★★★★
( 07.09.09 18:51:16 MSD ) автор топика
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
>собралось, правда все равно поле не выделяется как обязательное.
Ой-ёёё =) Делать «#define protected public» конечно не надо, protected-секции появляются неспроста, можно как раз порушить всю налаженную систему. Да и в остальном тексте такая замена может привести к сюрпризам.
Короче, вот ссылка для примера: http://doc.trolltech.com/4.5/qwizard.html#mandatory-fields Версия QT у тебя какая, кстати? В общем просто не знаю, что там может не работать, если не сможешь повторить пример, свой код давай (не весь, а имеющую отношение к делу часть).
>Видимо,в qmake все-таки встроена защита от дурака)
qmake ничего такого не умеет, это просто система сборки.
Как говорится, no offence, но я бы рекомендовал поизучать C++ отдельно от QT первое время, да и от ОС тоже. Никакая IDE с автодополнением понимания в голове не даст. Я к тому, что это разные базисы, разные наборы концепций. Ты явно что-то торопишься сделать методом кувалды, а с языком C++ это добром не кончится. С QT — тем более. 🙂 Плохо осваивать сразу всё в одной куче.
nuffsaid
( 07.09.09 19:52:50 MSD )
Re: [Qt][С++] Как вызвать protected метод в экземпляре класса?
благодарю. Ссылка не открылась,т.к. нормальный интернет сейчас не работает,а с мобильного доступны только гугол и л.о.р.-) Унаследовал класс от QWizardPage, переопределил метод как public, a потом — promote to и все заработало. Да,я не знаю с++, да и С не особо(только 1 глава Кернигана),но это не для себя,а хоздоговорные работы,в которые неожиданно перешел диплом. Хочется освоить С, vim и openGL, но /me клинический слоупок. Да и вообще не туда поступил
Как вызвать protected метод c
Все поля, методы и остальные компоненты класса имеют модификаторы доступа . Модификаторы доступа позволяют задать допустимую область видимости для компонентов класса. То есть модификаторы доступа определяют контекст, в котором можно употреблять данную переменную или метод.
В языке C# применяются следующие модификаторы доступа:
- private : закрытый или приватный компонент класса или структуры. Приватный компонент доступен только в рамках своего класса или структуры.
- private protected : компонент класса доступен из любого места в своем классе или в производных классах, которые определены в той же сборке.
- file : добавлен в версии C# 11 и применяется к типам, например, классам и структурам. Класс или структура с такми модификатором доступны только из текущего файла кода.
- protected : такой компонент класса доступен из любого места в своем классе или в производных классах. При этом производные классы могут располагаться в других сборках.
- internal : компоненты класса или структуры доступен из любого места кода в той же сборке, однако он недоступен для других программ и сборок.
- protected internal : совмещает функционал двух модификаторов protected и internal . Такой компонент класса доступен из любого места в текущей сборке и из производных классов, которые могут располагаться в других сборках.
- public : публичный, общедоступный компонент класса или структуры. Такой компонент доступен из любого места в коде, а также из других программ и сборок.
Стоит отметить, что эти модификаторы могут применяться как к компонентам класса, так и к компонентам структуры за тем исключением, что структуры не могут использовать модификаторы private protected , protected и protected internal , поскольку структуры не могут быть унаследованы.
Все классы и структуры, определенные напрямую вне других типов (классов и структур) могут иметь только модификаторы public , file или internal .
Мы можем явно задать модификатор доступа, а можем его и не указывать:
public class Person < string name; public Person(string name) < this.name = name; >public void Print() => Console.WriteLine($"Name: "); >
Если для компонентов не определен модификатор доступа, то по умолчанию для них применяется модификатор private . Например, в примере выше переменная name неявно будет иметь модификатор private .
Классы и структуры, которые объявлены без модификатора и которые расположены вне других типов, по умолчанию имеют доступ internal , а вложенные классы и структуры, как и остальные компоненты классов/структур имеют модификатор private . Например:
class Phone < struct Camera < >>
Здесь класс Phone не является вложенным ни в один другой класс/структуру, поэтому неявно имеет модификатор internal . А структура Camera является вложенной, поэтому, как и другие компоненты класса, неявно имеет модификатор private
Модификаторы в рамках текущего проекта
Посмотрим на примере и создадим следующий класс State:
class State < // все равно, что private string defaultVar; string defaultVar ="default"; // поле доступно только из текущего класса private string privateVar = "private"; // доступно из текущего класса и производных классов, которые определены в этом же проекте protected private string protectedPrivateVar = "protected private"; // доступно из текущего класса и производных классов protected string protectedVar = "protected"; // доступно в любом месте текущего проекта internal string internalVar = "internal"; // доступно в любом месте текущего проекта и из классов-наследников в других проектах protected internal string protectedInternalVar = "protected internal"; // доступно в любом месте программы, а также для других программ и сборок public string publicVar = "public"; // по умолчанию имеет модификатор private void Print() =>Console.WriteLine(defaultVar); // метод доступен только из текущего класса private void PrintPrivate() => Console.WriteLine(privateVar); // доступен из текущего класса и производных классов, которые определены в этом же проекте protected private void PrintProtectedPrivate() => Console.WriteLine(protectedPrivateVar); // доступен из текущего класса и производных классов protected void PrintProtected() => Console.WriteLine(protectedVar); // доступен в любом месте текущего проекта internal void PrintInternal() => Console.WriteLine(internalVar); // доступен в любом месте текущего проекта и из классов-наследников в других проектах protected internal void PrintProtectedInternal() => Console.WriteLine(protectedInternalVar); // доступен в любом месте программы, а также для других программ и сборок public void PrintPublic() => Console.WriteLine(publicVar); >
Так как класс State не имеет явного модификатора, по умолчанию он имеет модификатор internal , поэтому он будет доступен из любого места данного проекта, однако не будет доступен из других программ и сборок.
Класс State имеет шесть полей для каждого уровня доступа. Плюс одна переменная без модификатора, которая является закрытой (private) по умолчанию. А также определено семь методов с разными модификаторами, которые выводят значения соответствующих переменных на консоль. Поскольку все модификаторы позволяют использовать компоненты класса внутри данного класса, то и все переменные класса, в том числе закрытые, у нас доступны всем его методам, так как все находятся в контексте класса State.
Теперь посмотрим, как мы сможем использовать переменные класса State в другом классе, который, допустим, будет называться StateConsumer и который расположен в том же проекте :
class StateConsumer < public void PrintState() < State state = new State(); // обратиться к переменной defaultVar у нас не получится, // так как она имеет модификатор private и класс StateConsumer ее не видит Console.WriteLine(state.defaultVar); //Ошибка, получить доступ нельзя // то же самое относится и к переменной privateVar Console.WriteLine(state.privateVar); // Ошибка, получить доступ нельзя // обратиться к переменной protectedPrivateVar не получится, // так как класс StateConsumer не является классом-наследником класса State Console.WriteLine(state.protectedPrivateVar); // Ошибка, получить доступ нельзя // обратиться к переменной protectedVar тоже не получится, // так как класс StateConsumer не является классом-наследником класса State Console.WriteLine(state.protectedVar); // Ошибка, получить доступ нельзя // переменная internalVar с модификатором internal доступна из любого места текущего проекта // поэтому можно получить или изменить ее значение Console.WriteLine(state.internalVar); // переменная protectedInternalVar так же доступна из любого места текущего проекта Console.WriteLine(state.protectedInternalVar); // переменная publicVar общедоступна Console.WriteLine(state.publicVar); >>
Таким образом, в классе StateConsumer мы смогли только обратиться к переменным internalVar, protectedInternalVar и publicVar, так как их модификаторы позволяют использовать в данном контексте.
Аналогично дело обстоит и с методами:
class StateConsumer < public void PrintState() < State state = new State(); state.Print(); //Ошибка, получить доступ нельзя state.PrintPrivate(); // Ошибка, получить доступ нельзя state.PrintProtectedPrivate(); // Ошибка, получить доступ нельзя state.PrintProtected(); // Ошибка, получить доступ нельзя state.PrintInternal(); // норм state.PrintProtectedInternal(); // норм state.PrintPublic(); // норм >>
Здесь нам оказались доступны только три метода: PrintInternal, PrintProtectedInternal, PrintPublic, которые имееют соответственно модификаторы internal, protected internal, public.
Модификаторы в рамках сборок
Допустим, у нас есть проект (и соответственно сборка) MyLib, в которой определены три класса:
namespace MyLib; // класс доступен из других сборок public class PublicState < internal void PrintInternal() =>Console.WriteLine(«internal»); protected internal void PrintProtectedInternal() => Console.WriteLine(«protected internal»); public void PrintPublic() => Console.WriteLine(«public»); > // класс доступен только в текущей сборке — по умолчанию internal class DefaultState < >// класс доступен только в текущей сборке internal class InternalState
Здесь классы DefaultState и InternalState имеют модификатор internal , поэтому доступны только в текущем проекте.
Класс PublicState модификатором public доступен из других проектов. Однако его метод PrintInternal() доступен только в текущем проекте. Вне текущего проекта доступен только его метод PrintPublic и PrintProtectedInternal() (доступен в другом проекте только в классах-наследниках).
Допустим, мы подключаем сборку этого проекта MyLib в другой проект, где есть класс StateConsumer:
using MyLib; class StateConsumer < public void PrintState() < // Ошибка DefaultState - по умолчанию internal, поэтому нет доступа DefaultState defaultState = new DefaultState(); // Ошибка InternalState - internal, поэтому нет доступа InternalState internalState = new InternalState(); // норм, PublicState - public, доступен из других программ PublicState publicState = new PublicState(); // Ошибка, нет доступа - метод доступен только в свой сборке publicState.PrintInternal(); // Ошибка, нет доступа - StateConsumer НЕ является классом-наследником от класса PublicState, // поэтому метод не доступен publicState.PrintProtectedInternal(); // нет доступа // норм - общедоступный метод publicState.PrintPublic(); // норм >>
В классе StateConsumer есть доступ только к классу PublicState и его методу PrintPublic , потому что они имеют модификатор public . К остальной функциональности подключенной сборки StateConsumer доступа не имеет.
Благодаря такой системе модификаторов доступа можно скрывать некоторые моменты реализации класса от других частей программы.
Файл как область видимости
C# 11 был добавлен еще один модификатор видимости — file , который применяется к классам, структурам, делегатам, перечислениям, интерфейсам. Типы с этим модификатором могут использоваться только внутри текущего файла кода.
file class Person
Данный модификатор не может использоваться в паре с другими модификаторами.
Управление доступом к членам (C++)
Элементы управления доступом позволяют отделять public интерфейс класса от private сведений о реализации и protected членов, которые используются только производными классами. Спецификатор доступа действует для всех членов, объявленных после него, пока не будет объявлен следующий спецификатор доступа.
class Point < public: Point( int, int ) // Declare public constructor.; Point();// Declare public default constructor. int &x( int ); // Declare public accessor. int &y( int ); // Declare public accessor. private: // Declare private state variables. int _x; int _y; protected: // Declare protected function for derived classes only. Point ToWindowCoords(); >;
Доступ по умолчанию находится private в классе и public в структуре или союзе. Спецификаторы доступа класса могут использоваться любое количество раз и в любом порядке. Выделение хранилища для объектов типов классов зависит от реализации. Однако компиляторы должны гарантировать назначение элементов последовательно более высоким адресам памяти между описателями доступа.
Управление доступом к членам
Тип доступа | Значение |
---|---|
private | Члены класса, объявленные как private могут использоваться только функциями-членами и друзьями (классами или функциями) класса. |
protected | Члены класса, объявленные как protected могут использоваться функциями-членами и друзьями (классами или функциями) класса. Кроме того, они могут использоваться производными классами данного класса. |
public | Члены класса, объявленные как public можно использовать любой функцией. |
Управление доступом помогает предотвратить использование объектов способами их использования. Эта защита теряется при выполнении явных преобразований типов (приведения).
Управление доступом одинаково применимо ко всем именам: функциям-членам, данным члена, вложенным классам и перечислителям.
Управление доступом в производном классе
Доступность в производном классе членов базового класса и унаследованных членов определяется двумя следующими факторами.
- Объявляет ли производный класс базовым классом public с помощью описателя доступа.
- Какой доступ к членам предоставляется в базовом классе.
В следующей таблице показана взаимосвязь между этими факторами и определен доступ к членам в базовом классе.
Доступ к членам в базовом классе
private | protected | public |
---|---|---|
Всегда недоступно с любым доступом к производным данным | private в производном классе, если используется private производное | private в производном классе, если используется private производное |
protected в производном классе, если используется protected производное | protected в производном классе, если используется protected производное | |
protected в производном классе, если используется public производное | public в производном классе, если используется public производное |
В следующем примере показано наследование доступа:
// access_specifiers_for_base_classes.cpp class BaseClass < public: int PublicFunc(); // Declare a public member. protected: int ProtectedFunc(); // Declare a protected member. private: int PrivateFunc(); // Declare a private member. >; // Declare two classes derived from BaseClass. class DerivedClass1 : public BaseClass < void foo() < PublicFunc(); ProtectedFunc(); PrivateFunc(); // function is inaccessible >>; class DerivedClass2 : private BaseClass < void foo() < PublicFunc(); ProtectedFunc(); PrivateFunc(); // function is inaccessible >>; int main() < DerivedClass1 derived_class1; DerivedClass2 derived_class2; derived_class1.PublicFunc(); derived_class2.PublicFunc(); // function is inaccessible >
В DerivedClass1 , функция-член PublicFunc является public членом и ProtectedFunc является protected членом, так как BaseClass является базовым классом public . PrivateFunc BaseClass имеет значение private , и недоступно для всех производных классов.
В DerivedClass2 функциях PublicFunc и ProtectedFunc считаются private членами, так как BaseClass это базовый private класс. Опять же, это значит, PrivateFunc что private BaseClass он недоступен для любых производных классов.
Производный класс можно объявить без спецификатора доступа базового класса. В таком случае производный класс считается, private если объявление производного класса использует class ключевое слово. Производные считаются, public если объявление производного класса использует struct ключевое слово. Например, приведенный ниже код
class Derived : Base .
class Derived : private Base .
struct Derived : Base .
struct Derived : public Base .
Члены, объявленные как private имеющие доступ, недоступны для функций или производных классов, если эти функции или классы не объявлены с помощью friend объявления в базовом классе.
Тип union не может иметь базовый класс.
При указании private базового класса рекомендуется явно использовать private ключевое слово чтобы пользователи производного класса понимали доступ к члену.
Управление доступом и статические члены
При указании базового класса как private он влияет только на нестатические члены. Открытые статические члены по-прежнему доступны в производных классах. Однако доступ к членам базового класса с помощью указателей, ссылок или объектов может потребовать преобразования, который снова применяет управление доступом. Рассмотрим следующий пример:
// access_control.cpp class Base < public: int Print(); // Nonstatic member. static int CountOf(); // Static member. >; // Derived1 declares Base as a private base class. class Derived1 : private Base < >; // Derived2 declares Derived1 as a public base class. class Derived2 : public Derived1 < int ShowCount(); // Nonstatic member. >; // Define ShowCount function for Derived2. int Derived2::ShowCount() < // Call static member function CountOf explicitly. int cCount = ::Base::CountOf(); // OK. // Call static member function CountOf using pointer. cCount = this->CountOf(); // C2247: 'Base::CountOf' // not accessible because // 'Derived1' uses 'private' // to inherit from 'Base' return cCount; >
В предыдущем примере кода управление доступом запрещает преобразование указателя на Derived2 к указателю на Base . Указатель this неявно имеет тип Derived2 * . Чтобы выбрать функцию CountOf , this необходимо преобразовать в тип Base * . Такое преобразование не допускается, так как Base является косвенным базовым классом Derived2 private. Преобразование в private тип базового класса допустимо только для указателей на непосредственные производные классы. Поэтому указатели типа Derived1 * можно преобразовать в тип Base * .
Явный вызов CountOf функции без использования указателя, ссылки или объекта для его выбора не подразумевает преобразования. Именно поэтому вызов разрешен.
Члены и друзья производного класса T , могут преобразовать указатель в указатель T на прямой базовый private класс T .
Доступ к виртуальным функциям
Управление доступом, применяемое к virtual функциям, определяется типом, используемым для вызова функции. Переопределение объявлений функции не влияет на управление доступом для данного типа. Например:
// access_to_virtual_functions.cpp class VFuncBase < public: virtual int GetState() < return _state; >protected: int _state; >; class VFuncDerived : public VFuncBase < private: int GetState() < return _state; >>; int main() < VFuncDerived vfd; // Object of derived type. VFuncBase *pvfb = &vfd; // Pointer to base type. VFuncDerived *pvfd = &vfd; // Pointer to derived type. int State; State = pvfb->GetState(); // GetState is public. State = pvfd->GetState(); // C2248 error expected; GetState is private; >
В предыдущем примере вызов виртуальной функции GetState с помощью указателя на вызовы VFuncDerived::GetState типов VFuncBase и GetState рассматривается как public . Однако вызов GetState с помощью указателя типа VFuncDerived является нарушением управления доступом, так как GetState объявляется private в классе VFuncDerived .
Виртуальную функцию GetState можно вызвать с помощью указателя на базовый класс VFuncBase . Это не означает, что вызываемая функция является базовой версией этой функции.
Управление доступом с множественным наследованием
В решетках множественного наследования, в которых используются виртуальные базовые классы, к конкретным именам можно обращаться несколькими путями. Поскольку в разных путях могут применяться разные средства управления доступом, компилятор выбирает тот путь, который позволяет получить наиболее широкий доступ. См. следующий рисунок:
На схеме показана следующая иерархия наследования: класс VBase является базовым классом. Класс LeftPath наследует от VBase с помощью виртуальной виртуальной private базы данных. Класс RightPath также наследует от VBase, но с помощью виртуальной виртуальной public базы данных. Наконец, класс Производный наследует от класса LeftPath и класса RightPath с помощью public LeftPath, public RightPath.
Доступ вдоль линий графа наследования
На этом рисунке обращение к имени, которое было объявлено в классе VBase , всегда будет выполняться через класс RightPath . Правильный путь является более доступным, так как RightPath объявляется VBase как базовый public класс, а LeftPath объявляется VBase как private .
Модификаторы private и protected, переопределение методов, полиморфизм
Продолжаем рассматривать возможности механизма наследования классов и это занятие начнем с рассмотрения того, что и как наследуется в дочерних классах. В качестве примера возьмем два класса и в базовом классе будет закрытое поле id:
class Properties { private int id; int width, color; Properties() { System.out.println("Конструктор Properties"); } } class Line extends Properties { double x1, y1; double x2, y2; Line() { System.out.println("Конструктор Line"); } }
Теперь, обращаясь к полю id из дочернего класса:
class Line extends Properties { … Line() { System.out.println("Конструктор Line, + id); } }
возникнет ошибка. То же самое и с методами. Если в базовом классе определить какой-либо метод, например, сеттер:
class Properties { … void setProp(int width, int color) { this.width = width; this.color = color; } … }
то он автоматически унаследуется дочерним. Но, если указать модификатор private:
private void setProp(int width, int color) { … }
то он будет недоступен в дочернем классе Line. Все остальные режимы доступа разрешают наследование элементов класса. Вот так можно управлять механизмом наследования с помощью модификатора доступа private.
Режим доступа protected
Но здесь возникает вопрос: а как задавать поля или методы, которые бы могли наследоваться, но к которым нельзя было бы обратиться извне? Как мы видели, режим private для этого не подходит – он запрещает наследование элементов. Здесь нам на помощь приходит другой режим доступа под названием protected. Он разрешает наследование элементов, но закрывает внешний доступ к полям и методам, если класс определен в другом пакете. В текущем пакете внешний доступ к полю или методу protected будет разрешен. Например, если пометить поле id как protected, то оно унаследуется дочерним классом Line:
class Properties { protected int id; . } class Line extends Properties { double x1, y1; double x2, y2; Line() { id = 1; System.out.println("Конструктор Line с style="color: #339933;">+id); } }
Теперь, при запуске программы мы увидим значение При этом, извне мы также можем обратиться к этому полю, так как находимся в текущем пакете:
line.id = 5; // ошибки нет
Но, если переместить определение класса Line в другой пакет, то прямого доступа к id уже не будет и строчка:
line.id = 5; // будет ошибка
выдаст ошибку. Вот так (в отличие от некоторых других языков программирования) работает модификатор protected в Java.
Переопределение методов и динамическая диспетчеризация
В языке Java мы можем не только наследовать методы базового класса и использовать их в дочерних, но и переопределять их. Например, объявим в базовом классе Properties метод draw() для рисования графического примитива. Очевидно, что базовый класс «не знает» о конкретном примитиве, поэтому этот метод будет иметь некую общую реализацию:
class Properties { . void draw() { System.out.println("Этот метод следует переопределить"); } . }
Если теперь его вызвать из объекта класса Line:
line.draw();
то увидим ожидаемое сообщение. Но, переопределив этот метод в дочернем классе Line:
@Override void draw() { System.out.println("Рисование линии"); }
Получаем уже вызов этого переопределенного метода. (Здесь нотация @Override – это необязательное указание переопределения метода. Ее можно не записывать, но правило хорошего тона программирования предполагает ее использование).
Вообще, переопределение метода кажется вполне очевидным и потому напрашивается вопрос: зачем вообще что-то определять в базовом классе? Давайте просто записывать методы в дочерних и они также будут вызываться с уже нужной реализацией? Да, и часто именно так и следует поступать. Но переопределенный метод draw() имеет одну важную особенность: если мы его будем вызывать через ссылку на базовый класс:
Properties p = line; p.draw();
то вызовется не метод базового класса, а переопределенный метод дочернего класса. Такой эффект возникает благодаря работе механизма под названием «динамическая диспетчеризация методов». Логика его работы проста: если он видит, что метод базового класса имеет переопределение в дочернем, то вызов переходит именно к последнему, переопределенному методу:
Здесь в обоих случаях будет вызван метод дочернего класса Line, т.к. он имеет последнее переопределение метода draw() базовых классов.
Полиморфизм
В частности, благодаря динамической диспетчеризации реализуется полиморфизм классов в Java. Здесь единый интерфейс draw() может иметь множество реализаций через переопределения в дочерних классах. В результате, можно выполнять рисование графических примитивов через ссылки Properties на базовый класс:
final int N = 2; Properties p[] = new Properties[N]; p[0] = new Line(); p[1] = new Triangle(); for(int i = 0;i N;++i) p[i].draw();
Видите, как это удобно. Мы определили один метод draw() и, затем, через него работаем со множеством графических примитивов. Мало того, если в будущем потребуется добавить еще классы примитивов, например, дугу или круг, то менять основную логику программы не придется. Все изменения на себя возьмет динамическая диспетчеризация. В этом сила третьего кита ООП по имени полиморфизм.
Запрет наследования через final
В ряде случаев нам может потребоваться запретить наследование классов и переопределение методов. Например, определим сеттер setProp() в базовом классе. И мы бы не хотели, чтобы программист намеренно или случайно мог сделать переопределение этого метода. Модификатор private в этом случае нам не подойдет, т.к. предполагается доступ к сеттеру извне класса. Как же поступить? Здесь на помощь нам приходит ключевое слово final, которое не ограничивает режим доступа, но запрещает дальнейшее изменение метода. То есть, если определить сеттер вот так:
class Properties { . final void setProp(int width, int color) { this.width = width; this.color = color; } . }
то переопределить его в дочерних классах уже будет невозможно и запись:
class Line extends Properties { . void setProp(int width, int color) { } . }
приведет к ошибке. Или же мы можем запретить наследование всего класса, указав перед его определением это ключевое слово:
также приведет к ошибке уже при наследовании. Так работает ключевое слово final с методами и классами. Но, если записать final перед переменной класса, то она, конечно, не будет унаследована, но может быть определена (точно такая же переменная) в дочернем классе:
class Properties { final int id = 5; . } class Line extends Properties { int id = 5; . }
Все работает. Разумеется, переменные id в обоих классах – это совершенно разные переменные.
Вот так с помощью ключевого слова final можно управлять механизмом наследования.
Путь кодера
Подвиг 1. Запишите базовый класс Graph для представления графиков с полями: массив из N значений (значения графика), название. И производные от него классы: LineGraph (для линейного графика), Chart (для круговой диаграммы), Bar (для столбикового графика). В дочерних классах следует реализовать перегрузку метода draw() базового класса Graph для рисования графика в соответствующем виде (рисование – это вывод в консоль сообщения, что рисуется такой-то график с такими-то значениями). Создать несколько экземпляров дочерних классов со ссылками на них типа Graph. Через эти ссылки вызвать метод draw() и убедиться в работоспособности механизма динамической диспетчеризации (вызовов методов из дочерних классов).
- добавление/удаление книги из библиотеки;
- вывод информации по всем книгам;
- поиск книг по автору, а также по году издания.