Руководство по объектно ориентированной логике by GreenMarine (с особыми ссылками на объектно ориентированные места в UnrealScript) Вы можете всегда найти самую свежую версию этого документа на http://www.planetquake.com/osp/tuts/GM-OOtutorial.html Введение Говоря на каналах #UnrealED и #UnrealScript (оба активные EF-Net каналы на IRC), мое внимание привлекло то, что многие из интересующихся UnrealScript не очень знакомы с объектно ориентированной логикой. В попытке сделать мой кусочек хорошего дела, я пишу это руководство как короткий экскурс в ООП. Надеюсь, что время которое вы потратили на чтение, даст вам достаточно сильное понимание ООП чтобы свободно работать с UnrealScript. Основываясь на моем опыте, я узнал, приближаясь к идее мода или проблеме с ООП дизайном в моей голове, что ответ найдется намного проще. Я надеюсь, что это руководство будет полезным для вас. Я продолжу изменять его до тех пор пока буду видеть необходимость. Если вы имеете какую-либо информацию или исправления, пишите мне. Я буду очень счастлив включить такие элементы с похвалой автора. Это первый в серии полезных руководств, которые я планирую создать. Держите ваши глаза открытыми. Способ которым думают программисты Программисты являются странным племенем. Они часто забывают есть. Они часто забывают спать. Они даже склонны к пренебрежению к своим подругам/друзьям (если они достаточно счастливы чтобы иметь их) все время пытаясь сделать довольно глупую машину более умной, чем любая разумная персона считалась бы стоящей. Программисты, вы видите, неразумные люди. Это отсутствие благоразумия является частично благодаря вышеупомянутому недостатку еды, сна, секса и также частично благодаря способу мышления программиста. Программисты любят решать проблемы. В частности, программисты любят решать проблемы изменяя способ которым они пытаются разрешить проблему. ООП, которое является центром этого руководства, является одним из таких путей решения проблем. Чтобы понять ОО методы решения проблем, мы сначала должны изменить путь решения этой проблемы. Разберите это Предположим вы, как программист, имеете проблему. Вы хотите написать программу которая будет строить машину. Звучит сильно, не правда ли? Да. Постройка машины является несомненно непростой задачей, но половина трудности в обдумывании решения является путь которым мы обдумывам проблему. Если вы говорите "Я хочу написать программу которая построит машину", вы возможно будете умственно потрясены необъятностью задачи! Машина делается из тысячи частей! Как вы можете написать программу которая сделает все эти части? Тысячи частей? Ага! Мы уже начали разбирать это. Если вместо "Я хочу написать программу которая построит машину" вы скажете "Я хочу написать программукоторая построит множество частей машины и затем соберет эти части." Все еще устрашающая задача, но определенно более организованная. Это есть что мы называем "разберите это" или "нисходящее программирование". Разбивая основную проблему на последовательные меньшие кусочки, вы встретитесь с многими маленькими, простыми задачами вместо одной большой, трудной задачей. Если вы хотели бы оставить аналогию с машиной и перейти к Unreal аналогии, каждый может сказать "Я хочу написать бота который играет в Unreal". Большая проблема. Однако, если вы разобъете бота на на последовательные меньшие кусочки, вы окончите проект нахождения пути, проект использования оружия и так далее. Это первый шаг в ОО приближении к программированию. Становление организованным Сейчас мы имеем группу маленьких задач которые составляют нашу большую задачу, мы должны стать организованными. Если вы должны писать вашу программу по сбору машины в классическом C стиле, вы будете иметь множество функций и большой беспорядок. Вы могли бы очистить это присвоив определенным частям машины свои файлы, но это будет еще более диким. Как мы организуем маленькие части большой задачи? Решением является изменить способ которым мы думаем о частях проблемы. Давайте посмотрим на аналогию с машинами. Мы хотим построить и мы собираемся сделать это имея одну часть нашей программы, которая строит части машины и другая собирает эти части в работающую машину, так? Что точно является частью машины? Это может быть смесь металла, пластика, меньших частей и она имеет специальные функции... различные части машины одного и того же типа могут быть слегка различны. В основном, мы могли бы сказать, что часть машины является общим типом объекта. Он не является специфичным объектом в особенности, просто копия благодаря которой мы могли бы изготовить дейтствительную вещь, Давайте назовем это класс. Класс В ОО логике, класс является описанием какая вещь может быть если она была создана. Класс является общим шаблоном. Он описывает абстрактные свойства (часть машины имеет цвет), но обычно не определяет природу этх свойств (часть машины красная). Класс также определяет поведение объекта. Набор специальных функций которыми владеет класс определяют точный способ как объект, который вы создаете, работает в вашей программе. Говоря программистским языком, абстрактные свойства называются "переменными" и функции задающие поведение "методами". Процесс взятия класса и создание объекта из него называется instantiation. Очень важно представлять себе, что класс не является объектом, просто шаблон благодаря которому объект может быть создан. Когда вы создаете объект из класса, вы создаете модель из шаблона. Механика классов В UnrealScript, мы описываем класса посредством объявления класса: class MyClass expands MyParentClass; Определитель класса говорит UnrealScript, что вы начинаете определять новый класс. MyClass является именем этого класса (оно может быть любым как вы захотите, предпочтительно что-нибудь выразительное, как CarPart). После того, как вы сделали объявление класса в UnrealScript, вы готовы описывать переменные класса. Это сделано перечислением группы переменных отвечающих за свойства: var int color; //Индексный номер цвета части var byte manufacturer; //Значение ссылки на производителя Вы можете узнать специфические типы данных, которые поддерживает UnrealScript после того как вы прочитаете руководство UnrealScript Language Reference by Tim Sweeney. Может быть отделение ваших переменных от остального кода комментарием как показано ниже: //////////////////////////////////////////// // Переменные для MyClass Это просто дело вкуса, но это не повредит. Это является обычно хорошим делом делая ваш код более читабельным, особенно если вы хотите разделить его с другими или вернуться к нему позже. Определение методов для вашего нового класса похоже на определение переменных, просто сделайте список функций: function doAThing() { // Выполнение некоторого кода UnrealScript } function doAnotherThing() { // Другая функция которая делает что-то другое } Однажды снова, вы можете захотеть разделить область методов вашего класса от остального кода используя комментарии. Также весьма советуется ставить имена ваших методов после их функций. Например, функция которая чистит ваш сокс может называться cleanSocks(). В UnrealScript, мы называем создание объекта spawning (рождение). По существу, вы используете функцию Spawn() чтобы создать новый объект из шаблона класса: var actor MyObject; // Переменная содержащая объект MyObject=Spawn(MyClass); // Рождение объекта Краткое обсуждение методов Объекты являются независимыми скоплениями интерактивных данных находящихся в памяти. Ключевое слово независимые. Каждый объект делает свою собственную работу. Если вы имеете класс называющийся MyBot и вы родили два объекта из этого класса называющиеся BotA и BotB, эти два объекта не знают друг о друге. Это подводит нас к следующей концепции ООП: объекты изменяют сами себя. Когда объект родился, он обычно помещается в хэш таблицу в памяти, ему дается ключ поиска и он забывается. Система использует ключ поиска чтобы найти выбранный объект в хэш таблице, если вы хотите сделать что-то с ним, но для всех намерений и целей объект является просто как бы недоступным. Недоступность в этом смысле, не как нормальная переменная, вы просто не можете изменить объект. Вы не можете, например, открыть объект и изменить собержимое переменных. (Это один из сторон, по которой объекты отличаются от структур в C++). Вместо этого, вам придется сказать объекту чтобы он изменил сам себя. Это делается посредством методов. Методы класса определяют путь в котором объекты будут вести себя когда родятся. Если вы хотите изменить переменную объекта, вам придется написать метод в классе, который позволит такому способу иметь место. Наш класс CarPart может иметь метод который выглядит следующим образом: function setColor(int newColor) { color=newColor; } В этом случае, когда будет вызываться метод указанный выше, объект возьмет целочисленный аргумент и присвоит переменной color это значение. Объект изменяет сам себя. Синтаксис для вызова метода объекта в UnrealScript выглядит следующим образом: MyObject.setColor(15); // сказать объекту вызвать его метод setColor() Методы, переменные и защита объектов Помните когда я говорил что объект изменяет сам себя? Но это не совсем правильно. Я хотел бы чтобы вы поверили, что вы могли бы начать думать об объектах как независимых субстанциях в вашем программном окружении. Возможно, по правде говоря, изменять переменные объекта напрямую: MyObject.color=15; Заметьте разницу. В случае метода, вы говорите объекту изменить себя и в случае присваивания мы изменяем природу объекта напрямую. Это представляет один из моментов "Как думают программисты". Вы возможно спрашиваете себя, если я могу просто изменить объект напрямую, почему я должен метод чтобы сделать это? Хорошо, подумаем об этом. Что, если есть очень специальные ограничения на переменную color. Например, вы можете захотеть чтобы переменная color принимала только значения от 1 до 10. Все что не входит в этот диапазон может заставить вашу программу действавать непредсказуемо. В этом случае, это не будет хорошей идеей позволить пользователю объекта изменять значение переменной напрямую. Правильно? Даже, хотя вы можете знать, что от 1 до 10 являются только корректными значениями, кто-нибудь еще, кто будет использовать ваш код, может не знать этого. Решением является сделать переменную частной, так чтобы только класс мог сам изменять ее: var private int color; // объявление частной переменной Часный указатель показывает, что эта переменная является доступной только объектам этого класса. Объект может менять значение переменной color изнутри его собственного метода, но внешним присваиванием, как здесь: MyObject.color=15; Это позволит вам контролировать ввод в ваши объекты и более тщательно определять их поведение. Сейчас вы могли бы сделать чтобы ваш объект возвращал код ошибки или выполнял соответствующие действие, если ему было передано неправильное значение посредством метода. Семейства классов Не устали еще? Сейчас самое время схватить Dr.Pepper. Мы сейчас получаем основные элементы объектов! Как вы можете видеть из рассмотренного выше (если вы оригинальное создание и вы должны им быть если вы читаете это), одни объекты имеют множество возможностей. Объекты делают просто разбивание проблемы на удобные части. Однако, это может быть все еще трудным, если вам приходится управлять множеством объектов. Это дает нам основы ООП. Взаимосвязь между объектами. Хороший способ проиллюстрировать взаимосвязь между объектами является наша аналогия с машиной. Класс CarPart несомненно не уходит очень далеко в описании что есть CarPart. Установленно, что мы знаем об объекте до сих пор, мы вероятно даже не использовали CarPart, нам придется писать классы как SteeringWheel (рулевое колесо) что является более специфичным и полезным. Фактически, это не совсем case. В нашем понятии, CarPart уже имеет созданную взаимосвязь со SteeringWheel. SteeringWheel является разновидностью части машины. Правильно? Поэтому, что если класс CarPart описывался очеь общими методами и переменными, что использовали все части машины, и другой класс называемый SteeringWheel расширяет эту функциональность? Говоря программистским языком, мы называем это родительско-дочернее взаимоотношение. CarPart является родительским классом (или супер классом) SteeringWheel. В UnrealScript, мы описываем дочерний класс как здесь: class SteeringWheel expands CarPart package(MyPackage); Видете указатель expands? Он показывает что класс, который мы объявляем (SteeringWheel) является дочерним классом CarPart. Как только Unreal видит это, он создает специальную связь между двумя классами. Основа 1: Наследование Что, точно, делает для нас это родительско-дочернее взаимоотношение, как решатель проблемы? Оно упрощает решение, вот что! Создавая такие взаимоотношения между двумя классами, дочерний класс немедленно наследует свойства и методы родительского класса. Даже без написания вами и строки кода, SteeringWheel содержит всю функциональность CarPart. Если CarPart имеет определенный метод setColor() (как обсуждалось выше) SteeringWheel имеет тот же самый метод. Наследование применяется к переменным, методам и состояниям. Это позволяет нам создавать, что программисты называют "иерархия объектов" (или семейство классов). Например, как дерево классов: Object | expanded by Actor | expanded by CarPart | expanded by SteeringWheel Object и Actor являются специальными классами в Unreal, которые описываются в руководстве Tim Sweeney. Полное дерево классов для Unreal является расползающейся сетью отношений как вы можете несомненно представить. В нашем примере, мы имеем основное "есть" (is-a) отношение: A SteeringWheel is a CarPart. A CarPart is an Actor. An Actor is an Object. Каждый следующий слой дерева семейства наследует и расширяет функциональность и детали предыдущего слоя. Это позволяет нам просто описывать сложные объекты в терминах его составляющих объектов. Важно представлять что взаимосвязь не перестановочна. SteeringWheel является всегда CarPart, но CarPart не всегда SteeringWheel. Передвигаясь вврех по дереву вы получаете больше общего и передвигаясь вниз вы получаете более специализированное. Поняли? Хорошо! Но подождите секунду... если вы строите машину и машина собирается из частей, где класс Car? Это дает нам важное различие во взаимоотношениях: is-a (есть) против has-a(имеет). Ясно, CarPart не разновидность Car. Поэтому, взаимосвязь CarPart exnads Car будет неправильной. Лучше, вы могли бы иметь стуктуру дерева которая может вы глядить как показано: Object | Actor / \ Car CarPart | SteeringWheel Car является классом происходящий из Actor, но он не имеет прямой связи с CarPart (вы можете сказать, что они потомки от одних родителей). Вместо этого, внутреннее определение класса Car может включать переменные которые есть CarParts. В этом случае мы имеем связь has-a (имеет). Car имеет SteeringWheel, но SteeringWheel не Car. Если вы разрабатываете иерархию классов как показано выше и вы запутались с взаимосвязями объектов, иногда очнь полезно выразить это в стиле is-a или has-a. Как вы можете видеть, иерархия взаимоотношений позволяет нам делать очень интересные вещи. Если мы хотим, например, сделать более широкое определение Car, мы могли бы добавить Vehicle: Object | Actor / \ Part Vehicle / | | \ CarPart AirPart Car Airplane Очень круто? Не только это является отличнымспособом организовать и визуализировать данные, но выгода наследования значит, что мы сохраняем время, которое было бы потрачено на копирование и переписывание кода! Основа 2: Полиморфизм Поли что? Это еще что сумашедшие программисты говорят. (Если вы понимали все выше до сих пор, вы являетесь более программистом, чем вы думаете). Полиморфизм является другой основой ООП. В наследовании, дочерний класс получает переменные, методыи состояния родительского класса... но что если мы захотим изменить эти наследованные элементы? В нашем примере про машину, мы можем захотеть иметь класс Pedal, который определяет мметод называемый pushPedal(). Когда вызывается pushPedal(), метод выполняет действия по умолчанию (может он активирует тормоз). Если мы расширяем класс Pedal новым классом называемом AcceleratorPedal, метод pushPedal() внезапно становится неправильным. Акселератор безусловно не должен включать тормоза! (Или вы будете иметь много судебных процессов, когда выпустите вашу программу, поверьте мне). В этой ситуации, нам придется замещать поведение, которое мы наследовали от класса Pedal, чем-то новым. Это сделано посредством процесса называемого "Полиморфизм" или "Подавление функции". Вы будет сталкиваться с этим все время когда вы будете писать на UnrealScript. Чтобы усвоить, объяснение от Tim Sweeney: "Подавление функции ссылается на написание новой версии функции в подклассе. Например, вы пигите скрипт для нового вида монстров называемого Demon. Класс Demon, который вы только-что создали, расширяет класс Pawn. Сейчас, когда pawn видит игрока первый раз, вызывается функция SeePlayer() pawn, таким образом что pawn начинает атаковать игрока. Это замечательная концепция, но вы хотите реализовать функцию SeePlayer() по другому в вашем новом классе Demon." Чтобы сделать это, просто переопределите функцию в дочернем классе. Когда будет создан объект, но будет иметь новое поведение, не как в родительском классе. Если вы не хотите чтобы кто-нибудь переопределил функцию, которую вы добавили в класс, поставьте указатель final в объявление функции: function final SeePlayer() Это предотвращает скритп от переопределения функции во вторичных классах и может быть очень полезно в поддержки целостности поведения в коде который вы пишите. Tim Sweeney замечает, что это также оказывает влияние на увеличение скорости внутри Unreal Приведем это все вместе Таким образом, вы знаете основы ООП и имеете хорошую идею как объекты относятся друг к другу. Что вы делает дальше? Лучший совет программируйте. Погружайтесь в код и не поднимайтесь на воздух, если обещания еды, сна или секса виднеются недалеко. Ищите зоны. Или... вы можете всегда прочитать руководство Tim Sweeney по UnrealScript. Это даст больше лучших подробностей о синтаксисе ООП в Unreal. В дополнение, я предлагаю вам искать другие ресурсы в сети касающиеся ООП. Когда я разрабатывал это руководство, я попытался упомянуть несколько хороших ссылок, которые я приведу ниже. Есть много тонких моментов в ООП которые только могут быть выучены. Некоторые на самом деле не поддерживаются Unreal, некоторые да. Некоторые являются только путем решения проблемы. И что приходит мне в этой заключительной точке. ООП является насколько путем решения проблем, настолько и путем программирования. Когда вы идете в школу или едете на работу, попытайтесь представить взаимоотношения между вещами которые вы видите (дерево имеет листья, роза это цветок). Это будет сильно усилит ваше понимание ООП. Создайте комплекс взаимоотношений в вашем уме и затем найдите путь представления их в код. Тем кто действительно понимает это и наслаждается этим, программирование является умственной, материальной и духовной задачей. Это может звучать странно, но программирование касается основных способов, которыми мы думаем и решаем проблемы. Если вы может думать в ООП, затем ваш ум неограничется, когда он подойдет к решению проблем в ООП. Чем больше вы используете его, тем более вы будете приходить к осознания что есть истина. Тем не менее, ООП не может решить все. Просто как любой другой путь решения, ООП игнорирует определенные элементы проблемыдля того чтобы усилить аналогию с естественной системой. Наиболее правдоподобно, тем не менее, вы не будете обращены к этим моментам когда вы будете писать на UnrealScript, пока вы не напишите чертовски превосходительный мод.