Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.77/13: Рейтинг темы: голосов - 13, средняя оценка - 4.77
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57

Разбираюсь с паттерном MVP

04.07.2023, 11:10. Показов 3166. Ответов 48
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Доброго времени, всем!


MVP

Правильно ли я уловил суть?


Model

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace Test.Core
{
    public class DataBase
    {
        // описал "шаблон" для делегата
        public delegate void DataBaseChangedDelegate(string message);
 
        // описал событие
        public event DataBaseChangedDelegate? DataBaseChanged;
 
        // описал базу данных
        public List<string[]> dataBase = new ();
 
        // метод добавления в базу данных
        public void Add(string[] str)
        {
            // добавление в коллекцию
            dataBase.Add(str);
            
            // передаю в событие сообщение
            if (DataBaseChanged is not null)
                DataBaseChanged ("Операция добавления в базу данных...");
        }
 
        // метод удаления из базы данных
        public void Remove(string[] str)
        {
            // удаление из коллекции
            dataBase.Remove(str);
            
            // передаю в событие сообщение
            if (DataBaseChanged is not null)
                DataBaseChanged ("Операция удаления из базы данных...");
        }
    }
}

View

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
namespace Test.Core
{
    public class Viewer
    {
        public void Message(string message) => Console.WriteLine(message);
    }
}

Presenter

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace Test.Core
{
    class Program
    {
        static void Main()
        {
            DataBase dataBase = new ();
 
            dataBase.DataBaseChanged += DataBaseChanged;
 
            dataBase.Add(new string[4]);
 
            dataBase.Remove(new string[4]);
        }
 
        static void DataBaseChanged(string str)
        {
            Viewer viewer= new ();
 
            viewer.Message(str);
 
            viewer.Message("База данных изменена!");
        }
    }
}


Что лучше использовать в моём случае для Model, View - Класс, Абстрактный класс, Интэрфейс или др.?

Спасибо!
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
04.07.2023, 11:10
Ответы с готовыми решениями:

Как рисовать на форме? Пользуюсь MVP паттерном
День добрый! У меня или навык гугленья сломался или что-то еще, поэтому прошу помощи реальных людей. Есть форма собственно часть...

Разобраться с паттерном Decorator
Добрый день. У кого есть время и терпенье, помогите разобрать с декоратором… (На примере С#) Хочу добавить к классу DirectoryInfo метод...

Регулярные выражения с переменным паттерном
Мне нужно отправлять личные сообщения людям из небольшого списка на одном форуме. За один раз можно отправлять сообщения пяти...

48
Эксперт JavaЭксперт по электроникеЭксперт .NET
 Аватар для wizard41
3385 / 2705 / 573
Регистрация: 04.09.2018
Сообщений: 8,519
Записей в блоге: 3
04.07.2023, 22:26
Цитата Сообщение от zaka4kin Посмотреть сообщение
Правильно ли я уловил суть?
Ну.. почти. Можно сказать что уловил. Однако в презентере не нужно каждый раз создавать новый View (в пределах одного приложения).

Добавлено через 2 минуты
Цитата Сообщение от zaka4kin Посмотреть сообщение
Класс, Абстрактный класс, Интэрфейс или др.?
Классы сойдут.
Абстрактный класс - в твоем случае не очень подходит.
Интерфейс можно применить, в качестве контракта для презентера... Но тоже не особо нужен в данном случае.
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
05.07.2023, 04:14  [ТС]
wizard41, понятно. изменил Presenter

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace Test.Core
{
    class Program
    {
        static readonly DataBase dataBase = new ();
 
        static readonly Viewer viewer= new ();
 
        static void Main()
        {
            dataBase.DataBaseChanged += DataBaseChanged;
 
            dataBase.Add(new string[4]);
 
            dataBase.Remove(new string[4]);
        }
 
        static void DataBaseChanged(string str)
        {
            viewer.Message(str);
 
            viewer.Message("База данных изменена!");
        }
    }
}

Спасибо!
0
2281 / 1597 / 400
Регистрация: 26.06.2017
Сообщений: 4,721
Записей в блоге: 1
05.07.2023, 17:57
Цитата Сообщение от zaka4kin Посмотреть сообщение
class DataBase
Нууу, скорее это не модель данных, это ближе к репозиторию. Вы же в базе собираетесь хранить вполне конкретные сущности, вот они-то и должны быть моделированы.
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
05.07.2023, 18:45  [ТС]
Цитата Сообщение от Uswer Посмотреть сообщение
Вы же в базе собираетесь хранить вполне конкретные сущности, вот они-то и должны быть моделированы.
что имеете в виду под "сущности... должны быть моделированы"?
в "базе" (List<>) будут лежать массивы строк.
не совсем понял куда клоните...
Спасибо!
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
05.07.2023, 21:08
Цитата Сообщение от Uswer Посмотреть сообщение
Нууу, скорее это не модель данных, это ближе к репозиторию. Вы же в базе собираетесь хранить вполне конкретные сущности, вот они-то и должны быть моделированы.
Не путайте устаревший термин "Модель" из старого ADO, с термином "Модель" из семейства паттернов MV*.

"Модель ADO" - служит для парсинга SQL запросов. Даже в ЕF подобные типы называются уже "Сущность".

"Модель в паттерне" - это СЛОЙ приложения в котором находится вся Бизнес (Доменная) Логика. Состоять Модель может из очень многих других объектов. Часто Модель разбивается ещё на подслои. Одним из таких подслоёв нижнего уровня может быть Репозиторий. В задачах где кроме "общения" с БД нет другой БЛ, по сути вся Модель состоит из одного только Репозитория.

В упрощённом общении часто под Моделью понимают не весь слой, а только "морду", интерфейс этого слоя через который с Моделью "общаются" её потребители.

Добавлено через 12 минут
Цитата Сообщение от zaka4kin Посмотреть сообщение
// описал "шаблон" для делегата
        public delegate void DataBaseChangedDelegate(string message);
Вложенные публичные типы "не есть хорошо". Вынесите объявление делегата в пространство имён.

Цитата Сообщение от zaka4kin Посмотреть сообщение
// описал базу данных
        public List<string[]> dataBase = new ();
Модель должна контролировать своё состояние и извещать об его изменении. А как в такой реализации она сможет узнать об изменении списка?

Нужно так:
C#
11
12
13
14
15
        // описал базу данных
        public IReadOnlyList<IReadOnlyList<string>> Datas;
        private readonly List<IReadOnlyList<string>> dataList = new ();
 
        public DataBase() => Datas = new ReadOnlyCollection(dataList);
В методах Add и Remove работаем с dataList.

Цитата Сообщение от zaka4kin Посмотреть сообщение
// метод удаления из базы данных
        public void Remove(string[] str)
        {
            // удаление из коллекции
            dataBase.Remove(str);
Это не будет работать без переопределения метода сравнения.

Попробуйте:
C#
1
2
3
List<string[]> datas = new ();
datas.Add(new string[] {"1", "2"});
datas.Remove(new string[] {"1", "2"});
1
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
06.07.2023, 06:18  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Модель должна контролировать своё состояние и извещать об его изменении. А как в такой реализации она сможет узнать об изменении списка?
я думал над этим. искал в List<> событие для отслеживания. поискал в глобе.
ничего лучше кроме ObservableCollection<T>.CollectionChange d не нашёл.
решил реализовать через событие, описанное в Model
C#
1
event DataBaseChangedDelegate? DataBaseChanged;
и подписаться на него в Presenter
C#
1
dataBase.DataBaseChanged += DataBaseChanged;
Спасибо! за совет. попробую по Вашему рецепту
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
06.07.2023, 11:09
Цитата Сообщение от zaka4kin Посмотреть сообщение
ничего лучше кроме ObservableCollection<T>.CollectionChange d не нашёл.
Есть два основных типа коллекций, позволяющих отслеживание изменений:
1) INotifyCollectionChanged - называют "Наблюдаемая коллекция";
2) IBindingList - "Привязываемый список".

ObservableCollection - это самая простая реализация INCC. Для WPF является основным типом мутабельной коллекции.
В Формах в основном используется BindingList. WPF тоже может его использовать, но ObservableCollection всё же проще.

Цитата Сообщение от zaka4kin Посмотреть сообщение
искал в List<> событие для отслеживания
List<> - это реализация IList<> "Простой индексированный список". Его реализует и T[], и многие другие коллекции. Но вот уведомления об изменении в нём не предусмотренно.

Цитата Сообщение от zaka4kin Посмотреть сообщение
решил реализовать через событие, описанное в Model
Чаще всего этот подход и будет самым верным.
Использование в свойствах Модели коллекции за состоянием которой нужно следить - это довольно громоздко и сложно.
Из реально используемых таких решений "на ум" приходит только Local.ToObservableCollection() из Контекста БД в EF.

