ПИТОН. Язык Python 46. Что такое ооп
Скачать 0.64 Mb.
|
§ 49. Скрытие внутреннего устройства Во время построения объектной модели задачи мы выделили отдельные объекты, которые для обмена данными друг с другом используют интерфейс – внешние свойства и методы. При этом все внутренние данные и детали внутреннего устройства объекта должны быть скрыты от «внешнего мира». Такой подход позволяет • обезопасить внутренние данные (поля) объекта от изменений (возможно, разрушительных) со стороны других объектов; • проверять данные, поступающие от других объектов, на корректность, тем самым повышая на- дежность программы; • переделывать внутреннюю структуру и код объекта любым способом, не меняя его внешние характеристики (интерфейс); при этом никакой переделки других объектов не требуется. Разберем простой пример. Во многих системах программирования есть класс, описываю- щий свойства «пера», которое используется при рисовании линий в графическом режиме. Назо- вем этот класс TPen, в простейшем варианте он будет содержать только одно поле color, кото- рое определяет цвет. Будем хранить код цвета в виде символьной строки, в которой записан ше- стнадцатеричный код составляющих модели RGB. Например, "FF00FF" – это фиолетовый цвет, потому что красная (R) и синяя (B) составляющие равны FF 16 = 255, а зелёной составляющей нет вообще. Класс можно объявить так: class TPen: def __init__ ( self ): self.color = "000000" По умолчанию в Python все члены класса (поля и методы) открытые, общедоступные (англ. public). Имена тех элементов, которые нужно скрыть, должны начинаться с двух знаков подчёркивания, например, так: class TPen: def __init__ ( self ): self.__color = "000000" В этом примере поле __color закрытое (англ. private – частный). К закрытым полям нельзя обра- титься извне (это могут делать только методы самого объекта), поэтому теперь невозможно не только изменить внутренние данные объекта, но и просто узнать их значения. Чтобы решить эту проблему, нужно добавить к классу еще два метода: один из них будет возвращать текущее зна- чение поля __color, а второй – присваивать полю новое значение. Эти методы доступа назовем getColor (англ. получить Color) и setColor (англ. установить Color): Скрытие внутреннего устройства объектов называют инкапсуляцией («помещение в капсулу»). Инкапсуляцией также называют объединение данных и методов работы с ними в одном объекте. Задачи Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 9 http://kpolyakov.spb.ru 21.04.2017 class TPen: def __init__ ( self ): self.__color = "000000" def getColor ( self ): return self.__color def setColor ( self, newColor ): if len (newColor) != 6 : self.__color = "000000" else : self.__color = newColor Что же улучшилось в сравнении с первым вариантом (когда поле было открытым)? Согласно принципам ООП, внутренние поля объекта должны быть доступны только с помощью методов. В этом случае внутреннее представление данных может как угодно отличаться от того, как другие объекты «видят» эти данные. В простейшем случае метод getColor просто возвращает значение поля (см. текст про- граммы выше). В методе setColor мы можем обрабатывать ошибки, не разрешая присваивать полю недопустимые значения. Например, в нашем методе требуется, чтобы символьная строка с кодом цвета состояла из шести символов. Если это не так, в поле __color записывается код чёр- ного цвета "000000". Теперь, если pen – это объект класса TPen, то для установки и чтения его цвета нужно ис- пользовать показанные выше методы: pen = TPen() pen.setColor ( "FFFF00" ) # изменение цвета ( "цвет пера:" , pen.getColor() ) # получение цвета Итак, мы скрыли (защитили) внутренние данные, но одновременно обращение к свойствам стало выглядеть довольно неуклюже: вместо pen.color="FFFF00" теперь нужно писать pen.setColor("FFFF00"). Чтобы упростить запись, во многие объектно-ориентированные языки программирования ввели понятие свойства (англ. property), которое внешне выглядит как переменная объекта, но на самом деле при записи и чтении свойства вызываются методы объек- та. Свойство сolor в нашем случае можно определить так: class TPen: def __init__ ( self ): self.__color = "000000" def __getColor ( self ): return self.__color def __setColor ( self, newColor ): if len(newColor) != 6 : self.__color = "000000" else : self.__color = newColor color = property ( __getColor, __setColor ) Здесь добавили два подчеркивания в начало названий методов getColor и setColor. Поэтому они стали защищёнными (англ. private – частный), то есть закрыты от других объектов. Однако есть общедоступное свойство (англ. property) с названием сolor: color = property ( __getColor, __setColor ) При чтении этого свойства вызывается метод __getColor, а при записи нового значения – ме- тод __setColor. В программе можно использовать это свойство так: pen.color = "FFFF00" # изменение цвета ( "цвет пера:" , pen.color ); # получение цвета Свойство – это способ доступа к внутреннему состоянию объекта, имитирующий обращение к его внутренней переменной. Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 10 http://kpolyakov.spb.ru 21.04.2017 Поскольку приведенная выше функция getColor просто возвращает значение поля __color и не выполняет никаких дополнительных действий, можно было вообще удалить метод __getColor и вместо него использовать «лямбда-функцию»: class TPen: def __init__ ( self ): self.__color = "000000" def __setColor ( self, newColor ): if len (newColor) != 6 : self.__color = "000000" else : self.__color = newColor color = property ( lambda x: x.__color , __setColor ) Эта «лямбда-функция» принимает единственный параметр-объект, который называется x, и воз- вращает его поле __color. Таким образом, с помощью свойства color другие объекты могут изменять и читать цвет объектов класса TPen. Для обмена данными с «внешним миром» важно лишь то, что свойство color – символьного типа, и оно содержит 6-символьный код цвета. При этом внутреннее уст- ройство объектов TPen может быть любым, и его можно менять как угодно. Покажем это на при- мере. Хранение цвета в виде символьной строки неэкономно и неудобно, поэтому часто исполь- зуют числовые коды цвета. Будем хранить код цвета в поле __color как целое число: self.__color = 0 При этом необходимо поменять методы __getColor и __setColor, которые непосредственно работают с этим полем: class TPen: def __init__ ( self ): self.__color = 0 def __getColor ( self ): return "{:06x}" . format ( self.__color ) def __setColor ( self, newColor ): if len (newColor) != 6 : self.__color = 0 else : self.__color = int ( newColor, 16 ) color = property ( __getColor, __setColor ) Для перевода числового кода в символьную запись используется функция format. Формат «06x» означает «вывести значение в шестнадцатеричной системе ( x) в 6 позициях (6), свободные пози- ции слева заполнить нулями ( 0)». Для обратного преобразования применяем функцию int, кото- рой передаётся второй аргумент – основание системы счисления. В этом примере мы принципиально изменили внутреннее устройство объекта – заменили строковое поле на целочисленное. Однако другие объекты даже не «догадаются» о такой замене, потому что сохранился интерфейс – свойство color по-прежнему имеет строковый тип. Таким образом, инкапсуляция позволяет как угодно изменять внутреннее устройство объектов, не затра- гивая интерфейс. При этом все остальные объекты изменять не требуется. Иногда не нужно разрешать другим объектам менять свойство, то есть требуется сделать свойство «только для чтения» (англ. read-only). Пусть, например, мы строим программную модель автомобиля. Как правило, другие объекты не могут непосредственно менять его скорость, однако могут получить информацию о ней – «прочитать» значение скорости. При описании такого свойст- ва метода записи (второй аргумент в объявлении свойства) не указывают вообще (или указывает- ся пустое значение None): class TCar: def __init__ ( self ): self.__v = 0 v = property ( lambda x: x.__v ) Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 11 http://kpolyakov.spb.ru 21.04.2017 Таким образом, доступ к внутренним данным объекта возможен, как правило, только с по- мощью методов. Применение свойств (property) очень удобно, потому что позволяет использо- вать ту же форму записи, что и при работе с общедоступной переменной объекта. При использовании скрытия данных длина программы чаще все- го увеличивается, однако мы получаем и важные преимущества. Код, связанный с объектом, разделен на две части: общедоступную часть и закрытую. Их можно сравнить с надводной и подводной частью айс- берга. Объект взаимодействует с другими объектами только с помо- щью своих общедоступных свойств и методов (интерфейс). Поэтому при сохранении интерфейса можно как угодно менять внутреннюю структуру данных и код методов, и это никак не будет влиять на другие объекты. Подчеркнем, что все это становится действительно важно, когда разрабатывается большая программа и необходимо обеспечить ее надёжность. 1. Что такое «интерфейс объекта»? 2. Что такое инкапсуляция? Каковы ее цели? 3. Чем отличаются общедоступные и скрытые данные и методы в описании классов? 4. Почему рекомендуют делать доступ к полям объекта только с помощью методов? 5. Что такое свойство? Зачем во многие языки программирования введено это понятие? 6. Почему методы доступа, которые использует свойство, делают зарытыми? 7. Зачем нужны свойства «только для чтения»? Приведите примеры. 8. Подумайте, в каких ситуациях может быть нужно свойство «только для записи» (которое нельзя прочитать)? Подумайте, как ввести такое свойство в описание класса? 1. Измените построенную ранее программу моделирования движения так, чтобы все поля у объектов были закрытыми. Используйте свойства для доступа к данным. § 50. Иерархия классов Классификации Как в науке, так и в быту, важную роль играет классификация – разделение изучаемых объ- ектов на группы (классы), объединенные общими признаками. Прежде всего, это нужно для того, чтобы не запутаться в большом количестве данных и не описывать каждый объект заново. Например, есть много видов фруктов 2 (яблоки, груши, бананы, апельсины и т.д.), но все они обладают некоторыми общими свойствами. Если перевести этот пример на язык ООП, класс Ябло- ко – это подкласс (производный класс, класс-наследник, потомок) класса Фрукт, а класс Фрукт – это базовый класс (суперкласс, класс-предок) для класса Яблоко (а также для классов Груша, Ба- нан, Апельсин и других). Стрелка с белым наконечником на схеме обозначает наследование. Например, класс Яблоко – это наследник класса Фрукт. 2 Фруктами называют сочные съедобные плоды деревьев и кустарников. Задачи ? Контрольные вопросы Яблоко Груша Банан Апельсин базовый класс Фрукт классы-наследники свойства методы Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 12 http://kpolyakov.spb.ru 21.04.2017 Классический пример научной классификации – классификация животных или растений. Как вы знаете, она представляет собой иерархию (многоуровневую структуру). Например, горный кле- вер относится к роду Клевер семейства Бобовые класса Двудольные и т.д. Говоря на языке ООП, класс Горный клевер – это наследник класса Клевер, а тот, в свою очередь, наследник класса Бо- бовые, который также является наследником класса Двудольные и т.д. Например, можно сказать, что яблоко – это фрукт, а горный клевер – одно из растений се- мейства Двудольные. В то же время мы не можем сказать, что «машина – это разновидность двигателя», поэтому класс Машина не является наследником класса Двигатель. Двигатель – это составная часть ма- шины, поэтому объект класса Машина содержит в себе объект класса Двигатель. Отношения ме- жду двигателем и машиной – это отношение «часть – целое» Иерархия логических элементов Рассмотрим такую задачу: составить программу для моделирования управляющих схем, по- строенных на логических элементах (см. главу 3 в учебнике 10 класса). Нам нужно «собрать» за- данную схему и построить ее таблицу истинности. Как вы уже знаете, перед тем, как программировать, нужно выполнить объектно- ориентированный анализ. Все объекты, из которых состоит схема – это логические элементы, од- нако они могут быть разными («НЕ», «И», «ИЛИ» и другие). Попробуем выделить общие свойства и методы всех логических элементов. Ограничимся только элементами, у которых один или два входа. Тогда иерархия классов может выглядеть так: Среди всех элементов с двумя входами мы показали только элементы «И» и «ИЛИ», остальные вы можете добавить самостоятельно. Итак, для того, чтобы не описывать несколько раз одно и то же, классы в программе должны быть построены в виде иерархии. Теперь можно дать классическое определение объектно- ориентированного программирования: Базовый класс Построим первый вариант описания класса Логический элемент ( TLogElement). Обозначим его входы как In1 и In2, а выход назовем Res (от англ. result – результат). Здесь состояние логического элемента оп- ределяется тремя величинами ( In1, In2 и Res). С помощью такого базо- вого класса можно моделировать не только статические элементы (как «НЕ», «И», «ИЛИ» и т.п.), но и элементы с памятью (например, триггеры). Для сохранения значений входов и запоминания выхода введём три поля, принимающих логические значения и заполним их в конструкторе: Класс Б является наследником класса А, если можно сказать, что Б – это разновидность А. Объектно-ориентированное программирование – это такой подход к программированию, при котором программа представляет собой множество взаимодействующих объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследова- ния. Логический элемент с одним входом с двумя входами ИЛИ И НЕ ЛогЭлемент In1 (вход 1) In2 (вход 2) Res (результат) calc Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 13 http://kpolyakov.spb.ru 21.04.2017 class TLogElement: def __init__ ( self ): self.__in1 = False self.__in2 = False self._res = False Названия полей __in1 и __in2 начинаются с двух подчеркиваний, поэтому они будут скрытыми (доступны только внутри методов класса TLogElement). Имя поля _res начинается с одного подчёркивания, то есть оно не скрывается. В то же время одно подчёркивание говорит о его спе- циальной роли, мы вернёмся к этому чуть позже. Для доступа к полям введём свойства In1, In2 и Res (свойство только для чтения): class TLogElement: def __init__ ( self ): self.__in1 = False self.__in2 = False self._res = False def __setIn1 ( self, newIn1 ): self.__in1 = newIn1 self.calc() def __setIn2 ( self, newIn2 ): self.__in2 = newIn2 self.calc() In1 = property ( lambda x: x.__in1, __setIn1 ) In2 = property ( lambda x: x.__in2, __setIn2 ) Res = property ( lambda x: x._res ) Методы чтения для всех свойств записаны как «лямбда-функции», они выполняют прямой доступ к соответствующим полям. Вы, наверное, заметили, что оба метода записи после присваивания нового значения входу вызывают какой-то метод calc, которого нет в описании класса. В то же время видно, что этот метод принадлежит классу, потому что перед его именем добавлена ссылка self. Метод calc должен пересчитать значение выхода логического элемента сразу после изме- нения его входа. Проблема в том, что мы не можем написать метод calc, пока неизвестно, какой именно логический элемент моделируется. С другой стороны, мы знаем, что такую процедуру имеет любой логический элемент. В такой ситуации можно написать метод-«заглушку» (который ничего не делает): class TLogElement: ... def calc ( self ): pass Но это не совсем правильно, поскольку кто-то может создать такой элемент и пытаться его ис- пользовать, а он не работает! Поэтому не будем его определять вообще. Такой метод называется абстрактным методом. Более того, не существует логического элемента «вообще», как не существует «просто фрукта», не относящегося к какому-то виду. Такой класс в ООП называется абстрактным. Его отли- чительная черта – хотя бы один абстрактный (нереализованный) метод. Для надёжности нужно совсем запретить создание объектов класса TLogElement, потому что это бессмысленно. Для этого в конструкторе класса определим, есть ли у объекта метод calc, и если нет – будем искусственно воздавать аварийную ситуацию – исключение 3 : class TLogElement: def __init__ ( self ): self.__in1 = False 3 В Python есть и другие способы объявить класс абстрактным, например, с помощью модуля ABCMeta. Абстрактный метод – это метод класса, который используется, но не реализуется в классе. Абстрактный класс – это класс, содержащий хотя бы один абстрактный метод. Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 14 http://kpolyakov.spb.ru 21.04.2017 self.__in2 = False self._res = False if not hasattr ( self, "calc" ): raise NotImplementedError ( "Нельзя создать такой объект!" ) Функция hasattr возвращает логическое значение True, если у переданного ему объекта (здесь – self) есть указанное поле или метод (calc). Ключевое слово raise означает «создать исключение», тип этого исключения – NotImplementedError (ошибка «не реализовано»), в скобках записана символьная строка, которую увидит пользователь в сообщении об ошибке. Итак, полученный класс TLogElement – это абстрактный класс. Его можно использовать только для разработки классов-наследников, создать в программе объект этого класса нельзя. Чтобы класс-наследник не был абстрактным, он должен переопределить все абстрактные методы предка, в данном случае – метод calc. Как это сделать, вы увидите в следующем пункте. Получается, что классы-наследники могут по-разному реализовать один и тот же метод. Та- кая возможность называется полиморфизм. Теперь давайте вспомним про поле с именем _res, которое хранит значение выхода логи- ческого элемента. Очевидно, что оно должно быть доступно классам-наследникам, которые будут изменять его в методе calc. Поэтому делать его скрытым нельзя. С другой стороны, часто ис- пользуют соглашение о том, что одно подчёркивание означает специальное поле, которое не должно изменяться другими объектами и функциями 4 Классы-наследники Теперь займемся классами-наследниками от TLogElement. Поскольку у нас будет единст- венный элемент с одним входом («НЕ»), сделаем его наследником прямо от TLogElement (не будем вводить специальный класс «элемент с одним входом»). class TNot ( TLogElement ): def __init__ ( self ): TLogElement. __init__ ( self ) def calc ( self ): self._res = not self.In1 После названия нового класса TNot в скобках указано название базового класса. Все объекты класса TNot обладают всеми свойствами и методами класса TLogElement. В конструкторе класса-наследника нужно обязательно вручную вызвать конструктор класса- предка. В отличие от других объектно-ориентированных языков, этот вызов автоматически не вы- полняется. Новый класс определяет метод calc, который записывает в поле _res инверсию входного сигнала (результат применения логической операции not). Таким образом, класс TNot уже не абстрактный, в нём все нужные методы определены. Теперь можно создавать объект этого класса TNot и использовать его: n = TNot() n.In1 = False ( n.Res ) Все типы логических элементов, которые имеют два входа, будут наследниками класса class TLog2In ( TLogElement ): pass 4 В других объектно-ориентированных языках программирования (C++, Паскаль) кроме общедоступных и закрытых элементов класса есть ещё защищённые (англ. protected). Доступ к ним имеет только сам класс, в котором они объявлены, и наследники этого класса. К сожалению, в языке Python так сделать невоз- можно. Полиморфизм (от греч. πολυ — много, и μορφη — форма) – это возможность классов- наследников по-разному реализовать метод, описанный для класса-предка. Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 15 http://kpolyakov.spb.ru 21.04.2017 В нашем случае этот класс пустой, но если нам понадобится ввести какие-то свойства и методы, характерные для всех элементов с двумя входами, мы сможем это легко сделать в классе TLog2In. Класс TLog2In – это тоже абстрактный класс, потому что он не переопределил метод calc. Это сделают его наследники TAnd (элемент «И») и TOr (элемент «ИЛИ»), которые описы- вают конкретные логические элементы: class TAnd ( TLog2In ): def __init__ ( self ): TLog2In.__init__ ( self ) def calc ( self ): self._res = self.In1 and self.In2 class TOr ( TLog2In ): def __init__ ( self ): TLog2In.__init__ ( self ) def calc ( self ): self._res = self.In1 or self.In2 Теперь мы готовы к тому, чтобы создавать и использовать построенные логические элемен- ты. Например, таблицу истинности для последовательного соединения элементов «И» и «НЕ» можно построить так: elNot = TNot() elAnd = TAnd() ( " A | B | not(A&B) " ); ( "-------------------" ); for A in range ( 2 ): elAnd.In1 = bool (A) for B in range ( 2 ): elAnd.In2 = bool (B) elNot.In1 = elAnd.Res ( " " , A, "|" , B, "|" , int (elNot.Res) ) Сначала создаются два объекта – логические элементы «НЕ» (класс TNot) и «И» (класс TAnd). Да- лее в двойном цикле перебираются все возможные комбинации значений переменных A и B (ка- ждая из них может быть равна 0 или 1). Они подаются на входы элемента «И», а его выход – на вход элемента «НЕ». Чтобы при выводе таблицы истинности вместо False и True выводились более компактные обозначения 0 и 1, значение выхода преобразуется к целому типу ( int). Модульность Как вы знаете из главы 6, большие программы обычно разбивают на модули – внутренне связные, но слабо связанные между собой блоки. Такой подход используется как в классическом программировании, так в ООП. В нашей программе с логическими элементами в отдельный модуль (сохраним его в виде файла logelement.py) можно вынести всё, что относится к логическим элементам: class TLogElement: def __init__ ( self ): self.__in1 = False self.__in2 = False self._res = False if not hasattr ( self, "calc" ): raise NotImplementedError ( "Нельзя создать такой объект!" ) def __setIn1 ( self, newIn1 ): self.__in1 = newIn1 self.calc() def __setIn2 ( self, newIn2 ): self.__in2 = newIn2 self.calc() In1 = property ( lambda x: x.__in1, __setIn1 ) Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 16 http://kpolyakov.spb.ru 21.04.2017 In2 = property ( lambda x: x.__in2, __setIn2 ) Res = property ( lambda x: x._res ) class TNot ( TLogElement ): def __init__ ( self ): TLogElement.__init__ ( self ) def calc ( self ): self._res = not self.In1 class TLog2In ( TLogElement ): pass class TAnd ( TLog2In ): def __init__ ( self ): TLog2In.__init__ ( self ) def calc ( self ): self._res = self.In1 and self.In2 class TOr ( TLog2In ): def __init__ ( self ): TLog2In.__init__ ( self ) def calc ( self ): self._res = self.In1 or self.In2 Чтобы использовать такой модуль, нужно подключить его в основной программе с помощью клю- чевого слова import: import logelement elNot = logelement.TNot() elAnd = logelement.TAnd() ... Обратите внимание, что при создании объектов нужно указывать имя модуля, где определены классы TNot и TAnd. Сообщения между объектами Когда логические элементы объединяются в сложную схему, желательно, чтобы передача сигналов между ними при изменении входных данных происходила автоматически. Для этого можно немного расширить базовый класс TLogElement, чтобы элементы могли передавать друг другу сообщения об изменении своего выхода. Для простоты будем считать, что выход любого логического элемента может быть подклю- чен к любому (но только одному!) входу другого логического элемента. Добавим к описанию класса два скрытых поля и один метод: class TLogElement: def __init__ ( self ): ... self.__nextEl = None self.__nextIn = 0 ... def link ( self, nextEl, nextIn ): self.__nextEl = nextEl self.__nextIn = nextIn Поле __nextEl хранит ссылку на следующий логический элемент, а поле __nextIn – номер входа этого следующего элемента, к которому подключен выход данного элемента. С помощью общедоступного метода link можно связать данный элемент со следующим. Нужно немного изменить методы setIn1 и setIn2: при изменении входа они должны не только пересчитывать выход данного элемента, но и отправлять сигнал на вход следующего class TLogElement: ... def __setIn1 ( self, newIn1 ): self.__in1 = newIn1 self.calc() Информатика и ИКТ, 11 класс К.Ю. Поляков, Е.А. Еремин 17 http://kpolyakov.spb.ru 21.04.2017 if self.__nextEl: if self.__nextIn == 1 : self.__nextEl.In1 = self._res elif __nextIn == 2 : __nextEl.In2 = self._res Запись « if __nextEl» означает «если следующий элемент задан». Если он не был установлен, значение поля __nextEl будет равно None, и никаких дополнительных действий не выполняет- ся. С учетом этих изменений вывод таблицы истинности функции «И-НЕ» можно записать так (операторы вывода заменены многоточиями): elNot = logelement.TNot() elAnd = logelement.TAnd() elAnd.link ( elNot, 1 ) ... for A in range ( 2 ): elAnd.In1 = bool (A) for B in range ( 2 ): elAnd.In2 = bool (B) ... Обратите внимание, что в самом начале мы установили связь элементов «И» и «НЕ» с помощью метода link (связали выход элемента «И» с первым входом элемента «НЕ»). Далее в теле цикла обращения к элементу «НЕ» нет, потому что элемент «И» автоматически сообщит ему об измене- нии своего выхода. 1. Что такое классификация? Зачем она нужна? Приведите примеры. 2. В каком случае можно сказать, что «класс Б – наследник класса А», а когда «объект класса А содержит объект класса Б»? Приведите примеры. 3. Что такое иерархия классов? 4. Объясните приведенную иерархию логических элементов. Обсудите ее достоинства и недос- татки. 5. Дайте полное определение ООП и объясните его. 6. Что такое базовый класс и класс-наследник? Какие синонимы используются для этих терми- нов? 7. На примере класса TLogElement покажите, как выполнена инкапсуляция. 8. Что такое абстрактный класс? Почему нельзя создавать объекты этого класса? 9. Что нужно сделать, чтобы класс-наследник абстрактного класса не был абстрактным? 10. Что такое полиморфизм? 11. Какие преимущества даёт применение модулей в программе? 12. Объясните, как объекты могут передавать сообщения друг другу. 13. Подумайте, как можно организовать передачу сигнала с выхода логического элемента сразу на несколько выходов других элементов. 1. Добавьте в иерархию классов элементы «исключающее ИЛИ», «И-НЕ» и «ИЛИ-НЕ». 2. «Соберите» в программе RS-триггер из двух логических элементов «ИЛИ-НЕ», постройте его таблицу истинности (обратите внимание на вариант, когда оба входа нулевые). 3. *Используя материалы Интернета, выясните, как можно создать абстрактный класс с помо- щью модуля ABCMeta. Внесите соответствующие изменения в программу. Какой из подхо- дов вам больше нравится? Почему? Задачи ? Контрольные вопросы |