Top.Mail.Ru

C# и концепция программирования ООП

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

  • Императивное программирование. Это классический метод создания программ, выполняемых на машине Тьюринга. Машиной Тьюринга, по факту, является любой современный компьютер, за исключением, разве что квантовых компьютеров, но там совсем другая концепция. Если говорить простыми словами, то императивное программирование – это просто создание программы из набора инструкций. Классический пример – машинные коды или язык ассемблер. Первые языки высокого уровня, такие как Basic и Pascal, а также язык C не далеко ушли от данной концепции.
  • Декларативное программирование. Если в случае императивного программирования мы описываем компьютеру, как он должен выполнить задачу (описываем алгоритм на определенном языке), то в декларативном программирование мы говорим компьютеру что надо сделать, а вот как он это будет делать, уже его проблемы. Идея хорошая, по сути, это первый шаг к созданию Искусственного Интеллекта. Только вот проблема, что компьютер – это «тупая машина», которая ничего не может делать самостоятельно, без детально описанного алгоритма. Поэтому дальше таких чисто декларативных языков, как Prolog (язык логического программирования), HTML и SQL дальше дело не зашло. Кроме того, такие языки не полны по Тьюрингу, и поэтому область их применения весьма ограничена.
  • Структурное программирование. Это по сути, то же самое императивное программирование, но с использованием определенных правил. В частности, нельзя использовать оператор GOTO. Вся программа состоит из набора подпрограмм, циклов и ветвлений. По сути, это первая попытка структурировать код и первый шаг к ООП.
  • Функциональное программирование. Здесь процесс вычислений функций в их математическом понимании. Суть в том, чтобы программу описывать исключительно языком математики. Что характерно, при функциональном программировании очень часто используется рекурсия и прочая «бяка». Пример подобных языков lisp и F#. Так как для написания программы при таком подходе приходиться буквально «выворачивать мозги наизнанку» данная концепция не нашла широкого распространения.
  • Объектно-ориентированное программирование (ООП). Основная идея – это представление программы в виде совокупности объектов, где каждый объект является экземпляром класса, а классы образуют иерархию наследования. Сам объект по своей сути представляет собой набор данных плюс программный код, который их обрабатывает. Чем такой подход лучше других? Тем, что программа естественным образом структурируется по образу и подобия нашего мышления. Человеческий мозг привык работать с абстракциями (объектами). При ООП тот же подход. Мы декомпозируем задачу на отдельные сущности и затем кодируем их на ОПП языке программирования.

А теперь попробуем написать на языке C# что-нибудь, например, компьютерную игру. Ну например, игру «Мафия». Чтобы в нее могли играть как реальные игроки, так и виртуальные (компы). И еще чтобы ее потом не трудно было переделать в игру «Вторжение рептилоидов». В этом на как раз и поможет ООП.

И так, прежде чем приступить, я расскажу о правилах игры «Вторжение рептилоидов». Как и «Мафия», это пошаговая игра. В ней есть следующие персонажи:

  • Рептилоиды. Их цель чипировать всех землян.
  • Земляне. Цель – не дать себя чипировать и уничтожить рептилоидов и ZOG.
  • ZOG. Цель
    украсть технологию рептилоидов и всех чипировать (и тех и других).
  • Чипированный. Цель – не спалиться что он чипирован и быть лояльным тому, кто его чипировал. То есть, чипированный не может указать на того, кто его чипировал. При попытке нарушить это правило чип взрывается.

Дополнительно можно добавить такого персонажа как хакер, который может взламывать чипы. И да, надо учесть, что как в «Мафии», так и в игре «Вторжение рептилоидов» пользователи могут захотеть, чтобы программист добавил в игру новые персонажи или изменил их геймплей.

Подробно правила описывать не буду, они будут придуманы, когда дойдем до «Вторжения рептилоидов». А сейчас будет программировать «Мафию». Прежде чем откроем Visual Studio, сядем и немного подумаем. Во-первых, какие у нас тут есть абстракции? Давайте перечислим их:

  • Персонаж. Персонажи бывают разные, они могут находиться в разных состояниях (живой, мертвый, чипированный). Они могут совершать какие-то действия. Возможно, у каждого персонажа будет свой набор допустимых состояний и допустимых действий. Причем, персонажи могут управляться как человеком, так и программно (иметь некое подобие ИИ).
  • Интерфейс человек – персонаж. Должен позволять принимать команды управление персонажем с клавиатуры, от мышки и так далее. Следует учесть, что игра может быть сетевой. Сейчас мы не будет реализовывать сетевой вариант игры, но на будущее надо предусмотреть такую возможность. Кроме того, необходимо обеспечить соблюдение правил игры.
  • Контроллер правил. Если правила игры надо соблюдать, то логично выделить отдельный класс, который бы позволяя делать игрокам только то, что разрешают правила. Это не должен делать ни персонаж, ни интерфейс человек – персонаж, ни модуль ИИ, который будет управлять виртуальным игроком.
  • Блок управление персонажем. Должен уметь соблюдать правила игры и принимать решение о тех или иных действиях. Правила он будет получать, конечно, от контроллера правил, но должен держать их в «уме» чтобы принимать решения.
  • Ядро игры. Осуществляет взаимодействие между объектами, выполняет шаги игры.

