Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

web - tec / PHP 5 для начинающи

.pdf
Скачиваний:
72
Добавлен:
12.06.2015
Размер:
26.79 Mб
Скачать

512 Глава 12

Как это работает

Класс Customer инициализирует значения из базы данных на основе идентифика+ тора покупателя. Идентификатор покупателя можно получить из дисконтной карточки, которые используются в большинстве крупных сетей супермаркетов. Имея идентифи+ катор покупателя, можно получить его персональную информацию из базы данных (в этом примере номер карточки жестко запрограммирован), а также целое число, представляющее количество покупателей, которые совершили покупки до этого поку+ пателя. Вся эта информация сохраняется в общедоступных переменных экземпляра.

В классе SweepstakesCustomer функциональность конструктора расширяется. Сначала вызывается конструктор родительского класса (parent::__construct), ко+ торому передаются необходимые параметры. Затем проверяется значение свойства customerNumber. Если оно равно 1000 000, то приложение сообщает покупателю о выигрыше приза.

Интерфейсы

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

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

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

Практика Использование интерфейсов

Создайте файл interface.Openable.php:

<?php

interface Openable { abstract function open(); abstract function close();

}

?>

Класс называется class.[имяКласса].php. То же самое соглашение следует использовать для именования файлов с интерфейсами, присваивая им имена типа interface.[имяИнтерфейса].php.

Введение в объектно*ориентированное программирование 513

Интерфейс объявляется с помощью синтаксиса, аналогичного объявлению класса, за исключением того, что вместо ключевого слова class используется слово interface. Как правило, интерфейс не имеет переменных экземпляра и в нем не описана реализация функций экземпляра.

Поскольку реализация не определена, такие функции объявляются как абстракт+ ные (abstract). Ключевое слово abstract сообщает PHP, что любой класс, реали+ зующий данный интерфейс, должен обеспечить реализацию соответствующих функ+ ций. Если в классе, реализующем интерфейс, не реализованы все абстрактные функции, то PHP+интерпретатор генерирует ошибку времени выполнения. Выбороч+ ная реализация некоторых абстрактных методов не допускается ++++++ необходимо реа+ лизовать все абстрактные методы.

Абстрактный метод ++++++ метод, для которого в интерфейсе не предусмотрена реализа+ ция. Если какой+либо метод класса объявлен как абстрактный, то сам класс также должен быть объявлен как абстрактный (в объявлении класса перед словом class должно при+ сутствовать ключевое слово abstract). Невозможно создать объект самого абстрактно+ го класса ++++++ абстрактный класс должен иметь подклассы, которые обеспечивают кон+ кретную реализацию абстрактных методов. Обратите внимание, что объявления абстрактных методов не содержат фигурных скобок и заканчиваются точкой с запятой.

Как это работает

Интерфейс Openable представляет собой соглашение с остальными частями при+ ложения о том, что класс, реализующий данный интерфейс, обеспечит два метода ++++++

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

Создайте файл class.Door.php:

<?php

require_once('interface.Openable.php');

class Door implements Openable {

private $_locked = false;

public function open() {

if($this->_locked) {

print "Дверь не открывается. Она заперта.";

}else {

print "скрип...<br>";

}

}

public function close() { print "Хлоп!!<br>";

}

public function lockDoor() { $this->_locked = true;

}

public function unlockDoor() { $this->_locked = false;

}

}

?>

514 Глава 12

и файл class.Jar.php:

<? require_once('interface.Openable.php');

class Jar implements Openable { private $contents;

public function __construct($contents) { $this->contents = $contents;

}

public function open() { print "банка открыта<br>";

}

public function close() { print "банка закрыта<br>";

}

}

?>

Для того чтобы можно было использовать эти классы, создайте новый файл testOpenable.php в том же каталоге:

<?php require_once('class.Door.php'); require_once('class.Jar.php');

function openSomething(Openable $obj) { $obj->open();

}

$objDoor = new Door(); $objJar = new Jar("желе");

openSomething($objDoor); openSomething($objJar);

?>

Так как оба класса Door и Jar реализуют интерфейс Openable, объекты обоих этих классов можно передавать функции openSomthing(). Поскольку она принимает только объекты, реализующие интерфейс Openable, можно утверждать, что внутри этой функции можно вызывать методы open() и close(). Однако пытаться использовать свойство contents класса Jar или методы lock() либо unlock() класса Door внутри функции openSomething() не следует, потому что это свойство и методы не являются частью интерфейса. Соглашение интерфейса гарантирует только то, что в реализую+ щем данный интерфейс классе будут присутствовать методы open() и close().

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

Инкапсуляция

Как уже отмечалось в этой главе ранее, объекты позволяют скрывать подробности их реализации от своих пользователей. Например, пользователь не должен знать, где класс Volunteer сохраняет информацию, необходимую для вызова метода signup (в базе данных, в неструктурированном текстовом файле, в XML+файле), или использует

Введение в объектно*ориентированное программирование 515

иной механизм хранения данных. Аналогично, ему не обязательно знать, где внутри объекта хранится информация о добровольцах ++++++ в скалярных переменных, в массиве или даже в другом объекте. Возможность скрывать подробности реализации называ+ ется инкапсуляцией (encapsulation). Вообще инкапсуляция подразумевает две идеи: за+ щита внутренних данных класса от внешнего по отношению к данному классу кода и сокрытие деталей реализации.

Слово ‘‘инкапсулировать’’ буквально означает ‘‘помещать что+либо в капсулу или внешний контейнер’’. Хорошо организованный класс обеспечивает полноценную внешнюю оболочку вокруг своих внутренних данных и предоставляет внешнему коду интерфейс, который полностью отделен от деталей внутренней реализации класса. Это дает два преимущества: во+первых, можно в любое время изменять детали реали+ зации, не влияя при этом на код, использующий данный класс. Во+вторых, поскольку внешний по отношению к данному классу код не может случайно незаметно изменить состояние или свойства объекта данного класса, можно быть уверенным, что состоя+ ние объекта и значения его свойств являются достоверными и имеют смысл.

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

ифункции экземпляра класса могут использоваться как внутренним кодом класса, так

икодом за его пределами.

Вообще говоря, все внутренние переменные класса должны быть объявлены как ча+ стные (с ключевым словом private). Любой доступ к этим переменным извне класса должен осуществляться посредством общедоступных методов. Вы ведь не позволяете всем, кто предлагает вам попробовать новое блюдо, заталкивать его вам прямо в желу+ док. Для этого есть весомая причина ++++++ хочется самому посмотреть на это блюдо и ре+ шить, стоит ли его есть. Аналогично, если объект позволяет внешнему коду изменять его свойства или иначе влиять на его внутренние данные, то инкапсуляция доступа к этим данным в общедоступной функции (и сохранение внутренних данных частными) дает возможность оценить изменения, а затем принять или отвергнуть их.

Например, при создании банковского приложения, которое обрабатывает инфор+ мацию о счетах клиентов, можно использовать объект класса Account, имеющий свой+ ство totalBalance (итоговый баланс), а также методы makeDeposit (сделать вклад)

иmakeWithdrawal (снять деньги). Свойство totalBalance должно быть доступным только для чтения. Единственный способ повлиять на баланс ++++++ снять деньги или сде+ лать вклад. Если бы переменная totalBalance была реализована как общедоступная переменная экземпляра, то можно было бы написать код, который изменял бы значение данной переменной без фактического внесения или снятия денег со счета. Очевидно, что такая ситуация крайне нежелательна для банка. Поэтому данное свойство реализо+ вано в виде частной переменной экземпляра, а метод getTotalBalance возвращает значение этой переменной. То, что переменная, в которой хранится итоговый баланс, объявлена как частная, не позволяет манипулировать ею непосредственно. Поскольку повлиять на баланс могут только общедоступные методы makeWithdrawal и makeDeposit, пользователь вынужден внести вклад, чтобы увеличить сумму на своем счете.

Инкапсуляция внутренних данных и реализации методов позволяет объектно+ ориентированным программным системам защищать свои данные и управлять досту+ пом к ним, а также скрывать детали реализации, создавая, таким образом, гибкие

истабильные приложения.

516 Глава 12

Изменения объектно-ориентированных возможностей PHP5

Поддержка объектов была реализована в PHP, начиная с третьей версии языка. Разработчики не имели намерений поддерживать идею классов или объектов, но не+ которая ограниченная поддержка позднее все+таки была добавлена с целью создания (как сказал Зив Сураски (Zeev Suraski)) ‘‘синтаксического сахара ’’ для ассоциативных массивов. Поддержка объектов в PHP первоначально задумывалась как удобный спо+ соб группировки данных и функций, однако в PHP было включено только небольшое подмножество функций, которые обычно связывают с полнофункциональным объ+ ектно+ориентированным языком программирования.

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

Кроме того, не было поддержки абстрактных интерфейсов или методов. Методы и переменные экземпляра невозможно было объявить как статические. Не было дест+ рукторов. Все эти понятия хорошо знакомы программистам, имеющим опыт работы с объектно+ориентированными языками, а недостаток этих возможностей в объектной модели PHP мог усложнять преобразование кода из таких языков, как Java (которые действительно поддерживают все эти понятия), в PHP.

В PHP5 вводится множество значительных изменений в объектно+ориентирован+ ные возможности языка. Эти изменения решают перечисленные выше и многие дру+ гие проблемы, обеспечивая PHP реальными OO+возможностями и повышенной про+ изводительностью. Ниже представлен перечень нововведений.

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

PHP5 обеспечивает поддержку разыменования, которая позволяет писать код наподобие $obj->getObj()->doSomething(). В предыдущих версиях PHP поддержка разыменования была ограниченной.

Теперь в PHP поддерживаются статические методы и переменные классов, что улучшает проверку во время трансляции и выполнения сценариев. Статические методы вызываются с помощью оператора ::.

Унифицированные конструкторы ++++++ использование метода __construct() вместо метода с тем же именем, что и имя класса, ++++++ значительно упрощает из+ менение наследования, когда в дерево наследования вовлечено несколько классов.

Классы в PHP теперь имеют деструкторы ++++++ методы __destruct(), которые позволяют выполнять определенные действия во время уничтожения объекта.

В PHP5 была добавлена поддержка абстрактных классов и интерфейсов. Это позволяет определять необходимые методы в родительском классе, а реали+ зовывать их позднее ++++++ в производных классах. Невозможно создать объект

Введение в объектно*ориентированное программирование 517

абстрактного класса, только неабстрактный подкласс данного класса может порождать объекты.

В функциях и методах можно использовать контроль типов для принимаемых па+ раметров. Теперь можно указать класс для параметров функции, которая ожидает передачи ей объекта. Например, функция function foo(Bar $objBar) {...

гарантирует, что типом данных параметра $objBar будет объект класса Bar.

Резюме

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

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

Инкапсуляция ++++++ важное понятие объектно+ориентированного программирова+ ния. Она означает способность класса ограничивать доступ к своим внутренним пе+ ременным и ограждать детали своей внутренней реализации от пользователей. Суще+ ствует три уровня видимости данных и функций класса: частные члены, которые могут использоваться только во внутренних операциях класса; защищенные члены, которые видимы подклассам; общедоступные члены, которые могут использоваться кодом за пределами самого класса.

С введением пятой версии PHP и Zend Engine 2 поддержка объектно+ориентиро+ ванных возможностей была радикально пересмотрена. Новые функции и значительное повышение производительности делают PHP настоящим объектно+ориентированным языком программирования.

Упражнения

1.В чем разница между классом и объектом?

2.Объясните идею наследования и дайте пример того, когда его следует исполь+ зовать, не повторяя при этом примеров, рассмотренных в данной главе.

3.Опишите полезные особенности интерфейса и его практическое применение в программной архитектуре. Чем интерфейс отличается от наследуемого класса? Приведите дополнительные примеры возможного применения интерфейсов.

13

Работа с UML и классами

Одним из самых полезных инструментов для моделирования объектно+ориентирован+ ных программ является UML (Unified Modeling Language ++++++ унифицированный язык моделирования). Данная глава представляет собой введение в UML; в ней объясняет+ ся, почему этот язык стоит использовать, как различные UML+диаграммы способст+ вуют разработке PHP+приложений, а также описывается доступное программное обеспечение, позволяющее PHP+программистам создавать UML+диаграммы. Созда+ ваемые в качестве учебных примеров UML+диаграммы в конце главы составят полно+ масштабный пример приложения: диспетчера контактов.

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

Унифицированный язык моделирования

Громоздкая терминология OO+программирования может сильно затруднять пись+ менное описание множества объектов. Вместе с тем тщательное планирование и четкое документирование объектов является ключевым фактором для успешного создания приложения. Это особенно справедливо в случае крупных приложений, в которых используются десятки объектов. Однако как сгенерировать такую документацию, ко+ торая была бы доступной для тех, кто должен ее читать? Как говорится, одна картина стоит тысячи слов. UML (унифицированный язык моделирования) представляет со+ бой спецификацию, описывающую стандартный процесс создания и визуализации объектно+ориентированных программных систем.

Работа с UML и классами 519

UML ++++++ язык, но не в обычном смысле этого слова. Это система для представления в визуальной форме классов и взаимодействия между ними. В данной системе исполь+ зуются стандартизированные обозначения для построения полной графической мо+ дели приложения. В UML используются изображения для очевидного обозначения комплексных взаимосвязей классов, а также создаются документы, которые впослед+ ствии помогают понять это приложение и возможности его использования и моди+ фикации (это полезно как другим людям, так и самому разработчику по прошествии некоторого времени с момента первоначального выпуска приложения). До опублико+ вания UML+спецификации существовало несколько систем, представляющих анало+ гичные идеи. В UML+спецификацию вошли лучшие черты каждой из этих систем. В результате был сформирован единый стандарт, который отверг предыдущие систе+ мы и установил обобщенный визуальный язык.

В текущей версии UML+спецификации определено 12 различных типов диаграмм, которые описывают структуру, поведение или организацию приложения. В этой главе подробно рассматривается только одна из этих диаграмм, диаграмма классов. Кратко представлены несколько других видов диаграмм. Полное описание UML вы+ ходит за рамки данной книги; доступно множество отличных ресурсов, позволяю+ щих подробнее изучить UML, включая книгу Освой самостоятельно UML 2 за 24 часа (ИД ‘‘Вильямс’’, 2005 г.)

Зачем использовать UML?

Многие разработчики программного обеспечения озабочены необходимостью написания документации. Часто документация предназначается для клиентов, спе+ циалистов по развитию бизнеса и остального нетехнического персонала, и создание такой документации отвлекает разработчиков от прямых обязанностей ++++++ написания кода. Более того, документы, ориентированные главным образом на разработчиков, такие как технические спецификации, часто остаются нетронутыми и непрочитан+ ными после создания. UML+диаграммы задуманы как работоспособное средство для описания проекта, и их создание на самом деле приносит разработчику практическую выгоду во время всего процесса разработки, а не только на стадии планирования.

UML+диаграммы позволяют разработчику перенести на бумагу идеи, касающиеся архитектуры приложения, при этом не требуется тратить время на написание пояс+ нительных текстов. Во время планирования проекта диаграммы упрощают визуаль+ ное воспроизведение крупномасштабных изменений программной архитектуры, по+ зволяя разработчику не заботиться в это время о коде. Диаграммы предоставляют ‘‘носитель’’, посредством которого архитекторы программного обеспечения могут передавать свои идеи тем, кто будет их реализовывать. Но более всего впечатляет то, что в зависимости от инструмента, который используется для создания диаграмм, на базе этих диаграмм фактически можно генерировать большой объем кода для классов; иными словами, разработчик может буквально ‘‘нарисовать’’ код.

Инструменты для создания UML-диаграмм

Существуют десятки приложений, как с открытым исходным кодом, так и коммер+ ческих, которые способны генерировать UML+диаграммы. К сожалению, поддержка автоматического создания PHP+кода в настоящий момент крайне ограничена. Ниже кратко описывается несколько стоящих внимания инструментов.

520Глава 13

Dia (www.lysator.liu.se/~alla/dia/) ++++++ основанное на библиотеке GTK средство, распространяемое под лицензией GPL, которое поддерживает UML+ диаграммы. Существует скомпилированная версия для Windows. Приложение не поддерживает автоматическую генерацию кода. Все UML+диаграммы в книге созданы с помощью Dia.

Visio (www.microsoft.com/office/visio) ++++++ часть офисного пакета Microsoft, которая находит широкое применение от UML+диаграмм и сетевых диаграмм до компоновочных планов и организационных схем. Продукт Dia за+ думывался как открытая альтернатива Visio. Visio также не имеет средств для автоматической генерации кода.

IBM Rational Rose (www.rational.com) ++++++ давний золотой стандарт UML+ разработки, компания Rational Rose предоставляет не только программные ин+ струменты, но и полную методологию разработки программного обеспечения. Имеются генераторы кода для Java и .NET, но поддержка PHP отсутствует.

ArgoUML (http://argouml.tigris.org) ++++++ этот продукт написан на Java, но в настоящее время в нем имеется экспериментальная поддержка генерации PHP+кода. Этот продукт распространяется бесплатно под лицензией BSD+стиля (более подробная информация представлена на Web+сайте ArgoUML).

Umbrello (http://uml.sourceforge.net) ++++++ исключительно Unix+инструмент (нет версии для Windows), имеющий полную поддержку генерации PHP+кода. Продукт распространяется в виде открытого исходного кода.

Poseidon For UML, Professional Edition (www.gentleware.com/products/) ++++++

коммерческий UML+инструмент с полной поддержкой генерации PHP+кода.

Хотя для изучения оставшегося материала книги не требуется ни один из этих ин+ струментов, рекомендуется загрузить один из них, чтобы вы смогли самостоятельно создавать UML+диаграммы, подобные примерам в этой главе. Для создания примеров диаграмм использовалось приложение Dia, поэтому проще всего использовать имен+ но этот продукт. Для этого достаточно посетить соответствующий Web+сайт и следо+ вать инструкциям, в процессе инсталляции и инициализации программы проблем возникнуть не должно.

Существует две причины, по которым генерация PHP+кода ограничена или вообще отсутствует в большинстве упомянутых инструментов. Обычно разработчики про+ грамм на языках сценариев, таких как PHP, редко придерживаются строгих процес+ сов разработки программного обеспечения, которые характерны для Java+ и C+++ разработчиков. UML ++++++ технологическое средство и поэтому разработчики UML+ инструментов не обращали внимания на PHP. Другая причина заключается в том, что до выхода PHP5 в PHP была лишь зачаточная поддержка объектно+ориентированного программирования. Теперь, когда PHP имеет настоящую OO+поддержку, очевидно, следует ожидать того, что большинство из UML+инструментов будет поддерживать генерацию PHP+кода.

Диаграммы классов

Как уже отмечалось, в этой книге описывается только одна из 12 типов диаграмм, определенных в спецификации UML, ++++++ диаграмма классов. Диаграмма классов пока+ зывает классы в приложении и их взаимосвязь.

Работа с UML и классами 521

Базовым обозначением классов (рис. 13.1) является прямоугольник, разделенный на три части. В первой части указывается имя класса, во второй ++++++ его свойства, а в третьей ++++++ методы. Части отделяются друг от друга горизонтальными линиями.

MyClass

Рис. 13.1.

В примере представлен класс с именем MyClass, имеющий два общедоступных свойства (property1 ++++++ целое число и property2 ++++++ строка) и одну частную пере+ менную экземпляра (массив privateData). Также в классе присутствуют два метода: method1() ++++++ общедоступный метод, принимающий один целочисленный параметр param1 (по умолчанию принимается значение 0) и возвращающий булево значение; частный метод privateMethod() не принимает никаких параметров и не возвраща+ ет значений (Void+метод). Знак ‘‘плюс’’ перед свойством или методом указывает на то, что данное свойство или метод является общедоступным, тогда как знак ‘‘минус’’ обо+ значает частный элемент. Защищенные методы обозначаются символом фунта (#).

Очевидно, что понять все это намного проще, глядя на диаграмму, чем читая тек+ стовое описание. Именно поэтому UML используется для описания объектов. Тексто+ вое описание и его форматирование занимает приблизительно в три раза больше времени, чем создание диаграммы в Dia.

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

Создание диспетчера контактов

В остальной части этой главы описывается создание учебного приложения ++++++ дис+ петчера контактов. Приложение позволяет пользователям хранить информацию об организациях, частных лицах (такую как почтовый и e+mail+адреса, номера телефонов и т.д.), а также предоставляет возможность отслеживать взаимосвязь частных лиц и органи+ заций. Фактически те же задачи решает адресная книга Microsoft Outlook.

Приложение должно хранить информацию об адресатах в базе данных и отображать ее на Web+странице. В составе контактной информации адресата могут отсутствовать или быть в наличии почтовые и e+mail+адреса, а также номера телефонов. В контактной информации частного лица имеются сведения о работодателе, т.е. организации, у кото+ рой в свою очередь есть сведения о наемных работниках (частных лицах).

UML-диаграммы диспетчера контактов

Запустите приложение для создания UML+диаграмм и создайте новый файл

ContactManager.[extension], где [extension] ++++++ стандартное расширение фай+ лов в данном приложении (например, .dia для диаграмм, созданных с помощью Dia).

Соседние файлы в папке web - tec