Образовательная площадка для программистов
32,26
рейтинг
13 октября 2015 в 15:08

Разработка → Как «Змейка» может познакомить с ООП: сложная концепция простыми словами

Здравствуйте! Вас приветствует редакция сайта GeekBrains.ru, сервиса онлайн-обучения программированию. Мы решили завести блог на Хабре! Уверены, что ещё успеем рассказать и обсудить много интересного из мира программирования, ИТ и онлайн-образования. Но начнём очень просто, без особых прелюдий, с обзора бесплатного курса по основам C# и ООП от одного из наших учеников. Слоган курса гласит «Сложная концепция простыми словами». Давайте же посмотрим, насколько это соответствует действительности.



Пара слов о слушателе: менеджер IT-проекта, знаком с процедурным программированием, web-разработкой, SQL. Более тесное знакомство с ООП понадобилось для глубокого внедрения в бизнес-процессы. Итак, слово нашему выпускнику.

«Есть такая программистская шутка: язык программирования легко выучить — в отличие от иностранных языков, в нём совсем немного слов. Действительно, выучить названия команд, функций, библиотек, типы данных и даже синтаксис не так сложно, тем более, что многие из них схожи в разных языках. Однако недаром высказывание названо шуткой — для того, чтобы работать с конкретным языком программирования, нужно знать принципы, основы парадигмы, стандарты языка. Объектно-ориентированное программирование, как и любая парадигма программирования, имеет набор принципов и правил, которые справедливы для всех языков и пригодятся в любом случае.

Для себя я выбрал ознакомительный курс для освоения принципов объектно-ориентированного программирования, который построен на создании рабочего проекта на языке С# — консольной игры «Змейка». Это та самая змейка, за которой несколько поколений убивали время на лекциях, играя на тетрисах и на чёрно-белых телефонах Nokia. Но должен сказать, что писать свою игрушку значительно приятнее, а, главное, полезнее. В ходе создания игры преподаватель раскрывает все принципы ООП, причём так, что каждый принцип воспринимается не как навязанная скучная теория, а как решение уже назревшего в голове вопроса: «Как упростить код и сделать его читабельнее?» Но всё по порядку.

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

  • Visual Studio — интегрированную среду разработки для ряда языков программирования. Именно в Visual Studio можно познакомиться с редактором исходного кода, дизайнером классов, отладчиком и консолью.

  • GitHub — веб-сервис для хостинга IT-проектов и их совместной разработки, базирующийся на системе управления версиями Git. Знакомство с ним помогает понять, как устроен проект, обратиться к открытому коду, скопировать его, если это необходимо, просмотреть предшествующие версии кода. Для общения среды разработки и репозитория кода используется приложение Smartgit.

Выбранный язык — C#, но, как я уже понял из своей практики, принципы ООП одни и те же и могут быть легко применены при изучении другого языка.

Изначально преподаватель делает акцент на том, что будет разрабатываться продукт. Выбор на змейку пал неслучайно — всем известна логика игры, её особенности и требования. А в разработке важно уже на раннем этапе иметь целостное представление о будущем проекте. Такое видение помогает разбить его на значимые этапы и избежать многих упущений.

Первые два урока просты и понятны любому человеку, даже совершенно не знакомому с программированием. Традиционно работа начинается со счастливого ‘Hello, world!’

namespace Snake
{
	class Program
	{
		static void Main( string[] args )
		{
			Console.WriteLine("Hello world");
			Console.ReadLine();
		}
	}
}



Я ещё раз повторил для себя, что такое функция, как она работает, как создаются переменные. Для написания кода используется процедурный подход — функции последовательно применяются, принимая на входе заданные параметры. Сразу становятся очевидными два недостатка создания всего кода внутри главной функции main: разрастание кода и объявление переменных прямо внутри этой функции.

namespace Snake
{
	class Program
	{
		static void Main( string[] args )
		{
			int x1 = 1;
			int y1 = 3;
			char sym1 = '*';

			Draw( x1, y1, sym1 );

			int x2 = 4;
			int y2 = 5;
			char sym2 = '#';

			Draw( x2, y2, sym2 );

			Console.ReadLine();
		}

		static void Draw(int x, int y, char sym)
		{
			Console.SetCursorPosition( x, y );
			Console.Write( sym );
		}
	}
}

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



На третьей лекции я познакомился с понятием класса, типа данных. Класс — одно из основных понятий ООП, поэтому его изучению уделяется пристальное внимание. Переменные начинают создаваться как экземпляры класса, то есть объекты (отсюда и название ООП).

Если слушатель начинающий, то он учится понимать язык кода и выражение Point p1 = new Point(); начинает восприниматься как «создаётся объект точка p1 как экземпляр класса Point, принимающий на входе координаты».

namespace Snake
{
	class Point
	{
		public int x;
		public int y;
		public char sym;

		public void Draw()
		{
			Console.SetCursorPosition( x, y );
			Console.Write( sym );			
		}
	}
}

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



На четвёртом занятии создаётся конструктор класса Point — явно написанный конструктор со специальным синтаксисом, который ничего не возвращает.

public Point(int _x, int _y, char _sym)
		{
			x = _x;
			y = _y;
			sym = _sym;
		}

Я заметил, как сокращается объём кода основной программы после создания конструктора. Конструктор принимает на вход координаты точки и символ её обозначения, но деталей реализации пользователь не видит — они скрыты внутри конструктора. Так я столкнулся с первым из трёх принципов ООП — инкапсуляцией. Инкапсуляция — это свойство системы, позволяющее объединять данные и методы, работающие с ними, в классе и скрыть все детали реализации от пользователя.

Пятая лекция погружает в вопрос организации памяти, работы программы со стэком и кучей. Объяснения дополнены наглядными схемами. После этого начинается работа с новым классом стандартной библиотеки C# List (список), в котором создаются функции добавления и удаления элемента, а также возникает цикл foreach.

List<int> numList = new List<int>();
			numList.Add( 0 );
			numList.Add( 1 );
			numList.Add( 2 );

			int x = numList[ 0 ];
			int y = numList[ 1 ];
			int z = numList[ 2 ];

			foreach(int i in numList)
			{
				Console.WriteLine( i );
			}

			numList.RemoveAt( 0 );

			List<Point> pList = new List<Point>();
			pList.Add( p1 );
			pList.Add( p2 );

 			Console.ReadLine();
 		}
 	}

Работая с циклом в отладчике, слушатель ещё более наглядно понимает структуру и последовательность работы программы.
Для целей реализации игры мы создали горизонтальные и вертикальные линии препятствий для змейки, которые представляют собой не что иное как список точек. Я старался успевать следовать за преподавателем, разбирая его код, создавая свой и тренируясь уже в своей программе.

namespace Snake
{
	class HorizontalLine
	{
		List<Point> pList;

		public HorizontalLine(int xLeft, int xRight, int y, char sym)
		{
			pList = new List<Point>();
			for(int x = xLeft; x <= xRight; x++)
			{
				Point p = new Point( x, y, sym );
				pList.Add( p );
			}
			
		}

		public void Drow()
		{
			foreach(Point p in pList)
			{
				p.Draw();
			}
		}
	}
}

Преподаватель отмечает, что и точка, и линии, а в дальнейшем и сама подвижная змейка по сути являются фигурами, поэтому должно существовать какое-то решение для оптимизации кода, которое позволит не копировать код, а переиспользовать его. Так я познакомился со вторым принципом ООП — наследованием. Наследование — это свойство системы, позволяющее описывать новый класс на основе уже существующего с частично или полностью замещающейся функциональностью. Таким образом, каждая линия, змейка и точка становятся частным случаем (наследуются) от класса Фигура: class HorizontalLine: Figure.

namespace Snake
{
	class Figure
	{
		protected List<Point> pList;

		public void Drow()
		{
			foreach ( Point p in pList )
			{
				p.Draw();
			}
		}
	}
}

Наследный класс обязательно содержит признаки родительского класса, но может иметь и свои собственные. Пример наследования дополнительно разбирается на хрестоматийном и понятном примере класса Работник, унаследованном от класса Человек, имеющим от родительского класса рост и возраст и свой признак — зарплату. Кстати, для целей самостоятельной тренировки понимания наследования в ООП лучше всего работать именно с проектированием карточки студента или сотрудника — это я понял сразу, сперва самостоятельно закрепляя свои знания, а потом уже работая с проектом.

И вот змейка должна научиться двигаться в поле и управляться стрелками с клавиатуры. Задача кажется сложной, но я помнил, что речь идёт всё ещё о консоли и поэтому реализация передвижения змейки должна быть максимально простой. Я уже знал, что змейка должна двигать в четырёх направлениях, что-то есть, расти или уменьшаться. И вот тут наступает пора абстрагирования — ситуации, при которой код пишется на основе выбранных значимых характеристик объекта, а незначительные — исключаются. Выбираем значимые признаки: змейка — это фигура из точек на карте, у неё есть стартовая позиция, координаты и она движется в одном из четырёх направлений. Класс Snake серьёзно изменяется и растёт.

{
 	class Snake : Figure
 	{
		public Snake( Point tail, int length, Direction direction )
		Direction direction;

		public Snake( Point tail, int length, Direction _direction )
 		{
			direction = _direction;
 			pList = new List<Point>();
			for(int i = 0; i < length; i++)
			for ( int i = 0; i < length; i++ )
 			{
 				Point p = new Point( tail );
 				p.Move( i, direction );
 				pList.Add( p );
 			}
 		}

		internal void Move()
		{
			Point tail = pList.First();			
			pList.Remove( tail );
			Point head = GetNextPoint();
			pList.Add( head );

			tail.Clear();
			head.Draw();
		}

		public Point GetNextPoint()
		{
			Point head = pList.Last();
			Point nextPoint = new Point( head );
			nextPoint.Move( 1, direction );
			return nextPoint;
		}
 	}
 }

Вообще, если продолжить говорить об абстрагировании, в ООП широко используется понятие абстрактного класса. Создаётся шаблонный класс, который реализует только известную и нужную разработчику на данный момент функциональность. Классы, производные от абстрактного, всю функциональность в дальнейшем смогут дополнить.
Но вернёмся к проекту. Появляется класс Direction (направление), в котором используется ещё один тип данных enum — перечисление, состоящее из набора именованных констант. В нашем случае это константы-направления: right, left, up, down. У класса Point появляется метод Move.

public void Move(int offset, Direction direction)
 		{
 			if(direction == Direction.RIGHT)
 			{
 				x = x + offset;
 			}
 			else if(direction == Direction.LEFT)
 			{
 				x = x - offset;
 			}
 			else if(direction == Direction.UP)
 			{
 				y = y + offset;
 			}
 			else if(direction == Direction.DOWN)
 			{
 				y = y - offset;
 			}
 		}

Таким образом, передвижение змейки реализовано как сдвиг позиции с перетиранием хвоста пробелом. Змейка управляется клавишами и управление реализовано следующим образом.

public void HandleKey(ConsoleKey key)
		{
			if ( key == ConsoleKey.LeftArrow )
				direction = Direction.LEFT;
			else if ( key == ConsoleKey.RightArrow )
				direction = Direction.RIGHT;
			else if ( key == ConsoleKey.DownArrow )
				direction = Direction.DOWN;
			else if ( key == ConsoleKey.UpArrow )
				direction = Direction.UP;
		}

Вновь я столкнулся с инкапсуляцией — управление змейкой уходит в класс Snake.
На следующем этапе змейка начинает есть и добыча создаётся в бесконечном цикле с помощью функции FoodCreator, проверяется совпадение координат головы змейки и точки, представляющей собой еду.

while (true)
			{
				if(snake.Eat( food ) )
				{
					food = foodCreator.CreateFood();
					food.Draw();
				}
				else
				{
					snake.Move();
				}					

				Thread.Sleep( 100 );

				if (Console.KeyAvailable)
				{
					ConsoleKeyInfo key = Console.ReadKey();
					snake.HandleKey( key.Key );
				}
			}

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

На последнем занятии змейка стала вполне самостоятельной, а я учился обрабатывать столкновения с препятствиями и собственным хвостом змейки. Код в лекции уже не создаётся, а берётся из репозитория и разбирается. Я не пошёл на поводу у соблазна скопировать чужой код, а некоторое время после прослушивания курса создавал свой, раз за разом обращаясь к лекциям. Вам я советую поступать точно так же — потому что для работы и понимания нужны знания. Надеюсь, я дал достаточно тизеров, чтобы вам захотелось зайти на GitHub и разобраться в реализации простой игры, основной код которой составляет всего 52 строки, а это значит, что все принципы ООП были успешно применены.

Подводя итоги, преподаватель ещё раз возвращается к главным парадигмам ООП и обращает внимание на модификаторы доступа public и private и рассказывает о ключевом слове virtual, благодаря которому метод может быть переопределён в наследном классе. Private — это закрытые данные и код внутри объекта, public — открытые. Закрытые данные и код доступны только из другой части этого же объекта, то есть извне к ним обратиться нельзя. Открытые данные и код доступны из любой части программы и нередко служат интерфейсом к закрытым частям объекта.
Если говорить о курсе в целом, то он мне помог — изменились и качество моей работы, и уровень общения с разработчиками. Советую попробовать всем, кому хоть немного интересно программирование, как минимум, это развивает мозг и учит думать системно. Я точно вернусь послушать другие курсы и пообщаться с профессионалами. Ну, а отважным новичкам желаю удачи!»

Вы заметили, насколько популярным стал видеоформат в контенте, рекламе, управлении? Общеизвестно, что видео задействует сразу и зрение, и слух, а значит, воспринимается лучше. К тому же, видеокурс можно остановить, перемотать, прослушать ещё несколько раз, задать вопросы в комментариях. Плюс ко всему, в GeekBrains преподают практики, для которых программирование — ежедневная работа и поэтому они всегда в курсе самых последних тенденций своей отрасли. Конечно, просмотр курса со стаканом чая перед монитором принесёт мало пользы, поэтому в заключение хотим дать несколько советов слушателям.

  • Слушайте курс с карандашом или ручкой — записывайте моменты, которые стоит переслушать или дополнительно посмотреть в Интернете или книге.

  • Не пропускайте непонятные моменты, старайтесь разобраться, обращайтесь к дополнительным источникам.

  • Не копируйте код, создавайте свой — только это позволит вам по-настоящему научиться работать с кодом.

  • Работайте с отладчиком — знание того, как пошагово работает программа, помогает создавать стройные и понятные функции.

  • Возвращайтесь к теории и курсу даже когда вы уже смогли создать своё приложение — это помогает упорядочить знания и найти новую точку для развития.

Мы уверены, что время на самообразование никогда не бывает потраченным зря. Оно обязательно окупится.
Автор: @GeekBrains
GeekBrains
рейтинг 32,26
Образовательная площадка для программистов

Комментарии (11)

  • +5
    На скриншотах преподаватель «Евгений Картавец» — а я с ним учился, крайне умный и талантливый парень! :)
  • +5
    как я уже понял из своей практики, принципы ООП одни и те же и могут быть легко применены при изучении другого языка.

    Не совсем так, потому что очень многие люди, изучающие ООП на примере С#/Java/C++ потом плохо понимают прототипное ООП в js (lua, etc).

    ИМХО, если уж изучать ООП, то надо охватывать разные его реализации, объяснять разницу между ними и т.д. Потому что принципы-то у них общие, а детали порой сильно различаются.
    Т.е. хорошо было бы рассказать про классовый подход (на примере Java/C#), про прототипный (на примере js), рассказать как ООП устроено в CLOS и в SmallTalk и т.д.
  • –2
    Нужно с таких змеек начинать =)
  • +1
    Скромное имхо — про ООП много пишут, но как я понял у каждого он свой в силу разных взглядов на мир.

    Как думаете, если дать одинаковый проект трем сеньорам C# без возможности коммуникации между собой, какой шанс того что они выполнят его одинаково? (Например, разные классы и связи между ними. Вопрос не в паттернах, а глубже).
    • +2
      Ну так а принципы одни и те же, поэтому правильно, что им уделили внимание и рассказали подробнее. В своё время в вузе мне понятие ООП никто дать по-людски не смог, только занудно три принципа повторяли и зубрить их заставляли (изучали на С++). Пришло озарение, когда друг-практик в ресторане под водку сказал задумчиво: «А что ООП? Всё есть объект, а потом из кучи объектов программу строишь, как из кубиков, что сложного?» Тогда и пришло понимание и экземпляров класса, и конструкторов, и инкапсуляции.
      • +3
        Принципы-то вроде одни, но нюансов в реализациях этих принципов столько, что порой кажется, что одни и те же вещи в разных языках совсем разные.
        Например, многие изучавшие C++/Java/C# считают, что модификаторы доступа это и есть инкапсуляция. Хотя на деле выходит, что можно достучаться и до private-поля. А наследование от класса — нарушение инкапсуляции. С другой стороны, в некоторых языках вообще нет понятия модификаторов доступа (js, python), но в них инкапсуляция достигается за счёт замыканий. При чём эта инкапсуляция «надёжнее».
        Или, например, в Java не всё есть объект, а в питоне даже класс является объектом, и можно с помощью метаклассов создавать классы (или изменять поведение классов) в рантайме.
        Ну и тд. Поэтому помимо каких-то базовых принципов очень неплохо смотреть и на конкретные трактовки ООП на примере языков, тогда придёт более глубокое понимание темы, и понимание того, где лучше применять ОО-подход, а где лучше выбрать другую парадигму.
  • 0
    Из текста осталось непонятно следующее. Вы просто продаёте доступ к видео? Никакого фидбека и двусторонней связи? Я правильно понял, в случае вопросов и непоняток «слушателю» предлагается перемотать видео и слушать снова и снова до полного просветления? Преподаватель смотрит код, комментирует, даёт советы по улучшению?

    Вы заметили, насколько популярным стал видеоформат в контенте, рекламе, управлении?
    Увы, заметили.

    Общеизвестно, что видео задействует сразу и зрение, и слух, а значит, воспринимается лучше.
    А мне известно, что видео требует задействовать (читайте: напрягать) как слух, так и зрение, плюс — диктует мне свой темп, в отличие от текста, когда я не напрягаю слух, не подстраиваюсь под оговорки, хмыканья и кхеканья говорящего, его голос, интонации; и почти не задействую зрение — эта часть мозга тем временем отлично визуализирует лично мои образы, удобные и понятные, которые мне лучше запомнятся; и читаю в удобном мне темпе, не сбиваясь на то, чтобы «перемотать» нужное словосочетание/предложение/абзац сколько угодно раз. Но да, буквы сложнее продавать, чем видео :)
    • +2
      Здравствуйте, Iceg.

      Да, формат видеокурса предполагает самостоятельное изучение материала по предоставляемым записям. Хотя, разумеется, Вы можете обратиться к преподавателю через форму на сайте, задать интересующий Вас вопрос и получить квалифицированный ответ.

      Для полноценной работы с домашними заданиями и проверкой их преподавателем у нас есть онлайн-курсы, среди которых также можно найти бесплатные.
    • 0
      Абсолютно согласен по поводу видео. Я тоже считаю, что текст гораздо понятнее, чем видео. Вы очень хорошо указали на недостатки подачи материала через видео. Если мне нужно посмотреть какой-то урок, то мне придётся смотреть его практически целиком, сложно пропустить часть и перейти к другому разделу. Например, если видеоурок идёт 20 минут, то такое же самое количество информации я смогу прочитать за 5-10 минут, а в некоторых случаях — вообще 2-3 минуты. Навыки быстрочтения вообще не работают в случае просмотра видео.
      Хотя в некоторых случаях, конечно же, видео может быть полезным. Как вариант, хорошим компромиссом может быть мини-видео (лучше gif) на несколько секунд, чтобы показать сложный момент, который сложно объяснить текстом, чаще всего — показать работу с мышкой.
      • +1
        Зачем все так усложнять. В видео текст чаще зачитывают, немного изменяя «своими» словами. Что бы угодить большинству потенциальных клиентов лучше предоставлять материал во всех популярных форматах сразу, тем более что текст уже имеется в большинстве случаев.
    • 0
      Жаль, не могу плюсик поставить. Согласна с каждым словом. Увы, общеизвестно, что видео воспринимается лучше (хотя я бы не стала ставить знак равенства между «воспринимается лучше» и «меньше напрягает голову»).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка