Проектирование классов Лабораторная работа №2

advertisement
Проектирование классов
Лабораторная работа №2
Цель лабораторной работы: научиться проектировать классы и работать с системой
управления версиями.
Данная лабораторная работа состоит из двух частей:


В первой части необходимо создать проект в Visual Studio и разработать в нём классы на
заданную тематику;
Во второй части необходимо научиться работать с системой управления версиями и
поместить проект в хранилище версий.
Основы объектно-ориентированного программирования
Объектно-ориентированное программирование (ООП) — парадигма программирования
(совокупность идей и понятий, определяющих стиль написания компьютерных программ), в
которой основными концепциями являются понятия объектов и классов.
Другими словами: объектно-ориентированное программирование - это методология
программирования, основанная на представлении программы в виде совокупности объектов,
каждый из которых является экземпляром определенного класса, а классы образуют иерархию
наследования.
Объектное и объектно-ориентированное программирование возникло в результате развития
идеологии процедурного программирования, где данные и подпрограммы (процедуры, функции)
их обработки формально не связаны. Кроме того, в современном объектно-ориентированном
программировании часто большое значение имеют понятия события (так называемое событийноориентированное программирование) и компонента (компонентное программирование).
Основные концепции:





Система состоит из объектов.
Объекты некоторым образом взаимодействуют между собой
Каждый объект характеризуется своим состоянием и поведением
Состояние объекта задаётся значением полей данных
Поведение объекта задаётся методами
Инкапсуляция
Инкапсуляция — это свойство системы, позволяющее объединить данные и методы, работающие
с ними в классе, и скрыть детали реализации от пользователя.
Т.е. при использовании класса достаточно знать функции, которые он выполняет, не задумываясь
как внутри класса производится обработка и вычисления.
Можно привести аналогию с реальным миром: пользуясь сотовым телефоном человек не
задумывается что происходит когда он начинает звонок и разговор с другим абонентом. Для него
достаточно знать, что набрав номер телефона он инициирует звонок и может разговаривать
используя микрофон для передачи голоса, и динамик для того, чтобы услышать говорящего.
Полиморфизм
Полиморфизм — это свойство системы использовать объекты с одинаковым интерфейсом без
информации о типе и внутренней структуре объекта.
Если взять пример из реального мира: Производится множество различных моделей сотовых
телефонов, но интерфейс взаимодействия с пользователем у них практически одинаковый.
Абстракция данных
Абстракция данных — подход к обработке данных по принципу чёрного ящика. Данные
обрабатываются функцией высокого уровня с помощью вызова функций низкого уровня.
Абстракция в объектно-ориентированном программировании — это придание объекту
характеристик, которые чётко определяют его концептуальные границы, отличая от всех других
объектов. Основная идея состоит в том, чтобы отделить способ использования составных
объектов данных от деталей их реализации в виде более простых объектов, подобно тому, как
функциональная абстракция разделяет способ использования функции и деталей её реализации в
терминах более примитивных функций, таким образом, данные обрабатываются функцией
высокого уровня с помощью вызова функций низкого уровня.
Фундаментальная идея состоит в разделении несущественных деталей реализации
подпрограммы и характеристик, существенных для корректного ее использования. Такое
разделение может быть выражено через специальный «интерфейс», сосредотачивающий
описание всех возможных применений программы.
Под «интерфейсом» понимается некий набор методов и средств доступа к данным класса,
которые могут использоваться извне. Например, класс для генерации случайных чисел содержит
метод, при вызове которого возвращается произвольное случайное число. Доступа к алгоритму
генерации у программиста нет, но есть возможность вызвать метод, который возвратит результат
его работы. Этом и заключается основной принцип абстрации – есть метод, который выполняет
определённый алгоритм, при этом, логика работы самого алгоритма скрыта.
Класс
Идея классов пришла из работ по базам знаний, имеющих отношение к исследованиям по
искусственному интеллекту. Используемые человеком классификации в зоологии, ботанике,
химии, деталях машин, несут в себе основную идею, что любую вещь всегда можно представить
частным случаем некоторого более общего понятия. Конкретное яблоко — это в целом некоторое
яблоко, вообще яблоко, а любое вообще яблоко — фрукт.
Под классом подразумевается некая сущность, которая задает некоторое общее поведение для
объектов.
Например: Класс «Автомобиль» задаёт общее поведение для всех автомобилей.
Класс это структурное описание, которое делается на этапе написания кода. В процессе
выполнения программы создаются экземпляры класса (объекты), которые содержат конкретные
данные и методы и взаимодействуют между собой.
Определение класса выглядит следующим образом:
class Parent
{
}
Виды классов
Существуют следующие, основные виды классов:



Базовый (родительский класс). Базовый класс содержит данные и методы, общие для
всех его потомков. Например: класс «Автомобиль» может содержать свойство
«Количество колёс», которое будет общим для всех его потомков, если подразумевается,
что все автомобили имеют колёса;
Производный класс (наследник, потомок). Производный класс содержит собственные
методы и данные, которые расширяют функциональность базового класса. Например:
класс «Автобус» может хранить номер маршрута;
Абстрактный класс (виртуальный) класс. Данный класс может быть как базовым, так и
производным. Основное отличие заключается в том, что нельзя создавать экземпляры
данного класса (объекты).
Структура класса
В качестве основных составляющих класса (общих для всех объектно-ориентированных языков
программирования) являются:

Поля. В полях хранятся данные класса. Все данные объекта хранятся в его полях. Доступ к
полям осуществляется по их имени;
Определение поля выглядит следующим образом:
class Parent
{
int _value;
}
В данном примере объявлено поле класса с именем «_value» с типом int
Обычно каждому объекту соответствуют собственные значения всех его полей. Можно объявить
поле, которое будет единым для всех экземпляров класса. Такие поля называются статическими.
Внутри класса к данному полю можно обращаться по его имени. Снаружи класса для доступа к
полю необходимо обращаться через имя класса.
Определение статического поля выглядит следующим образом:
class Parent
{
static int _value;
}
Обращение к статическому полю, не внутри класса выглядит следующим образом:
Parent._value = 10;

Свойства. Свойства необходимы для того, чтобы управлять доступом к полям класса. При
проектировании классов необходимо иметь возможность управлять процессом установки
значений полям класса, которые делаются как из других, так и из дочерних классов.
Определение свойства для поля выглядит следующим образом:
class Parent
{
int _value;
int Value
{
get
{
Return _value;
}
set
{
_value = value;
}
}
}
В данном примере, в разделе «get» возвращается значение свойства, а в разделе «set»
устанавливается. Один из разделов может отсутствовать, тогда свойство будет либо только на
чтение, либо только на запись. То, что возврат и установка значений делается в блоке кода, даёт
возможность контроля за значениями.

Методы. Методы позволяют управлять данными класса и производить различные
вычисления.
Определение метода выглядит следующим образом:
class Parent
{
int _value;
int Calculate(int parAmount)
{
return _value + parAmount;
}
}
В данном примере объявлено поле класса с именем «_value» с типом int

Конструктор. Специальный метод класса в объектно-ориентированном
программировании, служащий для инициализации объекта при его создании.
Конструктором класса называется функция, имеющая то же имя, что и сам класс, и не
возвращающая никакого значения. Говоря более простым языком, конструктором
называется тот метод класса, который вызывается автоматически при создании
экземпляра класса. Имя конструктора должно совпадать с именем класса. Допускается
использовать несколько конструкторов с одинаковым именем, но различными
параметрами.
Определение конструктора выглядит следующим образом:
class Parent
{
Parent()
{
}
}
Различают следующие, основные, виды конструкторов:
o
o
Конструктор по умолчанию. Конструктор не имеющий аргументов. В отсутствие
явно заданного конструктора по умолчанию его код генерируется компилятором
(что в исходном тексте, не отражается). Пример конструктора по умолчанию указан
выше;
Конструктор копирования. Конструктор, аргументом которого является ссылка на
объект того же класса. Используется, если при создании нового объекта, в него
нужно скопировать данные из другого, аналогичного типа.
Определение конструктора копирования выглядит следующим образом:
class Parent
{
int _value = 0;
Parent(Parent parParentCopy)
{
_value = parParentCopy._value;
}
}

Деструктор. Специальный метод класса, служащий для деинициализации объекта
(например освобождения памяти). Имя деструктора должно совпадать с именем класса и
иметь префикс ~. У класса может быть только один деструктор. Деструктор не имеет
модификатора доступа и параметров.
Определение деструктора выглядит следующим образом:
class Parent
{
~Parent()
{
}
}
Отношения между классами
Классы могут взаимодействовать друг с другом, как на этапе проектирования, так и на этапе
выполнения программы. Существуют следующие, основные, отношения между классами:

