Классы-наследники
Теперь займёмся классами-наследниками от TLogElement. Поскольку у нас будет единственный элемент с одним входом (НЕ), сделаем его наследником прямо от TLogElement (не будем вводить специальный класс «элемент с одним входом»).
type TNot = class(TLogElement) protected procedure calc; override; end;
После слова class в скобках указано название базового класса. Все объекты класса TNot обладают всеми свойствами и методами класса TLogElement.
Новый класс переопределяет метод calc, на это указывает слово override (в переводе с англ. — перекрыть). Заметим, что у базового класса TLogElement этот метод не реализован — он абстрактный, поэтому в данном случае мы фактически программируем метод, объявленный в базовом классе. Для элемента «НЕ» он выглядит очень просто:
procedure TNot.calc; begin FRes := not In1 end;
Класс TNot уже не абстрактный, потому что абстрактный метод предка переопределён и теперь известно, что делать при вызове метода calc. Поэтому можно создавать объект этого класса и использовать его:
var n: TNot; ... n:=TNot.Create ; n. In1:=False; writeln(n.Res) ;
Остальные элементы имеют два входа и будут наследниками класса.
TLog2In = class(TLogElement) public property In2; end;
Единственное, что делает этот класс, — переводит свойство In2 в раздел public, т. е. делает его общедоступным. Отметим, что видимость можно только повышать, т. е. нельзя, например, в наследнике сделать общедоступное свойство класса-предка закрытым или защищённым.
Класс TLog2In — это тоже абстрактный класс, потому что он не переопределил метод calc. Это сделают его наследники TAnd (элемент И) и ТОr (элемент ИЛИ), которые определяют конкретные логические элементы:
type TAnd = class (TLog2ln} protected procedure calc; override; end ; TOr = class(TLog2In) protected procedure calc; override; end;
Реализация переопределённого метода calc для элемента «И» выглядит так:
procedure TAnd.calc; begin FRes := In1 and In2 end;
Для элемента «ИЛИ» этот метод определяется аналогично.
Обратим внимание на метод setln1, введённый в базовом классе:
procedure TLogElement.setln1(newln1: boolean); begin FIn1:=newln1; calc; end;
В нём вызывается метод calc, который пересчитывает значение на выходе логического элемента при изменении входа. Какой же метод будет вызван, если в базовом классе TLogElement он только объявлен, но не реализован?
Проблема в том, что для вызова любой процедуры нужно знать её адрес в памяти. Для обычных методов транслятор сразу записывает в машинный код нужный адрес, потому что он заранее известен. Это так называемое статическое связывание (связывание на этапе трансляции), при выполнении программы этот адрес не меняется.
В нашем случае адрес метода неизвестен: в классе TLogElement его нет вообще, а у каждого класса-наследника адрес метода calc свой собственный. Чтобы выйти из положения, используется динамическое связывание, т. е. адрес вызываемой процедуры определяется при выполнении программы, когда уже определён тип объекта, с которым мы работаем. Такой метод нужно объявлять виртуальным, что мы и сделали ранее. Это означает не только то, что его могут переопределять наследники, но и то, что будет использоваться динамическое связывание. Теперь можно дать полное определение виртуального метода.
Виртуальный метод — это метод базового класса, который могут переопределить классы-наследники, при этом конкретный адрес вызываемого метода определяется только при выполнении программы.
Теперь мы готовы к тому, чтобы создавать и использовать построенные логические элементы. Например, таблицу истинности для последовательного соединения элементов «И» и «НЕ» можно построить так:
Сначала создаются два объекта — логические элементы НЕ (класс TNot) и ИЛИ (класс TAnd). Далее в двойном цикле перебираются все возможные комбинации значений логических переменных А и В, они подаются на входы элемента И, а его выход — на вход элемента НЕ. Чтобы при выводе логических значений вместо False и True выводились более компактные обозначения 0 и 1, значения входов и выхода преобразуются к целому типу (integer).
Следующая страница Модульность