ООП - объектно-ориентированое программирование

Общие принципы, проектирование, модуляризация, темплейты и шаблоны

Re: ООП - объектно-ориентированое программирование

Postby Vitekkz88 on 11 Sep 2018, 19:03

jane_wild, еще какой реальный и довольно понятный.
Не бывает отдельного ООП для LV, отдельного ООП для Java, отдельного ООП для C++. Модель везде одинаковая, просто представление отличается и некоторые фишки. Чем раньше это поймете, тем лучше.
Зайти на ООП через практику - это как влазить в дом через дымоход. После того, как поймете теорию(именно поймёте) - попробуйте собрать этот пример самостоятельно, тем более для этого есть всё :D
За ссылку - пожалуйста...лишь бы просмотр не был ради просмотра, как с чтением :cry:
Инженер - это открыто светящийся интеллект, свободный и не обидный юмор, это легкость и широта мысли...Это воспитанность, тонкость вкусов, хорошая речь, плавно согласованная и без сорных словечек...
-А. И. Солженицын
User avatar
Vitekkz88
expert
expert
 
Posts: 1033
Joined: 21 Jan 2014, 15:45
Location: Томск
Medals: 3
Activity (1) Silver (1) Автор (1)
LabVIEW Version: 12,13,14
Karma: 300
hardware I/O VIP

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 11 Sep 2018, 22:07

jane_wild wrote:Если дочерние классы содержат свои данные A, B, C, тогда зачем они в базовом классе?

"Физически" дочерние классы не содержат поля А, В, С. Их содержит только базовый класс. Под "физическим" содержанием я имею ввиду эти поля в тайп-деф котроле класса.
Дочерние классы таким образом лишь имеют доступ к этим полям, и таки образом, могут хранить/читать данные "в себе", в своем контексте. Но, если базовый класс не будет иметь read/write методы (геттеры, и сеттеры), то тогда и дочерние классы не могут менять/читать напрямую эти поля.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby jane_wild on 11 Sep 2018, 22:56

"Физически" дочерние классы не содержат поля А, В, С. Их содержит только базовый класс.

Вот это уже более логично, но тогда возникает вопрос: как Vitekkz88 умудряется записывать/считывать если эти самые данные имеют разный тип у дочерних классов? В одном булевый тип, а в другом double. Поэтому я и сделала вывод, что данные у дочерних классов свои собственные, тогда зачем A, B, C в базовом. Кроме того базовый класс не имеет к ним доступа и как следствие никому его не предоставляет...
User avatar
jane_wild
user
user
 
Posts: 78
Joined: 30 Jun 2016, 02:11
LabVIEW Version: 2016
Karma: 0

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 11 Sep 2018, 23:27

jane_wild wrote:Вот это уже более логично, но тогда возникает вопрос: как Vitekkz88 умудряется записывать/считывать если эти самые данные имеют разный тип у дочерних классов? В одном булевый тип, а в другом double. Поэтому я и сделала вывод, что данные у дочерних классов свои собственные, тогда зачем A, B, C в базовом. Кроме того базовый класс не имеет к ним доступа и как следствие никому его не предоставляет...

Извиняюсь, этот момент я проглядел... Я просмотрел лишь скрины, а сам проект не открывал. Действительно, в таком случае каждый дочерний класс имеет свои поля, и базовый класс в них, по сути, не нуждается; и они там лишние. Если бы дочерние классы имплементрировали операции с числами, то тогда они могли бы использовать снова родительский класс и его поля данных.
Смотрите, все просто. В идеале, в родительские классы будут иметь поля, которые могут/или же нет использовать дочерние классы. Такую возможность тогда предоставляют геттеры и сеттеры. И дочерним классам нужно давать доступ лишь к тем полям, которые они действительно будут использовать - ведь давая доступ ко всем полям, Вы можете изменить логику работы родительского класса по ошибке, а это будет уже проблемой.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby Vitekkz88 on 12 Sep 2018, 05:41