Добавлено через 1 минуту
Цитата Сообщение от Элд Хасп Посмотреть сообщение
не будет работать без переопределения метода сравнения.
Поняли почему?
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
01.08.2023, 12:06  [ТС]
наконец-то, "дошли руки" продолжить обучение...
всё-таки бывает работа отнимает много времени и сил

Model

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
using System.ComponentModel;
 
namespace Test.Core
{
    public class DataBase
    {
        // Описал базу данных
        public BindingList<string[]> dataBase = new ();
    }
}


View

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
namespace Test.Core
{
    public class Viewer
    {
        // Описал метод вывода информации
        public void Message(string message) => Console.WriteLine(message);
    }
}


Presenter

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.ComponentModel;
 
namespace Test.Core
{
    class Program
    {
        // Объект с базой данных
        static readonly DataBase dataBase = new ();
 
        // Объект для вывода информации
        static readonly Viewer viewer= new ();
 
        static void Main()
        {
            // Подписываемся на событие изменения базы данных
            dataBase.dataBase.ListChanged += new ListChangedEventHandler(DataBaseChanged);
 
            // Добавление в базу данных
            dataBase.dataBase.Add(new string[1]);
 
            // Изменение элемента базы данных
            dataBase.dataBase[0] = new string[8];
 
            // Добавление в базу данных
            dataBase.dataBase.Add(new string[3]);
 
            // Удаление из базы данных
            dataBase.dataBase.RemoveAt(1);
        }
 
        // Обработка события ListChanged в базе данных
        static void DataBaseChanged(object? sender, ListChangedEventArgs e)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemAdded: viewer.Message("Произошло добавление элемента в базу данных!"); break;
 
                case ListChangedType.ItemDeleted: viewer.Message("Произошло удаление элемента из базы данных!"); break;
 
                default: viewer.Message("Произошло изменение элемента в базе данных!"); break;
            }
        }
    }
}


Соответствующий вывод

Кликните здесь для просмотра всего текста
Произошло добавление элемента в базу данных!
Произошло изменение элемента в базе данных!
Произошло добавление элемента в базу данных!
Произошло удаление элемента из базы данных!
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
01.08.2023, 12:21
Цитата Сообщение от zaka4kin Посмотреть сообщение
Presenter
Цитата Сообщение от zaka4kin Посмотреть сообщение
static void Main()
Как-то странно. Вообще-то в слое приложения должен быть View, а не Presenter...
Model - библиотека NET
Presenter - библиотека NET
View - приложение (Console, WF, WPF, Avalonia и т.д.)
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
01.08.2023, 18:02  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Как-то странно. Вообще-то в слое приложения должен быть View, а не Presenter...
Model - библиотека NET
Presenter - библиотека NET
View - приложение (Console, WF, WPF, Avalonia и т.д.)

что то типа этого?

Model

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
using System.ComponentModel;
 
namespace Test.Core
{
    public class Model
    {
        public BindingList<string[]> dataBase = new ();
    }
}


View

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.ComponentModel;
 
namespace Test.Core
{
    class Program
    {
        static void Main()
        {
            Presenter presenter = new ();
 
            presenter.model.dataBase.ListChanged += new ListChangedEventHandler(ChangeDataBase);
 
            presenter.model.dataBase.Add(new string[1]);
        }
 
        static void ChangeDataBase(object? sender, ListChangedEventArgs e)
        {
            Console.WriteLine("Добавление в базу данных");
        }
    }
}


Presenter

Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
using System.ComponentModel;
 
namespace Test.Core
{
    public class Presenter
    {
        public Model model = new ();
    }
}


если нет, то можно Ваш вариант, для понимания?
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
02.08.2023, 08:23
zaka4kin,
Паттерн MVP
Особенности реализации MVP для Windows Forms

Добавлено через 1 минуту
zaka4kin, И тут на форуме есть пример - Создание программы - ООП модель, MVP
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
02.08.2023, 08:50  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
на форуме есть

да вот тоже тему нашёл, а там написано:

Есть всякие паттерны MVVM, MVC, MVP но они не предназначены для консольного приложения, помогите справиться с архитектурой консольного приложения.
Находил в том числе и на этом форуме что MVVM и MVC не для консоли, и не занимайтесь ерундой.

Выше написали, что я верно разобрался. Кому верить?
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,227
02.08.2023, 09:15
Цитата Сообщение от zaka4kin Посмотреть сообщение
Кому верить?
Обычно в таком случае ищут дополнительные источники, выясняя правду.
Например,
1. MVP - Wiki
2. MVP - UI Design pattern - MSDN
3. Fowler - UI Architectures
0
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
02.08.2023, 09:27
zaka4kin, Вызывайте методы Presenter в консоли, кто вам не даёт...
Вы скорее всего не поняли сам смысл паттернов MV*. Они созданы для разделения логических частей приложения, которое упрощает разработку больших приложений в команде. Одни занимаются Model, вторые View, третие Presenter (ViewModel, Controller). И все эти группы людей практически не знают что делают другие, они видят только интерфейсы этих слоёв и работают с ними.
Распределение связей в этих паттернах такое
1. Model - сборка сама в себе, о существовании других вообще не догадывается.
2. Presenter (ViewModel, Controller) - имеет связь с Model, точнее с тем что выдано по интерфейсу в публичный доступ.
3. View - знает только о пункте 2. Пункт 1 для него не доступен на прямую.
4. Application - знает о всех слоях и задаёт взаимодействие между ними, запуская нужные сервисы для обслуживания потребностей.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
02.08.2023, 12:38
Andrey-MSK, не так. Вы описали за связи в паттерне MVVM. Это строго иерархический паттерн.
Например, в MVC View знает и о контролере, и о модели.

Добавлено через 2 минуты
В MVP Презентер дёргает View.
1
 Аватар для Andrey-MSK
3313 / 2200 / 387
Регистрация: 14.08.2018
Сообщений: 7,404
Записей в блоге: 4
02.08.2023, 12:48
Элд Хасп, Ну бывает

Добавлено через 4 минуты
Элд Хасп, Но всё равно - Presenter ну никак не может содержать метод Main(). Вот в этом посте
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
02.08.2023, 13:36
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Presenter ну никак не может содержать метод Main()
Класс Program - это точка запуска приложения и уровень приложения. Этот уровень не относится ни к одному из слоёв паттернов MV*. Здесь обычно происходит создание слоёв, их объектов, внедрение зависимостей.
Чтобы не было путаницы, лучше делать все слои MV* в отдельных от App проектах. К сожалению, настройки проектов для платформ GUI делают затруднительным создание View отдельно от App. Особенно для WPF. Поэтому в приложениях с GUI чаще всего App совмещён в одном проекте с View.
Совмещать App с Моделью и с VM-Controller-Presenter не имеет смысла ни при каких обстоятельствах.
0
1 / 1 / 0
Регистрация: 10.10.2021
Сообщений: 57
02.08.2023, 13:59  [ТС]
Элд Хасп, какой из примеров, был наиболее близок к MVP в контексте Console ?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
02.08.2023, 18:28
Цитата Сообщение от zaka4kin Посмотреть сообщение
какой из примеров, был наиболее близок к MVP в контексте Console ?
Ох, блин....
В общем смысле - любой паттерн подходит.
Консоль - это тип View. Во всех паттернах тип View может быть любым.

Другое дело - это вопрос о более частом применении паттерна для того или иного типа View.
MVC - исторически это первый паттерн. И, соответственно, примеров его применения для консоли гораздо больше.
Потом появились Формы и появилась потребность как-то адаптировать MVC для более удобного использования в приложениях с GUI. Появился MVP. MVVM появился уже как адаптация к WPF. Но даже его можно с успехом использовать в консоли.

Для консольного MVP я бы всё равно сделал View (консоль) в проекте с приложением. Там же сделал бы и рантайм сборку приложения - создание и связывание всех слоёв.
Но сам класс View должен быть отделён от Program.
Модель и Presenter - в отдельных проектах типа "Библиотека классов Standard".
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
02.08.2023, 18:28
Помогаю со студенческими работами здесь

Не могу разобраться с паттерном Стратегия
Здравствуйте. Изучал раньше Java, сейчас хотел попробывать себя в шарпе. Но столкнулся с одной задачкой, которая нормально...

Незадача с паттерном Цепочка обязанностей
В общем задание такое. Реализовать сказку(колобок). Естественно не через cout . И с обязательным применением цепочки обязанности. У меня...

Гостевая книга с паттерном MVC
Сделал простую гостевую книгу (Ввод имени, отзыва, сохранение и вывод данных из файла). ...

Гостевая книга с паттерном MVC
Сделал простую гостевую книгу (Ввод имени, отзыва, сохранение и вывод данных из файла). ...

Помогите с паттерном для RegExp
Есть такая строка: &lt;a class=&quot;title&quot; href=&quot;http://urlik.com/123456&quot;&gt;title 123456&lt;/a&gt; Нужно вытащить http://urlik.com/123456 и...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru