Форум Flasher.ru

Форум Flasher.ru (http://www.flasher.ru/forum/index.php)
-   ActionScript 3.0 (http://www.flasher.ru/forum/forumdisplay.php?f=83)
-   -   Выбрать шаблон проектирования для реализации XMLSocket (http://www.flasher.ru/forum/showthread.php?t=196189)

yasha005 20.03.2013 20:06

Выбрать шаблон проектирования для реализации XMLSocket
 
Посоветуйте, какой выбрать шаблон проектирования для реализации XMLSocket. Или может скомпоновать несколько шаблонов? Хотелось бы не просто тык пальцем в небо, а аргументированный выбор.

Вот список шаблонов, которое мне наиболее удобно применить:
Код:

1. Порождающе
        Фабричный метод
        Одиночка
2. Структурные
        Декоратор
        Адаптер
        Компоновщик
3. Поведенческие
        Наблюдатель
        Состояние

Суть в том чтобы различные компоненты занимались своими делами, при этом чтобы их можно было легко расширить и добавлять новые в случае надобности:
Код:

1. Отслеживание сокета на предмет ошибок
2. Отправка данных
3. Отслеживание инициализации приёма данных
4. Компановка структуры отправляемых данных
5. Парсинг структуры принимаемых данных
6. ...может что-то ещё


GBee 20.03.2013 22:33

Когда программист задает такие вопросы, ему обычно советуют использовать принцип ПКБ. Ну или более вежливый ППК.

dark256 20.03.2013 22:41

Я срочно бросаю подготовку к свадьбе, жарить рыбу, выпиливать лобзиком и бегу переписывать своих сокетов....

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

Короче, коллега. Не забивайте себе голову шаблонами. Просто сядьте и пишите. Написанное вами автоматически подстроится к вашей задаче, после чего вы можете ее красиво обозвать по шаблону...
И вовсе не исключено, что это не будет 4й вариант...

in4core 20.03.2013 23:05

Цитата:

Короче, коллега. Не забивайте себе голову шаблонами. Просто сядьте и пишите. Написанное вами автоматически подстроится к вашей задаче
Lol - всегда так делаю, а потом говорю - зацените какие я паттерны/шаблоны тут заюзал! Ух блин! А сам , даже и не думал об этом когда писал )) Обычно все давно уже на автомате делается

Котяра 20.03.2013 23:16

А зачем вам, собственно, его реализовывать, если он уже реализован?

expl 20.03.2013 23:31

2 yasha005:
Это не сарказм, а совет: "Bыкиньте подальше книгу про шаблоны и включите мозг".
Они, конечно хорошие (кроме синглтона) и полезные (кроме визитора), но:
- Ситуации в коде ваша книжка с 10-ком шаблонов не покроет, покроет только часть
- Шаблон используют когда есть проблема, и, надо же, решение из книжки подходит.
- Сначала пишут максимально просто - не напишете просто, а будете громоздить шаблон - ошибётесь с его выбором. И шаблон, опять же, пихать надо, если простое решение имеет проблемы.
Архитектурная абстракция нагороженная без "разведки" простым кодом чаще всего оказывается неверной - создаёт больше проблем чем решает.

Короче:
Вот какие у Вас проблемы с сокетом?
Чем он вам мешает, что сложно добавлять, где копипаста появляется?

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

yasha005 21.03.2013 00:47

Цитата:

Сообщение от expl (Сообщение 1126083)
2 yasha005:
Это не сарказм, а совет: "Bыкиньте подальше книгу про шаблоны и включите мозг".
Они, конечно хорошие (кроме синглтона) и полезные (кроме визитора), но:
- Ситуации в коде ваша книжка с 10-ком шаблонов не покроет, покроет только часть
- Шаблон используют когда есть проблема, и, надо же, решение из книжки подходит.
- Сначала пишут максимально просто - не напишете просто, а будете громоздить шаблон - ошибётесь с его выбором. И шаблон, опять же, пихать надо, если простое решение имеет проблемы.
Архитектурная абстракция нагороженная без "разведки" простым кодом чаще всего оказывается неверной - создаёт больше проблем чем решает.

Короче:
Вот какие у Вас проблемы с сокетом?
Чем он вам мешает, что сложно добавлять, где копипаста появляется?

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

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

Котяра 21.03.2013 01:02

Хорошая книжка на эту тему - Рефакторинг Мартина Фаулера. Там нет привычных Gof шаблонов, но есть шаблоны рефакторинга. Т.е. пишем пишем - видим копипасты - выделяем метод.. Кодим дальше - видим кучу одинаковых методов - выделяем фабрику и т.п..
Т.е. выбор шаблона по-ходу разработки, а не изначально.

expl 21.03.2013 01:07

Джошуа Кериевски «Рефакторинг с использованием шаблонов» туда же
Ктати, много где написано про визитор, но мало где про накапливающий параметр - а ведь последний чаще используется. В этой книге оно описано.

Степанов, кончено, на лекции в Яндекс, говорил, что нечего баловаться и читать всяких Кириевски. Но то Степанов и Яндекс. А нам, простым смертным эта книжка - самое то.

Цитата:

Если же я пишу код на основе шаблона, то мне достаточно всего лишь держать в голове структуру самого шаблона
Не знаю, на своей практике только уже не раз встречавшиеся проблемы решал сразу шаблоном - иначе нельзя угадать.

Давайте по-порядку:
Он вообще сейчас работает? Что с ним не так конкретно?
Цитата:

1. Отслеживание сокета на предмет ошибок
2. Отправка данных
3. Отслеживание инициализации приёма данных
4. Компановка структуры отправляемых данных
5. Парсинг структуры принимаемых данных
Берётся всё и пихается в один класс.
Если тяжело скроллить или хотите покрыть юнит-тестами - парсинг и компоновку вынесите в 2 отдельных класса.

Я вот даже не знаю что посоветовать.
Код в студию!
Цитата:

6. ...может что-то ещё
Не надо решать ещё не появившиеся проблемы

yasha005 21.03.2013 01:13

Цитата:

Сообщение от Котяра (Сообщение 1126096)
Хорошая книжка на эту тему - Рефакторинг Мартина Фаулера. Там нет привычных Gof шаблонов, но есть шаблоны рефакторинга. Т.е. пишем пишем - видим копипасты - выделяем метод.. Кодим дальше видим кучу одинаковых методов - выделяем фабрику и т.п..
Т.е. выбор шаблона по-ходу, а не изначально

Спасибо, вот это самый ценный совет. Я люблю подобного рода книжечки почитать.

Добавлено через 1 минуту
Цитата:

Сообщение от expl (Сообщение 1126099)
Джошуа Кериевски «Рефакторинг с использованием шаблонов» туда же
Ктати, много где написано про визитор, но мало где про накапливающий параметр - а ведь последний чаще используется. В этой книге оно описано.

Степанов, кончено, на лекции в Яндекс, говорил, что нечего баловаться и читать всяких Кириевски. Но то Степанов и Яндекс. А нам, простым смертным эта книжка - самое то.

Спасибо, тоже не менее ценный для меня совет

Добавлено через 1 час 17 минут
Цитата:

Сообщение от expl (Сообщение 1126099)
...Код в студию!

Не надо решать ещё не появившиеся проблемы

Так ведь в любом случае придётся расширять, так что задел под непоявившиеся проблемы должен быть


Что касается кода, я только черновые набросочки сделал, но это не окончательный вариант

Структура обмена данных:
Код AS3:

package server_exchange.server_socket_exchange
{
 
        public class SocketExchange
        {
                // Реализация шаблона одиночки
                private static var _instance:SocketExchange;
                // Реализация шаблона наблюдателя
                private var mySub:ISubject;
                // Парсит объекты из XML в Object
                private var parsingXMLtoObject:ParsingXMLtoObject
 
                public function SocketExchange(pvt:PrivateClass)
                {
                        // Объект рассылки
                        this.mySub = new ConcreteSubject();
                        // Парсит объекты из XML в Object
                        this.parsingXMLtoObject = new ParsingXMLtoObject();
                }
 
                public static function get Instance():SocketExchange
                {
                        if (SocketExchange._instance == null)
                        {
                                SocketExchange._instance = new SocketExchange(new PrivateClass());
                        }
                        return SocketExchange._instance;
                }
 
                // Подписываемся на событие
                public function Subscribe(obserName:String,obserFunc:Function):void
                {
                        this.mySub.AddObserver(obserName,obserFunc);
                }
 
                // Отписываемся от события
                public function UnSubscribe(obserName:String):void
                {
                        this.mySub.RemoveObserver(obserName);
                }
 
                // Обновляет данные в ConcreteSubject.SetParam при получении новых данных с сервера
                public function UpdateData():void
                {
                        this.parsingXMLtoObject.InputData = this.mySub.SetParam;
                }
 
 
 
        } // конец класса
 
} // конец пакета
 
 
class PrivateClass
{
        public function PrivateClass()
        {
        }
}

Интерфейс рассылки
Код AS3:

package server_exchange.server_socket_exchange
{
        //Subject Interface
        // Интерфейс рассылки
        internal interface ISubject
        {
                // Подписка наблюдателя
                function AddObserver(obserName:String,obserFunc:Function):void;
                // Отписка наблюдателя
                function RemoveObserver(obserName:String):void;
                // Уведомление наблюдателя
                function SetParam(obserName:String, objectParam:Object):void;
        }
}

Конкретная рассылка:
Код AS3:

package server_exchange.server_socket_exchange
{
        // Конкретная рассылка
        internal class ConcreteSubject implements ISubject
        {
                // Свойство хранения состояния как часть данного класса
                private var objectParam:Object;
                // Массив для хранения подписчиков-наблюдателей
                [ArrayElementType('Function')]
                private var observers:Array;
                // Имя объекта, кому конкретно предназначена рассылка
                private var observerName:String;
 
                function ConcreteSubject()
                {
                        //trace('*|*Concrete Subject*|*');
                        this.observers = new Array();
                }
 
                // Реализует процесс подписки
                public function AddObserver(obserName:String,obserFunc:Function):void
                {
                        this.observers[obserName] = obserFunc;
                }
 
                // Реализует процесс удаления (прекращения) подписки
                public function RemoveObserver(obserName:String):void
                {
                        this.observers[obserName] = null;
                        delete this.observers[obserName];
                }
 
                // Процесс уведомлений
                private function NotifyObserver():void
                {
                        for(var notify:String in this.observers)
                        {
                                // Применяется метод update из интерфейса Observer
                                if (notify == this.observerName)
                                {
                                        this.observers[notify](this.objectParam);
                                        this.observerName = null;
                                        this.objectParam = null;
                                        break;
                                }
                        }
                }
 
                // Устанавливает состояние
                public function SetParam(obserName:String,objectParam:Object):void
                {
                        this.observerName = obserName;
                        this.objectParam = objectParam;
                        this.NotifyObserver();
                }
        } // Конец класса
} // Конец пакета


Окружение XML-экземпляра (или попросту, константы составляющие структуру XML содержимого)
Код AS3:

package server_exchange.server_socket_exchange
{
 
        internal class NameParsingXML
        {
 
                /*//////////////////////////////////////////////////
 
                        Окружение xml-экземпляра модуля: CollocutorsList
 
                //////////////////////////////////////////////////*/

 
                // Статус ника в объекте cntxt[0].@statMember
                internal static const _addMember:String = 'addMember';
                internal static const _updateMember:String = 'updateMember';
                internal static const _removeMember:String = 'removeMember';
                // Назначение: чат, список в чате, приватное сообщение  cntxt[0].@nznchn
                internal static const _listAll:String = 'listAll' // для списка собеседников в чате: весь список
                internal static const _listSingle:String = 'listSingle' // для списка собеседников в чате: один посетитель
                //internal static const _mess:String = 'mess' // сообщение в чат
                //internal static const _private:String = 'private' // личное сообщение
                // Доставить в чат конкретной модели: указывается ник модели, либо в общий чат: указывается collective_chat
                // cntxt[0].@to
                internal static const _collocutors_list:String = 'collocutors_list'; // для отправки в общий список
                // Параметры ника в объекте: cntxt[0].paramNik
                internal static const _sex:String = 'sex';
                internal static const _tip:String = 'tip';
                internal static const _nikname:String  = 'nikname';
                // Параметры камеры
                internal static const _camStat = 'camStat';
                // Статус мембера: добавление, обновление или удаление cntxt[0].@statMember
                internal static const _statMember:String = 'statMember';
 
                /*///////////////////////////////////////////////////
 
                        Окружение xml-экземпляра модуля: Chat
 
                ///////////////////////////////////////////////////*/

 
                internal static const _collective_chat:String = 'collective_chat'; // для отправки в общий чат
                internal static const _listMessage:String = 'listMessage'; // список сообщений в чате
                internal static const _singleMessage:String = 'singleMessage'; // единичное сообщение в чат
                internal static const _addMessage:String = 'addMessage'; // статус добавления единичного сообщения в чат
 
 
 
                public function NameParsingXML()
                {
                        // constructor code
                }
 
        }
 
}


Вот здесь я планирую заменить URLLoarer (собственную библиотечку, которую я ранее уже описывал вот в этом сообщении: http://www.flasher.ru/forum/showpost...54&postcount=5) на событие сокета DataEvent
Код AS3:

package server_exchange.server_socket_exchange
{
        import configure.ConfigURL;
        import configure.NameObjects;
        import languages.Lang;
        import lib_loader.ILibLoader;
        import lib_loader.LibDataLoader;
        import modules.Module;
        import modules.ModuleXMLConstName;
        import modules.NameModules;
        import modules.NameModulesSub;
 
        // Парсит объекты из XML в Object
        // Обновляет данные при получении новых данных с сервера
        internal class ParsingXMLtoObject
        {
                private var inputData:Function;
                private var libDataLoader:ILibLoader;
                private var module:Module
                private var lastError:String;
                private var lang:Lang;
 
                public function ParsingXMLtoObject()
                {
                        // constructor code
                        this.lang = Lang.Instance;
                        this.libDataLoader = new LibDataLoader();
                        this.module = Module.Instance;
                }
 
                // Обновляет данные в принимаемом методе при получении новых данных из ConcreteSubject.SetParam
                internal function set InputData(inputData:Function):void
                {
                        this.inputData = inputData;
                        this.StartDownload();
                }
 
                private function StartDownload():void
                {
                        this.libDataLoader.SetParam =
                        {
                                urlLoc: ConfigURL.cfgUrlXmlDataExchange ,
                                functionComplitLoc: this.StartParsing ,
                                functionErrorLoc: this.ErrorLoadData ,
                                startLoc:true
                        }
                }
 
                // При ошибке выдавать сообщение об ошибке
                private function ErrorLoadData(e:String):void
                {
                        this.lastError = e;
                        var TextError:Function = this.TextError;
                        var obj:Object =
                        {
                                textLoc:TextError() ,
                                funcLngUpdLoc:TextError
                        }
                        this.module.InitModule = NameModules._MessageInfo + NameObjects._separator_ + NameModulesSub._error;
                        this.module.SetParam(NameModules._MessageInfo  + NameObjects._separator_ + NameModulesSub._error , obj);
 
                }
 
                private function TextError():String
                {
                        var string:String;
 
                        string = this.lang.LngVar(ModuleXMLConstName._Could_not_load_data_from_the_server);
                        string += '\n\n' + this.lastError + '\n\n';
                        return string;
                }
 
                /*////////////////////////////////////////////////////////////
 
                        Парсинг принимаемых данных
 
                ////////////////////////////////////////////////////////////*/

 
                // Если данные успешно загрузились, парсим их и передаём в качестве объекта принятому ранее методу
                // желательно применить шаблон декоратор к объекту this.xmlDataObject
                private function StartParsing(e:*):void
                {
                        // Принимаемый xml-листинг
                        var xmlData:XML = XML(e);
                        // Принимаемый обработанный объект xml-листинга
                        var takeObject:Object; // takeObject = { xmlDataObject , observerName };
 
                        // Тест
                        //trace(this.xmlData.name().localName);
                        //trace(this.xmlData.localName());
                        //for (var name:String in this.xmlData.name())
                        //{
                                //if (typeof (myObject[name]) == "string")
                                //{
                                        //trace(name+' = '+this.xmlData.name()[name]); //data
                                //}
                        //}
                        // конец теста
 
 
                        ///////////////////////////////////////////////////////////////
                        // Устанавливаем к какому модулю относится данный XML-листинг//
                        switch(xmlData.name().localName)
                        {
                                case NameModules._CollocutorsList:
                                        takeObject = ParsingCollocutorsList.CollocutorsList(xmlData);
                                        break;
                                case NameModules._Chat:
                                        takeObject = ParsingChat.Chat(xmlData);
                        }
                        // конец соотношения установки модуля к данному XML-листингу//
                        //////////////////////////////////////////////////////////////
 
 
                        /*///////////////////////////////////////////////////////////
                                Передача параметров объекту, принятых с сервера
                        ///////////////////////////////////////////////////////////*/

                        if ((takeObject is Object) && (this.inputData is Function)) //&& (this.xmlDataObject is Object))
                                this.inputData(takeObject.observerName, takeObject.xmlDataObject);
 
                } // Конец метода StartParsing
 
 
 
        } // Конец класса
} // Конец пакета


Парсинг сообщений чата:
Код AS3:

package server_exchange.server_socket_exchange
{
        // Парсинг XML листа списка собеседников: модуль CollocutorsList
        import configure.NameObjects;
 
        internal class ParsingChat
        {
                public function ParsingChat()
                {
 
                }
 
                internal static function Chat(xmlData:XML):Object
                {
                        var xmlDataObject:Object;
                        var observerName:String;
                        var returnObject:Object;
                        switch(String(xmlData.cntxt[0].@nznchn))
                        {
                                case NameParsingXML._listMessage:
                                        var addMessageList:Vector.<Object> = new Vector.<Object>();
                                        var i:int = xmlData.cntxt[0].AddMessage.length();
                                        while (--i > -1)
                                        {
                                                        addMessageList.push(
                                                        {
                                                                sex: String(xmlData.cntxt[0].AddMessage[i].@sex),
                                                                nikname: String(xmlData.cntxt[0].AddMessage[i].@nikname),
                                                                tip: String(xmlData.cntxt[0].AddMessage[i].@tip),
                                                                flag:String(xmlData.cntxt[0].AddMessage[i].@flag),
                                                                camStat: String(xmlData.cntxt[0].AddMessage[i].@camStat),
                                                                date: new Date(String(xmlData.cntxt[0].AddMessage[i].@date)),
                                                                message: String(xmlData.cntxt[0].AddMessage[i])
                                                        });
                                        }
                                        if (addMessageList.length)
                                        {
                                                xmlDataObject = {addMessageList:addMessageList}
                                                observerName = String(xmlData.name().localName) + NameObjects._separator_ + String(xmlData.cntxt[0].@to);
                                        }
                                        break;
                                case NameParsingXML._singleMessage:
                                        switch(String(xmlData.cntxt[0].@statMessage))
                                        {
                                                case NameParsingXML._addMessage:
                                                        xmlDataObject =
                                                        {
                                                                addMessage:true ,
                                                                sex: String(xmlData.cntxt[0].@sex),
                                                                nikname: String(xmlData.cntxt[0].@nikname),
                                                                tip: String(xmlData.cntxt[0].@tip),
                                                                flag:String(xmlData.cntxt[0].@flag),
                                                                camStat:String(xmlData.cntxt[0].@camStat),
                                                                date: new Date(String(xmlData.cntxt[0].@date)),
                                                                message: String(xmlData.cntxt[0])
                                                        };
                                                        break;
 
                                        }
                                        observerName = String(xmlData.name().localName) + NameObjects._separator_ + String(xmlData.cntxt[0].@to);
                                        break;
                        }
 
                        if (xmlDataObject is Object)
                        {
                                returnObject = { xmlDataObject:xmlDataObject , observerName:observerName };
                                return returnObject;
                        }
                        else
                        {
                                return null;
                        }
                }
 
 
        } // конец класса
} // конец пакета

Парсинг списка участников чата:
Код AS3:

package server_exchange.server_socket_exchange
{
        // Парсинг XML листа списка собеседников: модуль CollocutorsList
        import configure.NameObjects;
 
        internal class ParsingCollocutorsList
        {
                public function ParsingCollocutorsList()
                {
 
                }
 
                internal static function CollocutorsList(xmlData:XML):Object
                {
                        var xmlDataObject:Object;
                        var observerName:String;
                        var returnObject:Object;
                        switch(String(xmlData.cntxt[0].@nznchn))
                        {
                                case NameParsingXML._listAll:
                                        var addMemberList:Vector.<Object> = new Vector.<Object>();
                                        var i:int = xmlData.cntxt[0].AddMember.length();
                                        while (--i > -1)
                                        {
                                                        addMemberList.push(
                                                        {
                                                                sex: String(xmlData.cntxt[0].AddMember[i].@sex),
                                                                nikname: String(xmlData.cntxt[0].AddMember[i].@nikname),
                                                                tip: String(xmlData.cntxt[0].AddMember[i].@tip),
                                                                flag:String(xmlData.cntxt[0].AddMember[i].@flag),
                                                                camStat: String(xmlData.cntxt[0].AddMember[i].@camStat)
                                                        });
                                        }
                                        if (addMemberList.length)
                                        {
                                                xmlDataObject = {addMemberList:addMemberList}
                                                observerName = String(xmlData.name().localName) + NameObjects._separator_ + String(xmlData.cntxt[0].@to);
                                        }
                                        break;
                                case NameParsingXML._listSingle:
                                        switch(String(xmlData.cntxt[0].@statMember))
                                        {
                                                case NameParsingXML._addMember:
                                                        xmlDataObject =
                                                        {
                                                                addMember:true ,
                                                                sex: String(xmlData.cntxt[0].paramNik[0].@sex),
                                                                nikname: String(xmlData.cntxt[0].paramNik[0].@nikname),
                                                                tip: String(xmlData.cntxt[0].paramNik[0].@tip),
                                                                flag:String(xmlData.cntxt[0].@flag)
                                                        };
                                                        break;
                                                case NameParsingXML._updateMember:
                                                        xmlDataObject =
                                                        {
                                                                updateMember:true ,
                                                                nikname: String(xmlData.cntxt[0].paramNik[0].@nikname),
                                                                camStat:String(xmlData.cntxt[0].paramNik[0].@camStat)
                                                        };
                                                        break;
                                                case NameParsingXML._removeMember:
                                                        xmlDataObject =
                                                        {
                                                                removeMember:true ,
                                                                nikname: String(xmlData.cntxt[0].paramNik[0].@nikname)
                                                        };
                                                        break;
                                        }
                                        observerName = String(xmlData.name().localName) + NameObjects._separator_ + String(xmlData.cntxt[0].@to);
                                        break;
                        }
 
                        if (xmlDataObject is Object)
                        {
                                returnObject = { xmlDataObject:xmlDataObject , observerName:observerName };
                                return returnObject;
                        }
                        else
                        {
                                return null;
                        }
                }
 
 
        } // конец класса
} // конец пакета



Часовой пояс GMT +4, время: 14:33.

Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2026, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.