Kosist, выше я уже дал ответ на этот вопрос:
jane_wild: Если дочерние классы содержат свои данные A, B, C, тогда зачем они в базовом классе?
Vitekkz88: Потому что класс описывает объект, у которого есть атрибуты и методы. Абстрактный класс может содержать, а может и не содержать полей и методов.

Kosist: Действительно, в таком случае каждый дочерний класс имеет свои поля, и базовый класс в них, по сути, не нуждается; и они там лишние
Они там как минимум не вносят логического диссонанса.
Kosist: Физически" дочерние классы не содержат поля А, В, С.
jane_wild:Вот это уже более логично...В одном булевый тип, а в другом double.
Вот в этом и заключается проблема непонимания: во-первых, наследование не обязывает использовать всё, что определенно в родительском классе(хотя это уже ближе к композиции). Иными словами, вы можете описать абстрактный объект и не использовать его поля. Да и методы можете не использовать, смотря как проектируете :D
Я описал его(абстрактный объект) в абстрактном классе. Во-вторых - я эти поля мог бы использовать для обычного калькулятора, но я этой цели не преследовал.
В примере мы наследуем от родительского класса, но используем только методы. Поля я определил в дочерних классах самостоятельно с нужным мне типом данных. Смысловая нагрузка атрибутов одинаковая, только представление разное. Поэтому в абстрактном классе даётся общее описание, а в дочерних классах уже конкретное.

Kosist:В идеале, в родительские классы будут иметь поля, которые могут/или же нет использовать дочерние классы. Такую возможность тогда предоставляют геттеры и сеттеры. И дочерним классам нужно давать доступ лишь к тем полям, которые они действительно будут использовать - ведь давая доступ ко всем полям, Вы можете изменить логику работы родительского класса по ошибке
Верно, только set-ер нужно делать приватным, а get-ер - на своё усмотрение(как правило публичным). В этом случае никто данные шатать не будет кроме самого класса и инкапсуляция сохранится.

KosistЯ просмотрел лишь скрины, а сам проект не открывал.
А проект я и не выкладывал, там самому его собрать можно на раз-два.
Инженер - это открыто светящийся интеллект, свободный и не обидный юмор, это легкость и широта мысли...Это воспитанность, тонкость вкусов, хорошая речь, плавно согласованная и без сорных словечек...
-А. И. Солженицын
User avatar
Vitekkz88
expert
expert
 
Posts: 1033
Joined: 21 Jan 2014, 15:45
Location: Томск
Medals: 3
Activity (1) Silver (1) Автор (1)
LabVIEW Version: 12,13,14
Karma: 300
hardware I/O VIP

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 12 Sep 2018, 16:28

Vitekkz88 wrote:Верно, только set-ер нужно делать приватным, а get-ер - на своё усмотрение(как правило публичным). В этом случае никто данные шатать не будет кроме самого класса и инкапсуляция сохранится.

Тут я не совсем согласен. Если сеттер (родительского класса) будет private, то его может использовать лишь родительский класс; а дочерние - нет. Тогда метод-сеттер будет лишним, т.к. в родительском классе поле можно достать и без метода, а прямо "вытащить" его, как из кластера.
Другое дело - сделать его protected. Тогда да, дочерние классы могут его использовать, а все остальные, все контекста этих классов - нет.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby Vitekkz88 on 12 Sep 2018, 18:12

Сеттер делают приватным, а если кому-то нужен доступ и об этом просят - открывают(чаще protected, ага...а иногда прям public, тоже от степени необходимости и осторожно). Закрыт для того, чтоб инкапсуляцию не потерять. Детей может быть много, потом ищи-свищи кто гадит. Вы можете не хотеть давать права на изменение тех или иных полей, поэтому должны их защищать от записи. Сеттер это позволяет сделать в явном виде и избыточным он не будет. Или всё ещё будет? :crazy: С геттером наоборот - всегда доступен, чтоб читать могли.
Инженер - это открыто светящийся интеллект, свободный и не обидный юмор, это легкость и широта мысли...Это воспитанность, тонкость вкусов, хорошая речь, плавно согласованная и без сорных словечек...
-А. И. Солженицын
User avatar
Vitekkz88
expert
expert
 