Далее, как абстракции взаимодействуют? Это можно описать следующими шагами:

  1. Ядро игры выполняет шаг. Например «город спит, просыпается мафия». Выполнение шага заключается в том, чтобы изменить состояния персонажей. Тут сразу обращаем внимание, что кроме состояний «жив», «мертв», «чипирован», есть еще состояние «спит». Каждое состояние накалывает на персонаж определенные ограничения в плане того, какие действия он может совершать.
  2. Ядро передает управления персонажам (по очереди каждому). Персонаж, получив управление, выполняет некие действия, дозволенные его состоянием (или ничего не делает, если состояние не позволяет). В результате действий персонажей другие персонажи могут менять состояние. При том, стоит заметить, что изменение статуса должно произойти после завершения шага, так как может быть такое, что игроки убивает, например, друг друга. Но если они выполняют действия по очереди, то такое «не прокатит», так как убьет только тот, кто выполнит действие поровый. Второму не даст совершить действие его состояние «мертвый».
  3. Ядро посылает информацию для отображения (или позволяет пользовательскому интерфейсу отобразить новые состояния).
  4. Ядро проверяет, не наступил ли конец цикла игры. Если нет, то переход к шагу 1.

Теперь можно потихоньку начать программировать. Итак, заходим в Visual Studio и создаем новый проект:

Для простоты создадим пока игру на Windows Forms:

Не забудьте также нормально назвать проект и решение. Вот как у нас будет выглядеть Visual Studio после создания:

Важно отделить интерфейс от игровой логики, так как кто его знает, может, потом мы заходим сделать WEB-приложение и нам потребуется переписывать интерфейс Windows Forms на ASP. NET. Если мы все смешает в одну кучу, то это будет сделать ой как не просто. А если вовремя отделим интерфейс от логики, то нам надо будет просто переписать отображение для пользователя, и все.

Чтобы отделить интерфейс от логики, создадим еще один проект (который и будет отвечать за игровую логику. Для этого щелкнем правой кнопкой мыши по корню решения и выбрать «Добавить» -> «Создать проект»:

Проект назовем GameCore, а тип «Библиотека классов» (Class Library):

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

При этом откроется окно выбора ссылок, надо выбрать проект GameCore:

Создадим классы, пока это будут заготовки. Начнем с главного класса – Core. Добавляем (или переименовываем Class1.cs):

Начнем реализовывать наш игровой алгоритм. И сразу встает необходимость создать еще один класс создать абстрактный класс персонажа (назовем его AbstractUnit). Почему класс абстрактный? Потому, что мы еще не знаем, как будут реализованы действия каждого конкретного персонажа, но у них есть нечто общее:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameCore
{
    /// <summary>
    /// Абстрактный класс персонажа
    /// </summary>
    public abstract class AbstractUnit
    {

    }
}


Для Core также делаем заготовку:


    /// <summary>
    /// Ядро игры
    /// </summary>
    public class Core
    {
        /// <summary>
        /// Список персонажей
        /// </summary>
        protected List<AbstractUnit> units;

        public Core()
        {
            units = new List<AbstractUnit>();
        }

        /// <summary>
        /// Шаг игры 
        /// </summary>
        /// <returns>true - можно делать следующий шаг, false – нет больше шагов</returns>
        public bool step()
        {

        }
     }

 

Обратите внимание, step() у нас подчеркнутый красной волнистой линией:

Это значит, что тут ошибка. В данном случае ошибка в том, что функция должна возвратить значение, а она, в силу того, что не дописана, ничего пока не возвращает. Мы это реализуем позже, а пока я объясню, что мы с вами сделали. И так, запись:

        /// <summary>
        /// Список персонажей
        /// </summary>
        protected List<AbstractUnit> units;


Тут мы объявляем список персонажей. Для этого используем встроенный класс List – типизированный список. В него можно поместить только объекты указанного типа либо их потомки. Поле units имеет модификатор protected, это означает, что он будет доступен только изнутри самого класса или его потомков. Извне данное поле будет недоступно.

Далее, вот это конструктор:

        public Core()
        {
            units = new List<AbstractUnit>();
        }


Этот код будет вызываться при каждом создании нового экземпляра класса Core. Тут мы создаем экземпляр списка для хранения персонажей.

Ну а вот это

        /// <summary>
        /// Шаг игры 
        /// </summary>
        /// <returns>true - можно делать следующий шаг, false – нет больше шагов</returns>
        public bool step()
        {

        }

 

у нас пока заглушка, которую мы реализуем на следующем уроке.

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar