Главная » SQL, Базы данных » ЕЩЕ РАЗ ОБ ОБЪЕКТАХ И ОБЪЕКТНЫХ КЛАССАХ

0

В этой главе определенные понятия, представленные в предыдущем разделе, описаны более подробно. Рассмотрим более сложный пример с двумя  объектными классами: DEPT (Отдел) и ЕМР (Сотрудник). Предположим, что в системе уже были описаны определяемые  пользователем  классы  MONEY  (Деньги)  и  JOB  (Работа),  а  класс  CHAR (Символьная переменная) является встроенным. Тогда операции, необходимые для создания классов DEPT и ЕМР, могут выглядеть следующим образом (с использованием некоторого гипотетического синтаксиса).

5 Рассматриваемыеобъектыобязательнодолжныбытьизменяемыми(объясните,почему).

CLASS DEPT

PUBLIC ( DEPT#                                                                                                                        CHAR, DNAME                                                                                                                        CHAR, BUDGET                                                                                     MONEY, MGR                                                                                  OID ( EMP ) ,

EMPSOID ( SET ( OID ( EMP ) ) ) )

METHODS ( HIRE_EMP( OID ( EMP ) ) <код> ,

FIRE_EMP( OID ( EMP ) ) <код> , … ) … ;

CLASS EMP

PUBLIC ( EMP#                                                                                                                         CHAR, ENAME                                                                                                                         CHAR, SALARY                                                                                     MONEY, POSITION                                          OID ( JOB ) )

METHODS ( … ) …                                                                                     ;

Необходимо отметить несколько приведенных ниже важных особенностей.

1.          В этом примере описание отделов и сотрудников построено на основе иерархии вложения, в которой объекты ЕМР концептуально содержатся внутри объектов DEPT. Таким образом, объект класса DEPT содержит открытую переменную эк земпляра MGR, представляющую руководителя отдела, а также переменную EMPS, представляющую сотрудников отдела. Точнее, объекты класса DEPT содержат от крытую переменную экземпляра MGR, значение которой является указателем (т.е. идентификатором) объекта сотрудника, и переменную EMPS, значение которой является указателем на множество указателей на сотрудников. Понятие иерархии вложения в более широкой форме будет представлено ниже.

2.          В данном примере в объекты класса ЕМР не была включена некоторая переменная экземпляра, содержащая идентификатор объекта отдела DEPT, или же значение номера отдела DEPT# (переменная экземпляра для внешнего ключа). Это решение согласуется с выбранным нами методом представления связи между отделами и сотрудниками с помощью иерархии вложения. Но это также означает, что не су ществует возможности прямого перехода от заданного объекта класса ЕМР к соот ветствующему ему объекту класса DEPT. Подробнее данный вопрос обсуждается в подразделе "Связи" раздела 25.5.

3.          Обратите внимание на то, что определение каждого класса содержит объявления методов, которые применяются к объектам этого класса (без включения про граммного кода). Целевыми классами для подобных методов являются, безуслов но, классы, определения которых включают определение данного метода6.

На рис. 25.4 приведено несколько примеров экземпляров объектов для определенных ранее классов DEPT и ЕМР. Рассмотрим объект ЕМР, показанный в верхней части рисунка (с идентификатором (OID) еее), который содержит перечисленные ниже компоненты.

6  Отметим, что в нашем гипотетическом синтаксисе смешиваются понятия модели и  реализации (хотя такая ситуация и нежелательна, но весьма типична). Кроме того, автор в  другой  работе ([14.12]) доказал, что пример с отделами и сотрудниками все равно плохо подходит для изучения объектных классов! Однако данный вопрос мы здесь обсуждать не будем,  поскольку пришлось бы слишком далеко отойти от основной темы.

■     Неизменяемый объект ‘Е001′ встроенного класса CHAR в открытой переменной экземпляра ЕМР#.

■     Неизменяемый объект ‘ Smith’ встроенного класса CHAR в открытой переменной экземпляра ENAME.

■     Неизменяемый объект ‘$50  000′ определенного пользователем класса MONEY в открытой переменной экземпляра SALARY.

■     Идентификатор (OID) изменяемого объекта7 определенного пользователем класса

JOB в открытой переменной экземпляра POSITION.

Рис. 25.4. Пример экземпляров объектов классов DEPT и ЕМР

Объект ЕМР также включает по крайней мере две дополнительные закрытые переменные экземпляра, одна из которых (OID) содержит идентификатор еее самого объекта ЕМР, а другая (CLASS) — идентификатор, определяющий класс объекта (Class-Defining Object — CDO) для объектов сотрудников ЕМР, что позволяет найти код методов данного объекта.

Примечание. Эти два идентификатора физически могут храниться как вместе с объектом, так и отдельно от него. Например, значение еее не обязательно должно храниться как часть соответствующего объекта ЕМР; необходимо только, чтобы в приложении был

задан некоторый способ обнаружения объекта ЕМР по данному значению еее (т.е., чтобы

было задано некоторое отображение величины еее на физический адрес объекта ЕМР).

Однако концептуально пользователь всегда может считать идентификатор объекта частью этого объекта.

Теперь рассмотрим объект DEPT, расположенный в центре рисунка, с идентификатором (OID) ddd, который содержит перечисленные ниже объекты.

■     Неизменяемый объект ‘D01′ встроенного класса CHAR в открытой переменной экземпляра DEPT#

■     Неизменяемый объект ‘Mktg’ встроенного класса CHAR в открытой переменной экземпляра DNAME.

■     Неизменяемый   объект   ‘$1  000  000′   определенного   пользователем   класса

MONEY в открытой переменной экземпляра BUDGET.

■      Идентификатор еее изменяемого объекта определенного пользователем  класса ЕМР в открытой переменной экземпляра MGR (это идентификатор объекта, представляющего руководителя отдела).

■     Идентификатор sss изменяемого объекта определенного пользователем класса

SET (OID (ЕМР)) в открытой переменной экземпляра EMPS.

■         Две закрытые переменные экземпляра, содержащие  идентификатор (OID) ddd                                                                                    самого  объекта  DEPT  и  идентификатор  соответствующего  объекта, определяющего                                         класс.

Объект с идентификатором sss состоит из набора идентификаторов индивидуальных

(изменяемых) объектов класса ЕМР, а также обычных закрытых переменных экземпляра.

На рис. 25.4 экземпляры объектов представлены в том виде, в котором они реально существуют, т.е. на рисунке отображается компонент структуры данных объектной модели, что позволяет яснее представить саму объектную модель. Обычно в книгах или документации такие схемы не используются; вместо этого данные представляются так, как на рис. 25.5, на более высоком уровне абстракции, что, как принято считать, облегчает понимание объектной модели.

Приведенная на рис. 25.5 схема в большей степени согласуется с интерпретацией на основе иерархии вложения. Однако в ней остается скрытым тот важный факт (как уже было сказано), что в объектах часто содержатся не сами другие объекты как таковые, а скорее идентификаторы этих других объектов (т.е. указатели на эти объекты). Например, согласно рис. 25.5 можно предположить, что объект класса DEPT для отдела с номером D01 содержит два экземпляра объекта класса ЕМР для сотрудника с номером Е001 (при этом, помимо всего прочего, может произойти так, что сотрудник с номером Е001 получает  две  различные зарплаты).  Такая ситуация  может  привести  к путанице,  поэтому предпочтение следует отдавать схемам, подобным приведенной на рис. 25.4.

В дополнение к этому следует отметить, что реальные определения объектных классов способствуют возникновению запутанных ситуаций, поскольку переменные экземпляра

в них обычно определяются не как идентификаторы OID (как в приведенных выше выражениях с использованием гипотетического синтаксиса), а на основе непосредственного отображения иерархии вложения. Поэтому, например,  переменные экземпляра

EMPS в объектном классе DEPT обычно определяются не как OID (SET (OID (ЕМР))), а в более краткой форме: SET (ЕМР). Несмотря на некоторую громоздкость полной записи, мы все же предпочитаем наш стиль, как более ясный и точный.

Следует отметить, что вся прежняя критика иерархического подхода (например, иерархии вложения, воплощенной в СУБД IMS) в основном относилась именно  к  иерархиям вложения. Подробное рассмотрение данного вопроса заняло бы слишком много места, поэтому достаточно сказать, что основным доводом такой критики было отсутствие симметрии. В частности, иерархии не совсем удобны  для представления отношений типа "многие ко многим". Например, для рассматриваемого ранее отношения поставщиков и деталей может возникнуть  вопрос, содержатся ли объекты-поставщики в объектахдеталях или наоборот. А может, верно и то и другое? А что можно сказать об отношениях поставщиков, деталей и проектов?

Рис. 25.5. Пример представления экземпляров объектов DEPT и ЕМР в соответствии с иерархией их вложения

Действительно, подобных вопросов больше, чем можно было бы предположить. С одной стороны, как мы убедились, объекты представляют собой иерархию, и к ним также относятся привычные критические замечания относительно иерархий. А с другой стороны, как явствует из рис. 25.4, такие объекты на самом деле образуют не иерархию, а кортежи с перечисленными ниже компонентами.

1.  Неизменяемые подобъекты, т.е. самоидентифицируемые значения, такие как це лые числа и денежные суммы.

2.  Идентификаторы изменяемых подобъектов, т.е. указатели на другие (возможно,

совместно используемые) изменяемые объекты.

3.  Множества, списки, массивы и т.д. объектов, перечисленных в пп. 1, 2 и в этом пункте.

К этому списку следует также добавить определенные скрытые компоненты. Особого внимания заслуживает п. 3, поскольку обычно объектные системы поддерживают несколько генераторов типа коллекций (SET, LIST, ARRAY и др.), но обычно не поддерживают тип RELATION (отношение). Такие генераторы  могут  использоваться в сколь угодно сложных сочетаниях. Например, в определенных обстоятельствах как один объект может рассматриваться массив  списков мультимножеств массивов указателей на целочисленные  переменные.  Дополнительные  сведения  по  этой  теме  приведены  в  подразделе "Сравнение понятий классов, экземпляров и коллекций" ниже в данном разделе.

Еще раз об идентификаторе объекта

Чтобы сослаться на объект или идентифицировать его, в современных реляционных СУБД обычно используются ключи, определяемые и управляемые пользователями (далее для краткости будем называть их пользовательскими ключами). (Хотя как было описано в главах 1 и 3, в реляционных базах данных указатели наподобие идентификаторов объектов фактически намеренно запрещены; см. также продолжение этой темы в главе 26.) Но хорошо известно, что применение пользовательских ключей сопряжено с некоторыми проблемами. Эти проблемы достаточно подробно рассматриваются в [14.11] и [14.21], где сделан вывод, что  реляционные СУБД, по крайней мере, в качестве альтернативы должны поддерживать и ключи, определяемые системой {суррогатные). Доводы в пользу идентификаторов объектов в объектных системах подобны доводам в пользу суррогатных ключей в реляционных системах. (Однако их не следует приравнивать друг к другу, поскольку суррогатные ключи — это доступные пользователю обычные значения, а идентификаторы объектов — это адреса, которые, по крайней мере, концептуально, от пользователя скрыты. В [25.17] широко обсуждается это различие и другие связанные с ним вопросы.)

Из этого следуют некоторые приведенные ниже важные выводы.

1.          Необходимо понимать, что, как будет показано в разделе 25.4, наличие идентифи каторов объектов не позволяет полностью исключить применение пользователь ских ключей. Точнее, пользовательские ключи необходимы для общения с внеш ним миром, хотя внутри базы данных все объекты могут ссылаться один на другой только с помощью идентификаторов объектов.

2.          Что является идентификатором производного объекта, например соединения неко торого объекта класса ЕМР И соответствующего объекта класса DEPT или проекции объекта класса DEPT ПО атрибутам BUDGET И MGR? ЭТО очень важный вопрос для производных объектов, рассмотрение которого мы отложим до раздела 25.5.

3.          Идентификаторы объектов часто служат предметом критических замечаний, вы званных тем, что объектные системы выглядят, как "модифицированный стан дарт CODASYL". Как было описано в главе 1, стандарт CODASYL использовал ся для сетевых систем управления базами данных (например для СУБД IDMS), которые были созданы до появления реляционного подхода. Использование идентификаторов объектов приводит к низкоуровневому стилю программиро вания (см. раздел 25.4), что очень напоминает устаревший стиль программирова ния, основанный на использовании указателей, согласно стандарту CODASYL.

Кроме того, поскольку идентификаторы объектов являются указателями, часто можно встретить следующие утверждения.

Во-первых, системы типа CODASYL ближе к объектным системам, чем реляционные.

■     Во-вторых, реляционные системы основаны на значениях, в то время как объ ектные системы — на идентификаторах.

Сравнение понятий классов, экземпляров и коллекций

В области объектных систем четко разделяются концепции класса, экземпляра и коллекции. Как уже отмечалось, класс является по существу типом данных, причем он может быть встроенным или определенным пользователем, а также может  быть  сколь угодно сложным8. Каждый класс способен принять сообщение NEW, которое приводит к созданию нового (изменяемого) экземпляра объекта данного класса; вызываемый этим сообщением метод называют конструктором класса. Ниже приводится пример такого сообщения, записанного с помощью некоторого гипотетического синтаксиса.

Е := EMP NEW ( ‘E001′, ‘Smith’, MONEY ( 50000 ), POS ) ;

Здесь программная переменная POS содержит идентификатор некоторого  объекта класса JOB, а метод NEW вызывается для создания нового экземпляра класса ЕМР; в результате такого вызова создается новый объект данного класса, происходит инициализации переменных экземпляра заданными значениями и возвращается идентификатор нового объекта. Затем этот идентификатор присваивается программной переменной Е.

Поскольку одни объекты могут ссылаться на любые другие объекты с помощью идентификаторов, один объект может совместно использоваться несколькими другими объектами. В частности, один и тот же объект может одновременно относиться к нескольким различным коллекциям объектов. В продолжение рассматриваемого примера приведем еще одно следующее выражение.

CLASS EMP_COLL

PUBLIC ( EMPS OID ( SET ( OID ( EMP ) ) ) ) …; ALL_EMPS := EMP_COLL NEW (

) ; ALL_EMPS ADD ( E ) ;

Пояснения

■     Объект класса EMP_COLL содержит одну открытую переменную экземпляра EMPS, значением которой является указатель (идентификатор) на изменяемый объект, значение которого представляет собой множество указателей (идентификаторов) на отдельные объекты класса ЕМР.

■     ALL_EMPS — это программная переменная, значением которой является иденти фикатор объекта класса EMP_COLL. После выполнения операции присваивания

8 Как уже было сказано в разделе 25.2, в некоторых системах используются оба понятия — и "тип", и "класс". В этих системах "тип" означает тип понятия, т.е. его  содержание или сущность, а "класс" означает применение этого понятия, т.е. определенную коллекцию, или иногда   реализацию   (рассматриваемого   типа).   В   других   системах   данные   термины используются иначе… Мы же по-прежнему будем употреблять для обозначения типа термин "класс", в том же смысле, что и в главе 5.

она будет содержать идентификатор объекта, значением которого, в свою очередь,

будет идентификатор пустого множества идентификаторов объектов ЕМР.

■    ADD — это метод объектов класса EMP_COLL. В рассматриваемом примере данный метод применяется к объекту класса, идентификатор которого содержится в программной переменной ALL_EMPS, И предназначается для добавления идентификатора объекта ЕМР, который содержится в программной переменной Е, к множеству идентификаторов  (изначально  пустому).  Причем  идентификатор  этого  объекта содержится в объекте EMP_COLL, идентификатор которого находится в программной переменной ALL_EMPS.

Рассмотрев приведенную выше последовательность операций, можно  заметить, что переменная ALL_EMPS обозначает коллекцию объектов ЕМР, которая в настоящее время содержит только один объект, а именно — объект ЕМР, описывающий сотрудника с номером Е001. Кроме всего прочего, обратите внимание на то, что необходимо упомянуть значение пользовательского ключа в последнем предложении!

Безусловно, в некоторый заданный момент может существовать любое  количество разных и, возможно, перекрывающихся множеств объектов сотрудников, например, как показано ниже.

PROGRAMMERS := EMP_COLL NEW ( )

; PROGRAMMERS ADD ( E ) ;

HIGHLY_PAID := EMP_COLL NEW ( )

; HIGHLY_PAID ADD ( E ) ;

Приведенный ниже пример выражения SQL показывает, что в реляционных системах все организовано совсем по-другому.

CREATE TABLE ЕМР

( ЕМР#    …   NOT NULL ,

ENAME   …    NOT NULL , SALARY  …    NOT NULL , POSITION …    NOT NULL ) … ;

В  этом случае  с  помощью  оператора  SQL  одновременно  создаются  и  тип,  и коллекция, причем сложный тип соответствует заголовку таблицы, а исходно пустая коллекция соответствует телу таблицы. Точно так же приведенное ниже выражение SQL позволяет одновременно и создать отдельную строку ЕМР, и добавить ее к коллекции ЕМР (для упрощения предполагается, что этот оператор  INSERT действительно вставляет только единственную строку).

INSERT INTO ЕМР (…) VALUES ( … ) ;

Можно сделать вывод, что организация этих действий с помощью языка SQL характеризуется приведенными ниже особенностями.

1.  Отсутствует возможность обеспечить существование отдельного объекта ЕМР без включения его в состав некоторой коллекции; при этом фактически имеется только одна и только одна такая коллекция (но необходимо также учитывать замечания, приведенные ниже, и обратить внимание на то, что строку ЕМР не в полной мере можно считать объектом, как показано в главе 26).

2.    Не существует непосредственного способа создания двух различных коллекций

объектов ЕМР одного и того же класса (подробности приведены ниже).

3.    Не существует непосредственного способа совместного использования одного и того же объекта в нескольких коллекциях объектов ЕМР (подробности приведены ниже).

Иногда можно услышать именно такие замечания, однако они не выдерживают серьезной критики. Во-первых, для достижения аналогичного эффекта в каждом случае может применяться реляционный метод использования внешнего ключа. Например, можно определить  две  базовые  таблицы,  PROGRAMMERS   (Программисты)  и  HIGHLY_PAID (Высокооплачиваемые), каждая из которых  состоит из номеров соответствующих сотрудников. Во-вторых, что еще  важнее,  для достижения того же эффекта может быть применен  реляционный  метод  с  использованием  представлений. Например,  таблицы PROGRAMMERS и HIGHLY_PAID можно определить как следующие представления, созданные на основе базовой таблицы ЕМР.

CREATE VIEW PROGRAMMERS

AS SELECT EMP#, ENAME, SALARY, POSITION FROM   EMP WHERE POSITION = ‘Programmer’ ;

CREATE VIEW HIGHLY_PAID

AS SELECT EMP#, ENAME, SALARY, POSITION FROM EMP WHERE SALARY > некоторое предельное значение ;

Теперь один и тот же объект ЕМР можно вполне разместить одновременно в двух или нескольких коллекциях. Кроме того, принадлежность к этим коллекциям, которые являются представлениями, контролируется системой автоматически, а  не программистом вручную.

Завершая эту тему, следует упомянуть об одной интересной параллели, существующей между изменяемыми объектами объектных систем и явно заданными динамическими переменными  некоторых языков программирования  (например,  переменными типа

BASED языка PL/I). Как и в случае изменяемых объектов данного класса, может существовать любое количество отдельных явных динамических переменных данного типа, память для которых выделяется во время выполнения в результате явно  определяемых в программе действий. Более того, эти отдельные переменные, как и отдельные изменяемые объекты, не имеют имен, поэтому на них необходимо ссылаться с помощью указателей. Например, в языке PL/I можно записать такую последовательность выражений.

DCL XYZ  INTEGER BASED  ;     /*  XYZ переменная  типа  BASED                                                                                                                            */ DCL P POINTER  ,                                                                                                                       /*    Р — переменная-указатель                                                                                                                                                                                                                                                */

ALLOCATE XYZ  SET  (  Р  );     /*  создать  новый  экземпляр  XYZ,                                                                                                                          */

/*  и  присвоить   переменной  Р  значение,    которое

*/

/*  указывает  на  этот  экземпляр                                                                                                                                                                     */

Р  -> XYZ  =  3   ;                                                                                                                           /*   присвоить   значение   3   экземпляру                                                                                                                         */

/*  XYZ,   на  который  указывает   Р                                                                                                                                                                    */

Этот записанный на языке PL/I код очень похож на рассмотренный ранее объектный код. В частности, объявление переменной типа BASED подобно созданию класса объектов, а применение операции ALLOCATE — созданию нового экземпляра объекта этого

класса с помощью сообщения NEW. Таким образом, основная причина, по которой в объектной модели необходимы идентификаторы, заключается в том, что те объекты, которые они идентифицируют, не обладают уникальными именами (точно так же, как экземпляры переменных типа BASED в языке PL/I).

Иерархии классов

Описание основных концепций объектного подхода было бы неполным без рассмотрения иерархий классов (их не следует путать с иерархиями вложения). Однако объектная концепция иерархии классов, в сущности, является  аналогичной  концепции иерархии типов, которая подробно рассматривалась в главе 20. Следовательно, здесь можно ограничиться несколькими краткими определениями (большей частью это — перефразированные определения из главы 20) и соответствующими выводами.

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

Начнем с того, что объектный класс Y называется подклассом класса X, а объектный класс X — суперклассом объектного класса у тогда и только тогда, когда каждый объект

класса Y обязательно является также объектом класса X (т.е. "у ISA X"). В этом случае объект класса у наследует9 переменные экземпляра и методы класса х. Наследование переменных экземпляра обычно называют наследованием структуры, а наследование методов — наследованием поведения. В истинно объектных системах не может быть наследования структуры, а возможно лишь наследование  поведения (по крайней мере, это касается скаляров или полностью инкапсулированных объектов), поскольку отсутствует структура, которая может  быть унаследована (под этим подразумевается структура, доступная

пользователю).  Но  на  практике  объектные  системы  обычно  не  являются  такими "чистыми" и в какой-то мере поддерживают наследование структуры, которое, подчеркиваем, означает наследование открытых переменных экземпляра.

Примечание. Если подкласс содержит дополнительные открытые переменные экземпляра, кроме тех, которые он наследует от (непосредственно предшествующему ему) суперкласса, то принято применять выражение, что этот подкласс расширяет  свой суперкласс.

Если класс У является подклассом класса X, то пользователю предоставляется  возможность применять объект класса У вместо объекта класса X везде, где это допустимо, т.е. в качестве фактического параметра различных методов. Этот  принцип называется

принципом заменяемости и весьма полезен для повторного использования  кода. Однако,   поскольку   в   объектных   системах   не   всегда   четко   прослеживается разница   между   значениями   и   переменными   (т.е.   между   неизменяемыми   и

изменяемыми   объектами),   возникают   сложности   при   проведении   различий между   заменяемостью   значений   и   переменных   (этот   вопрос   дополнительно обсуждается в главе 20). Средство, позволяющее применять  один и тот же метод

для объектов класса х и класса у, называется полиморфизмом.

9   Он,  по-видимому,  унаследует  также  закрытые  переменные  экземпляра,  но  автор   рассматривает   такое наследование как проблему реализации, а не как составную часть модели.

Обычно в объектных системах заранее предусмотрены определенные встроенные иерархии классов. Например, в системе OPAL (см. раздел 25.4) каждый класс рассматривается как подкласс некоторого уровня встроенного класса OBJECT (поскольку "все является объектами"). Встроенные подклассы  класса OBJECT включают классы BOOLEAN, CHAR, INTEGER, COLLECTION и др. Класс COLLECTION в свою очередь включает подкласс BAG, а класс BAG содержит класс SET И Т.Д. И Т.П. (НО, по-видимому, COLLECTION, BAG и SET не  являются классами как таковыми, а "генераторами классов" наподобие RELATION в языке Tutorial D? Создается впечатление, что в этом вопросе есть некоторая путаница.)

Еще одно важное замечание состоит в том, что объектные системы обычно не допускают изменения класса объекта (см. аннотацию к [20.12]). Вследствие этого объектные системы не поддерживают уточнение или обобщение с помощью ограничений, поэтому

такие системы не способны поддерживать то, что, по мнению автора, можно  считать

"качественной" моделью наследования. Эта тема раскрыта более подробно в следующей главе (в разделе 26.3).

Наконец, в некоторых системах, кроме одиночного наследования, в той или  иной форме поддерживается множественное  наследование. Но автору не известно  ни одной

системы, которая поддерживала бы наследование кортежей или отношений (как одиночное, так и множественное) в том смысле, который указан в [3.3].

Источник: Дейт К. Дж., Введение в системы баз данных, 8-е издание.: Пер. с англ. — М.: Издательский дом «Вильямс», 2005. — 1328 с.: ил. — Парал. тит. англ.

По теме:

  • Комментарии