Posts: 1033
Joined: 21 Jan 2014, 15:45
Location: Томск
Medals: 3
Activity (1) Silver (1) Автор (1)
LabVIEW Version: 12,13,14
Karma: 300
hardware I/O VIP

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 12 Sep 2018, 22:26

Vitekkz88 wrote: Сеттер это позволяет сделать в явном виде и избыточным он не будет. Или всё ещё будет? :crazy:

Конечно будет лишним. Зачем создавать виайку, которая будет делать то, что можно сделать и без нее? Плюс по умолчанию, accessors создаются с public scope, т.е. нужно вручную менять на private - лишние телодвижения :crazy:
Ну а так - дело вкуса, конечно. Во всем есть плюсы, и минусы. С одной стороны - лишняя виайка. Но с другой - есть один удобный плюс - легко можно найти, где изменяется то или иное поле внутри класса, т.к. можно через древо проекта найти всех callers. Нужно над этим задуматься тогда... :think:
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby Artem.spb on 13 Sep 2018, 11:08

Kosist wrote:
Vitekkz88 wrote: Сеттер это позволяет сделать в явном виде и избыточным он не будет. Или всё ещё будет? :crazy:

Конечно будет лишним. Зачем создавать виайку, которая будет делать то, что можно сделать и без нее?

Эдак можно скатиться до маразма: зачем SubVI, когда можно обойтись без них.
Artem.spb
expert
expert
 
Posts: 1371
Joined: 31 Jul 2011, 23:05
Medals: 2
Activity (1) Автор (1)
LabVIEW Version: 12,14,15
Karma: 237
CLD hardware I/O VIP freelance

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 13 Sep 2018, 14:56

Artem.spb wrote:Эдак можно скатиться до маразма: зачем SubVI, когда можно обойтись без них.

Со всем уважением, но сравнение не корректное.
SubVI нужны для создания модульного кода, который можно использовать повторно и не повторять один и тот же код во многих местах; и обычно (но не всегда) сабвиайки содержат внутри больше, чем одну функцию. А сеттер/геттер - ни что иное, как bundle by name/unbundle by name функция, ну и кейс-структура на обработку ошибок (да и то, не всегда). Для них даже не нужно юнит тесты делать, ведь там нет (в идеале) дополнительного функционала.
Вы же в сабвиайках обычных не делаете для каждого элемента кластера отдельный сеттер/геттер? Ведь параметр можно достать прямо из кластера. Так вот - зачем для класса делать accessor, который будет использовать только в этом классе? Если у меня 30 полей в классе, то это может быть до 60 дополнительных виаек в проекте. А зачем? Читабельность кода это не улучшит.
Даже в текстовых языках геттеры/сеттеры объявляются лишь для тех полей, которые должны быть доступны вне этого класса. А те, которые используются лишь внутри этого класса - их можно считать/изменить напрямую, ведь это обычная переменання с ограниченым scope.
Я понимаю, зачем Вы так делаете - с точки зрения общего овервью проекта это имеет смысл; Вы видите сразу какие параметры читаются, какие пишутся - а уже за тем можете менять их тип, чтобы другие классы могли "достучаться" до этих данных. Но с другой стороны, это лишние методы, которые другого разработчика, не привыкшего к такому стилю, могут запутать.
Я ведь не говорю, что это плохо так делать - просто как по мне, это лишняя работа; а выгоды не так уж и много.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby Vitekkz88 on 14 Sep 2018, 07:04

Да никто и не говорит, что Вы говорите, что это плохо :crazy:
Kosist писал(а):, Если у меня 30 полей в классе, то это может быть до 60 дополнительных виаек в проекте. А зачем? Читабельность кода это не улучшит. Даже в текстовых языках геттеры/сеттеры объявляются лишь для тех полей, которые должны быть доступны вне этого класса.
А что Вас удивляет? Если у Вас такой объект и все поля могут требовать доступа, то будет +60 доп.методов. С читабельностью ничего не случится, просто дерево проекта раздуется. Работать с ООП и волноваться по поводу большого дерева проекта не стоит. Разумеется, не стоит создавать пару гет/сет для полей, которые используются только для внутренних нужд класса. Вы всё верно говорите, но в Вас борются два мнения между собой :D Мол, этого делать не нужно, а с другой стороны -можно и делать, как хотите в общем.
Чем меньше полей и методов будет использовано для полного описания объекта - тем качественнее будет сам класс. А если класс разрабатывают с прицелом на будущее - то очень удобно иметь уже реализованные методы доступа заменяя ключевое слова модификатора(private, public, protected).

Kosist писал(а): Это лишние методы, которые другого разработчика, не привыкшего к такому стилю, могут запутать. Я ведь не говорю, что это плохо так делать - просто как по мне, это лишняя работа; а выгоды не так уж и много.
Ни одного разработчика в текстовых языках это не запутает, да и LV-разраб не запутается, если есть опыт и понимание. Стиль может показаться непривычным, эт верно. Вообще, чтобы втянуть нового разработчика в проект - с ним(с разработчиком) проводится беседа и рассказывается о философии разработки ПО, которой придерживается компания или команда. Во всех компаниях есть свои фишки, которые будут непривычным первое время, но в целом все компании занимаются одним и тем же программированием.
Инженер - это открыто светящийся интеллект, свободный и не обидный юмор, это легкость и широта мысли...Это воспитанность, тонкость вкусов, хорошая речь, плавно согласованная и без сорных словечек...
-А. И. Солженицын
User avatar
Vitekkz88
expert
expert
 
Posts: 1033
Joined: 21 Jan 2014, 15:45
Location: Томск
Medals: 3
Activity (1) Silver (1) Автор (1)
LabVIEW Version: 12,13,14
Karma: 300
hardware I/O VIP

Re: ООП - объектно-ориентированое программирование

Postby Vitekkz88 on 14 Sep 2018, 23:00

Часть 3. LVOOP
LabVIEW OOP не поддерживает множественное наследование - это означает, что дети могут наследовать поля и методы только от одного родителя. Что делать, если требуется обратиться к методу класса, который не связан общим родителем?

В языке программирования, который поддерживает множественное наследование было бы проще в решении этой проблемы: наследуем и всё. Увы и ах, но наследование не бывает частичным, поэтому бонусом будут получены поля и методы от всякого. При разработке приложения это приведёт к приличному бардаку и путанице в проекте.
С другой стороны, есть языки, которые не поддерживают множественное наследование, например Java или C#. Однако, в этих языках программирования используются механизм интерфейсов. Интерфейс определяет контракт доступа к какому-то объекту. Важно понимать, что интерфейс не реализует сам метод, он предоставляет лишь доступ к нему. Одним и тем же интерфейсом могут пользоваться множество объектов, но реализация методов, которые содержит интерфейс, будет определена либо в каждом классе на свой манер, либо будет использоваться реализация метода по умолчанию из того класса, который предоставил этот интерфейс. Как раз то, что нужно, чтобы добраться до какого-то метода вне родственных классов.

В LabVIEW нет ни интерфейсов, ни множественного наследования и возникает вопрос: чего делать? я хочу конкретный метод(вероятно с конкретной реализацией) вооон того класса, который не от кого не зависит. Придётся выкручиваться :crazy: мы можем создать отдельный класс с реализацией нужного метода и наследоваться от него(ку-ку, дублирование кода), либо напрямую обратиться к объекту нужного класса для вызова метода(инкапсуляция, прости). И если в тексте это выражается в двух строчках быдло-кода(именно так), то в LabVIEW это будет выглядеть вот так:
Вызов_метода.png

Вот пожалуйста: у нас в методе класса Test создаётся объект класса ClassCalculator и вызывается его метод add. Поля мы берём из класса Test, вот пожалуйста – открыли доступ к set-еру класса ClassCalculator. Ну как?... Оно? Начал искать информацию про «интерфейсы в LabVIEW», нашел темы на Lava.org...Так там этой проблеме посвящены десятки тем с демонстрациями изворотливости на всякий вкус, посмеялся. В общем - это первая фишка, которая будет троллить LVOOP-шника. :D Второй фишкой будет отсутствие конструктора. Зачем в ООП-языках конструктор используют? Правильно, чтобы инициализировать поля некоторыми первоначальными значениями. Я наивно полагал, что делаю тоже самое, когда открываю кластер с полями объекта и устанавливаю в них какие-то значения. Ничего подобного: как были нули – так и остаются до тех пор, пока в явном виде не вызовешь сеттер для заполнения полей. Или нужно где-то галочку поставить? Еще одна фишка, которой пользуются эффективно и изящно – вложенные классы(nested class), но в LVOOP этого нет. Конечно же есть там какие-то варианты сделать нечто подобное – но это так же как и с интерфейсом. Мол, вложенные классы не часто используются( это я прочитал на одном из форумов :D ), поэтому и не нужны они, а если очень надо – вот вам кусочек как это можно обойти-заменить. А мне бы эта фишка пригодилась для того, чтобы не создавать отдельный дубль-класс с минимальными изменениями или объединить мелкие классы вместе внутри класса.
Так что в LabVIEW придётся более детально прорабатывать архитектуру проекта и сетовать на недостаточный уровень абстракции (хотя бы из-за отсутствия интерфейсов). Либо вообще изолировать классы друг от друга, используя их как кирпичи для построения финального приложения, в этом есть определенное здравое зерно. Возможно, в 2018 версии что-то изменилось, а то я в 2014-ой работаю.
Еще один пример напоследок как не надо делать: определили класс «прямоугольник» и класс «монитор».
Это два абсолютно не связанных логически между собой класса. Тем не менее, мы хотим наследовать реализованный метод из прямоугольника, чтобы нарисовать монитор прямоугольной формы. Берём и наследуем, да? На этом моменте мурашки пробегают и становится не по себе…особенно, если метод «рисовать» был унаследован от абстрактного класса «Геометрические фигуры». Поэтому, будьте внимательны и осторожны, дабы не ошибиться с проектированием домика. Не жалейте времени на качественный чертёж, тогда ваш объект прослужит верой и правдой долгие годы.
Трёх китов LVOOP вытягивает, уже хорошо. Дальше буду обозревать конкретные шаблоны(паттерны) проектирования: основные, порождающие, структурные, поведенческие. Вообще в википедии я насчитал около 60 паттернов, в хитовой книге Эрика и Элизабет Фримен приведены 12(может побольше, надо пролистать), в книге «Приёмы объектно-ориентированного проектирования» под авторством Гамм, Хелм, Джонсон, Влиссидес – 23. Посмотрю, сравню с тем, что уже приводится из готового для LabVIEW, сопоставлю с информацией из книг и что-нибудь разберу.
Многа буков, кто дочитал - тот молодец :super:
Полезная ссылка: https://forums.ni.com/t5/LabVIEW-Develo ... -p/3523820
Инженер - это открыто светящийся интеллект, свободный и не обидный юмор, это легкость и широта мысли...Это воспитанность, тонкость вкусов, хорошая речь, плавно согласованная и без сорных словечек...
-А. И. Солженицын
User avatar
Vitekkz88
expert
expert
 
Posts: 1033
Joined: 21 Jan 2014, 15:45
Location: Томск
Medals: 3
Activity (1) Silver (1) Автор (1)
LabVIEW Version: 12,13,14
Karma: 300
hardware I/O VIP

Re: ООП - объектно-ориентированое программирование

Postby Usss on 14 Sep 2018, 23:43

Лабвьюшные классы заставляют меня грустить. Все хорошо, пока нет необходимости их редактировать. Удалил пару лишних типов из базового контрола и вся прога сломалась. На блок диаграмме нет никаких ошибок, но в листе ошибок 77 забракованных vi. Открываю любую из них, в блок-диаграмме все чисто, но в листе ошибок написано "owning library has blocked execution of the VI". Иногда помогает пересохранить базовый тип данных, иногда нет. Где искать проблему не понятно.
Usss
junior
junior
 
Posts: 66
Joined: 19 Apr 2017, 23:06
LabVIEW Version: 2017
Karma: 10

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 15 Sep 2018, 08:50

Vitekkz88 wrote:LabVIEW OOP не поддерживает множественное наследование - это означает, что дети могут наследовать поля и методы только от одного родителя. Что делать, если требуется обратиться к методу класса, который не связан общим родителем?

Для этого можна использовать вот этот подход - https://forums.ni.com/t5/LabVIEW-Archit ... -p/3804592, мутацию классов. А по-сути - расширение функционала by composition (не знаю, как правильно перевести - при помощи композиции?).
Ну, и нужно не забывать, что начиная с LabVIEW 2017 SP1 (именно в сервис паке это уже работает), malleable VIs стали поддерживать адаптацию классов - т.е. это сейчас уже наиболее близко к интерфейсам. Можно создать класс-интерфейс, определить методы, и вызывать их из других классов, которые находятся в отдельной от него иерархии. Уже есть несколько постов по этому поводу, и примеры в самом LabVIEW (C:\Program Files (x86)\National Instruments\LabVIEW 2017\examples\Malleable VIs\Class Adaptation), посты: https://www.mediamongrels.com/malleable ... plication/, http://kosist.org/2018/07/malleable-vis ... -accessor/
P.S. Vitekkz88, забыл добавить: статьи по ООП дизайн-паттернам - это крутая мысть, будет классно и полезно, если Вы это сделаете! Есть идея тогда их выносить в отдельные посты, потому что боюсь если Вы будете писать их в этом посте, то они потеряются среди комментов и обсуждений которых тут, надеюсь, будет много.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

Re: ООП - объектно-ориентированое программирование

Postby Kosist on 15 Sep 2018, 08:55

Usss wrote:Лабвьюшные классы заставляют меня грустить. Все хорошо, пока нет необходимости их редактировать. Удалил пару лишних типов из базового контрола и вся прога сломалась. На блок диаграмме нет никаких ошибок, но в листе ошибок 77 забракованных vi. Открываю любую из них, в блок-диаграмме все чисто, но в листе ошибок написано "owning library has blocked execution of the VI". Иногда помогает пересохранить базовый тип данных, иногда нет. Где искать проблему не понятно.

В таком случае причина всегда одна - если хотя бы один из override методов "сломан", то и другие тоже будут неисполняемые; хотя их блок диаграмма и будет чиста. Обычно, LabVIEW помещает "сломанную" виайку на начало списка с ошибками, хотя это и не точно. Но пофиксив проблемный метод (или пересохранив базовый метод/целый проект) все будет работать.
Если удалять поля из базового класса, которые нигде не используются, и без них код будет работать - проблемы не бывает, проверено.
Мы делили апельсин - много наших полегло...
User avatar
Kosist
leader
leader
 
Posts: 857
Joined: 21 Feb 2011, 23:44
Medals: 2
Activity (1) Gold (1)
LabVIEW Version: 2013-2017
Karma: 245
CLAD I/O VIP students

PreviousNext

Return to Модели программирования

Who is online

Users browsing this forum: No registered users and 1 guest

cron