Наследование. Позволяет описать новый класс на основе уже существующего
(родительского), при этом свойства и функциональность родительского класса
наследуются новым классом. Например: у класса «Автомобиль» есть свойство
«Количество колёс». Если класс «Автобус» является наследником класса «Автомобиль», то
он также наследует и свойство «Количество колёс»;
Опредедение наследования класса выглядит следующим образом:
class Child : Parent
{
}
В данном примере, класс «Child» объявляется как наследник класса «Parent»;
Различают следующие виды наследования:
o
o

Простое наследование. Пример данного наследования показан выше. При простом
наследовании дочерний класс наследуется от одного родительского;
Множественное наследование. При множественном наследовании дочерний класс
может наследовать свойства и функционал нескольких родительских классов.
Возможность реализации данного наследования реализовано в языке C++;
Ассоциация. Объекты классов вступают во взаимодействие между собой. Т.е. методы
одного класса могут создавать и использовать экземпляры (объекты) другого класса;
Пример ассоциации выглядит следующим образом:
Class Client
{
string _name = “Иванов Иван Иванович”;
float _rest = 0.0f;
void LoadRest()
{
Account acc = new Account();
_rest = acc.LoadRestForClient(_name);
}
}
В данном примере в методе «LoadRest» класса «Client» используется класс «Account» для
получения информации по остатке на счёте клиента.

Агрегация. Объекты одного класса входят в объекты другого. В данном случае,
подразумевается, что данными одного класса являются экземпляры других классов.
Например: Есть класс «Двигатель», который содержит данные и методы,
характеризующие двигатели. У класс «Автомобиль» есть аналогичное свойство, которое
принимает значение одного из экземпляров класса «Двигатель»;
Пример ассоциации выглядит следующим образом:
Class Engine
{
}
Class Car
{
Engine _engine;
}
В данном примере класс «Car» содержит поле с типом «Engine», что является агрегацией.

Композиция. Объекты одного класса, входят в объекты другого и зависят друг от друга по
времени жизни. Например: Экранная форма является отдельным классом. На ней
расположены различные компоненты (поля для ввода, кнопки и пр.), которые тоже
являются классами. При этом, все они, составляют композицию, время жизни всех этих
объектов, составляющих композицию, ограничено временем жизни формы (при закрытии
формы все объекты удаляются).
Области доступа
Область доступа указывает участки кода, откуда можно обращаться к данной составляющей
класса. В большинстве объектно-ориентированных языков программирования поддерживаются
следующие области доступа:

private (закрытая составляющая класса) – обращения допускаются только из методов
класса, в котором определена данная составляющая. Например, если в классе
«Автомобиль» есть закрытая составляющая «Серийный номер», то прочитать её значение
можно только в методах этого же класса. Ни дочерние классы, ни те, которые будут
создавать экземпляр «Автомобиль» доступа к данному свойству получить не могут;
Закрытые составляющие класса объявляются следующим образом:
class Parent
{
private int _value;
private int Calculate(int parAmount)
{
reutrn _value + parAmount;
}
}
В данном поле и метод класса объявлены как закрытые;

protected (защищённая, внутренняя составляющая иерархии классов) – обращения
допускаются из методов класса, а также из методов всех дочерних классов. Например,
если в классе «Автомобиль» есть закрытая составляющая «Серийный номер», то прочитать
её значение можно только в методах этого класса и в методах любых дочерних классов.
Если другие классы будут создавать экземпляр класса «Автомобиль», то они не смогут
получить доступ к данному свойству;
Защищённые составляющие класса объявляются следующим образом:
class Parent
{
protected int _value;
protected int Calculate(int parAmount)
{
return _value + parAmount;
}
}
В данном поле и метод класса объявлены как закрытые;

public (открытый член класса) – обращения к составляющей класса возможны из любого
кода.
Открытые составляющие класса объявляются следующим образом:
class Parent
{
public int _value;
public int Calculate(int parAmount)
{
return _value + parAmount;
}
}
В данном поле и метод класса объявлены как закрытые;
Если область доступа не указывается явно, то она устанавливается в соответствии с настройками
среды разработки (область доступа по умолчанию).
Помимо области доступа составляющих класса, у самого класса также есть область доступа:


internal – При данной области доступа, к классу можно обращаться только внутри
библиотеки/программы, где он объявлен. Т.е. если класс объявлен в одной библиотеке с
доступом internal, то его нельзя будет использовать в других библиотеках, приложениях;
public – При данной области доступа, к классу можно обращаться из любых модулей;
Пример объявления области доступа класса:
public class Parent
{
}
Советы по использованию уровня доступа
Обычно область доступа полей класса делают закрытой (private), т.е. доступ к ним разрешается
только методам того же класса. Чтобы предоставить пользователям класса значения его полей,
используются свойства: они позволяют классу контролировать изменение его полей, например
проверять принадлежность заданного значения диапазону допустимых значений.
Когда доступ к полю инкапсулируется процедурами свойства, возможно обновлять код,
обрабатывающий изменения значения этого свойства, не нарушая совместимость с
существующими пользователями класса. Поэтому принято создавать свойства для доступа к
полям класса даже тогда, когда на начальном этапе разработки не требуется никакой
дополнительной обработки присваиваемых полю значений.
Примеры использования:
/// <summary>
/// Класс комплексного числа
/// </summary>
class Complex
{
/// <summary>
/// Целая часть числа
/// </summary>
private double re;
/// <summary>
/// Мнимая часть числа
/// </summary>
private double im;
/// <summary>
/// Конструктор с инициализацией
/// </summary>
public Complex(double i_re, double i_im)
{
re = i_re;
im = i_im;
}
/// <summary>
/// Целая часть числа
/// </summary>
public double RE
{
get
{
return re;
}
set
{
re = value;
}
}
/// <summary>
/// Мнимая часть числа
/// </summary>
public double IM
{
get
{
return im;
}
set
{
im = value;
}
}
}
Пример работы с классом:
Complex complex1 = new Complex(1, 1);
Complex complex2 = new Complex();
complex2.IM = 2;
complex2.RE = 2;
complex1.RE = complex2.RE;
«Паблик Морозов»
Паблик Морозов — в объектно-ориентированном программировании шуточное название классапотомка, который обеспечивает открытый доступ к закрытым полям класса-предка. Наличие
объектов типа «Паблик Морозов» говорит об избыточно закрытой конструкции библиотеки — или
даже об инверсии абстракции в ней.
Это название — каламбур, основанный на созвучии ключевого слова public (паблик), часто
означающего открытый доступ к методам и полям класса, и имени пионера-героя Павлика
Морозова, известного тем, что он выдал своего отца-кулака.
Абстрактный метод
Если в процессе проектирования необходимо для родительского класса указать метод, который
будет реализован в классах – потомках, но его реализация в родительском классе не нужна, то он
объявляется как абстрактный. Другими словами: абстрактный метод не имеет реализации при
объявлении.
Пример объявления абстрактного метода:
public class Parent
{
public abstract float Calculate(float parValue);
}
Абстрактный класс
Абстрактным называют класс, содержащий хотя бы один абстрактный метод. Абстрактный метод
не реализуется для класса, в котором описан, однако должен быть реализован для его
неабстрактных потомков. Особенностью абстрактного класса является то, что нельзя создавать его
экземпляры (объекты данного класса). В некоторых языках программирования можно объявлять
абстрактные классы без абстрактных методов.
Пример объявления абстрактного класса:
public abstract class Parent
{
}
Виртуальный метод
Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании
метод (функция) класса, который может быть переопределён в классах-наследниках так, что
конкретная реализация метода для вызова будет определяться во время исполнения. Таким
образом, программисту необязательно знать точный тип объекта для работы с ним через
виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику
класса, в котором метод объявлен.
Виртуальные методы — один из важнейших приёмов реализации полиморфизма. Они позволяют
создавать общий код, который может работать как с объектами базового класса, так и с
объектами любого его класса-наследника. При этом, базовый класс определяет способ работы с
объектами и любые его наследники могут предоставлять конкретную реализацию этого способа.
Базовый класс может и не предоставлять реализации виртуального метода, а только
декларировать его существование. Такие методы без реализации называются «чисто
виртуальными» или абстрактными. Т.е. абстрактные методы являются виртуальными.
Для каждого класса, имеющего хотя бы один виртуальный метод, создаётся таблица виртуальных
методов. Каждый объект хранит указатель на таблицу своего класса. Для вызова виртуального
метода используется следующий механизм: из объекта берётся указатель на соответствующую
таблицу виртуальных методов, а из неё, по фиксированному смещению, — указатель на
реализацию метода, используемого для данного класса. При использовании множественного
наследования или интерфейсов ситуация несколько усложняется за счёт того, что таблица
виртуальных методов становится нелинейной.
Пример объявления и работы виртуальных методов
class Ancestor
{
public virtual void function1 ()
{
Console.WriteLine(”Ancestor.function1()”);
}
public void function2 ()
{
Console.WriteLine(”Ancestor.function2()”);
}
}
class Descendant : Ancestor
{
public override void function1 ()
{
Console.WriteLine(”Descendant.function1()”);
}
public void function2 ()
{
Console.WriteLine(”Descendant.function2()”);
}
}
…
Descendant descendant = new Descendant();
Ancestor ancestor = descendant;
descendant.Function1();
descendant.Function2();
ancestor.Function1();
ancestor.Function2();
В этом примере класс Ancestor определяет две функции, одну из них виртуальную, другую — нет.
Класс Descendant переопределяет обе функции. Однако, казалось бы одинаковое обращение к
функциям, даёт разные результаты. На выводе программа даст следующее:
Descendant.Function1
Descendant.Function2
Descendant.Function1
Ancestor.Function2
Для продолжения нажмите любую клавишу . . .
Объект
Объект наряду с понятием «класс», является важным понятием объектно-ориентированного
подхода в программировании. Под объектом подразумевается некоторая сущность, обладающая
состоянием и поведением. Как правило, при рассмотрении объектов выделяется, что объекты
принадлежат одному или нескольким классам, которые в свою очередь определяют поведение
объекта. Время с момента создания объекта до его уничтожения называется временем жизни
объекта.
Инстанцирование (англ. instantiation) — создание экземпляра класса. В отличие от слова
«создание» применяется не к объекту, а к классу. То есть говорят создать экземпляр класса или
инстанцировать класс. Порождающие паттерны используют полиморфное инстанцирование.
Экземпляр класса (англ. instance) — это описание конкретного объекта в памяти. Класс описывает
свойства и методы, которые будут доступны у объекта, построенного по описанию, заложенному в
класс. Экземпляры используют для представления конкретных сущностей реального мира
Пример создания экземпляра класса (объекта):
Parent parent = new Parent();
Интерфейс
Интерфейс (от лат. inter — между и лат. face — поверхность) — это семантическая и
синтаксическая конструкция в коде программы, используемая для специфицирования услуг,
предоставляемых классом или компонентом.
С одной стороны, интерфейс — это контракт, который обязуется выполнить класс, реализующий
его. Один класс может реализовать несколько интерфейсов одновременно.
С другой стороны, интерфейс — это тип данных, потому что его описание достаточно четко
определяет свойства объектов, чтобы наравне с классом типизировать переменные.
Объявление интерфейса и его использования выглядит следующим образом:
interface IDoPrint
{
void DoPrint();
}
class Class1 : IDoPrint
{
public void DoPrint()
{
Console.WriteLine("Class1.DoPrint");
}
}
class Class2 : IDoPrint
{
public void DoPrint()
{
Console.WriteLine("Class2.DoPrint");
}
}
…
IDoPrint doPrint;
Class1 class1 = new Class1();
Class2 class2 = new Class2();
doPrint = class1;
doPrint.DoPrint();
doPrint = class2;
doPrint.DoPrint();
Таким образом, интерфейсы позволяют создавать общие функциональности у не связанных друг с
другом классов.
Особенностью работы с интерфейсами является то, что один класс может наследовать
функционал нескольких интерфейсов.
Диаграммы классов
Для проектирования классов удобно использовать диаграммы, которые позволяют
визуализировать процесс.
Рассмотрим проектирование классов на примере диаграмм из Microsoft Visual Studio.
Новую диаграмму классов можно добавить через контекстное меню, которое вызывается при
нажатии правой кнопки мыши на названии проекта.
Рисунок 1
Добавление нового элемента в проект
После этого, в окне выбора нового элемента, необходимо выбрать элемент «Class Diagram».
Рисунок 2
Выбор элемента «Диаграмма классов»
Внизу окна, в поле «Name», необходимо дать название файлу с диаграммой.
После нажатия на кнопку «Add» в проект добавляется диаграмма классов.
Рисунок 3
Форма редакрирования диаграммы классов
Диаграмма может работать не только с классами, но и с другими структурами (перечислениями,
интерфейсами), в рамках данного описания рассматривается только работа с классами.
Форма состоит из следующих частей:


Область редактирования диаграммы. Находится в центре формы. Здесь необходимо
помещать проектируемые элементы;
Область редактирования деталей выбранного класса. Находится под областью
редактирования. В области редактирования можно создавать, изменять, удалять поля
класса, свойства, методы и события.
Диаграмму можно использовать двумя способами:
1. Редактировать существующие классы. Для этого, необходимо «перетащить» классы из
проекта на диаграмму;
2. Проектировать новые и изменять существующие классы. Добавление на диаграмму новых
классов, создание связей между ними осуществляется через панель инструментов слева.
Для добавления нового класса необходимо выбрать на панели инструментов иконку с названием
«Class» и перетащить её на область редактирования диаграммы.
После этого появится диалоговое окно создания нового класса:
Рисунок 4
Диалоговое окно создания нового класса
На данном окне необходимо заполнить следующие поля:



Class Name: Имя создаваемого класса;
Access: Уровень доступа для класса;
File name: Вариант добавить новый файл для класса («Create new file») и вариант добавить
в существующий файл («Add to existing file»). Всегда необходимо выбирать добавление
нового файла для класса. И задавать ему такое же имя, как и для класса.
После нажатия на кнопку «Ок» класс добавится на область редактирования.
Рисунок 5
Показ класса на области редактирования
После выбора класса на области редактирования в панели деталей класса можно добавлять,
изменять и удалять:




Методы (Methods);
Свойства (Properties);
Поля (Fields);
События (Events);
Принцип добавления значений одинаков: Достаточно написать название в столбце «Name» и
заполнить значения оставшихся столбцов.
При добавлении методов, дополнительно, можно добавлять параметры. Чтобы
добавлять/редактировать параметры метода необходимо раскрыть их список. Это делается
нажатием на кнопку «+» рядом с именем метода.
Пример заполненных составляющих для класса:
Рисунок 6
Заполненные элементы класса
Дополнительные параметры для каждого составляющего класса можно заполнить на панели
свойств (вызывается при нажатии клавиш Ctrl+W, P). На приведённом выше примере, панель
свойств располагается справа.
Если выбрать класс и нажать «Enter», то покажется исходный код класса.
Рисунок 7
Текст класса
Для указания наследования одного класса от другого необходимо произвести следующие
действия:



Выбрать «Inheritance» в панели «Toolbox» слева;
Нажать и удерживать левую кнопку мыши над классом – наследником;
Продолжая удерживать левую кнопку мыши, переместить курсор, пока он не окажется над
классом наследником. На экране будет показываться пунктирная линия, соединяющая
классы:
Рисунок 8

Связывание дочернего класса с родительским
Отпустить кнопку мыши.
После отпускании кнопки мыши создастся связь между классами и будет отображена на
диаграмме.
Рисунок 9
Отображение связи дочернего класса с родительским
Исходный код дочернего класса будет выглядеть следующим образом:
Рисунок 10
Исходный текст дочернего класса
Задание
В соответствии с вариантом задания разработать программу, используя рекомендации,
описанные в данной лабораторной работе. Программа должна быть написана на языке
программирования C#, использую среду разработки Microsoft Visual С# Express Edition.
Для выполнения лабораторной работы необходимо создать новый проект.
В последующих лабораторных работах будут использоваться разработанные классы, поэтому
рекомендуется их оформить в виде библиотеки.
При выполнении работы необходимо спроектировать иерархию классов согласно заданию.
Иерархия должна состоять как минимум из трех классов, в каждом классе должно быть не менее:



Одного поля;
Одного метода;
Одного свойства.
Для разработанных классов необходимо создать в проекте диаграмму классов.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
Сотрудники компании
Каталог товаров
Инструменты для косьбы
Двигатели
Лодки
Самолеты
Бритвы
Телевизоры
Плееры
Замки для закрывания/открывания
Средства для приготовления еды (не посуда, а на чём готовят)
Музыкальные инструменты
Поезда
Часы
Телефоны
Download