вторник, 31 августа 2010 г.

Создание Аттрибута для хранения информации о разработчике

В этой статье мы создадим Аттрибут для хранения информации о девелопере измнившем код подследним. Обязательными являются его (фамилия)ник типа string и дата модификации кода. Опциональными - комментарий типа string .

Коллекция Attribute связывает предопределенную системную информацию или заданную пользователем информацию с целевым элементом. Целевым элементом может быть: сборка, класс, конструктор, делегат, перечисление, событие, поле, интерфейс, метод, переносимый исполняемый (PE) файл, модуль, параметр, свойство, возвращаемое значение, структура или другой атрибут.

Информация, предоставляемая атрибутом, называется также метаданными. Метаданные можно анализировать в приложении во время выполнения, для того чтобы управлять тем, как это приложение осуществляет обработку данных, или до времени выполнения внешними средствами для управления обработкой и выполнением самого приложения. Например, на платформе .NET Framework предопределены и используются типы атрибутов для управления поведением времени выполнения, и некоторые языки программирования используют типы атрибутов для представления языковых функций, не поддерживаемых непосредственно общей системой типов .NET Framework.

Все типы атрибутов прямо или косвенно наследуются от класса Attribute. Атрибуты могут быть применены к любому целевому элементу; несколько экземпляров атрибута могут быть применены к одному и тому же целевому элементу; атрибуты могут наследоваться элементом, являющимся производным от целевого элемента. Используйте класс AttributeTargets для задания целевого элемента, к которому применяется атрибут.

Класс Attribute предоставляет удобные методы для извлечения и проверки настраиваемых атрибутов.

Давай те обратимся к реализации.

using System;

using System.Collections.Generic;

using System.Text;

namespace Task_2

{

public sealed class DeveloperAttribute : System.Attribute

{

private string lastName;

public string LastName

{

get { return lastName; }

set { lastName = value; }

}

private DateTime date;

public DateTime Date

{

get { return date; }

set { date = value; }

}

private string comment;

public string Comment

{

get { return comment; }

set { comment = value; }

}

public DeveloperAttribute(string lastName, string date)

{

this.lastName = lastName;

this.date = Convert.ToDateTime(date);

}

public DeveloperAttribute(string lastName, string date, string comment)

{

this.lastName = lastName;

this.date = Convert.ToDateTime(date);

this.comment = comment;

}

public override string ToString()

{

return lastName + date.ToString();

}

}

}

А теперь используем наш описанный выше класс атрибута в коде. Например, в классе из написанной мной ранне статьи “Модификация приложения JobTerrarium ”.

using System;

using System.Collections.Generic;

using System.Text;

using Task_2Winforms;

namespace Task_2

{

[Developer("Vasya Ivanov", "23.11.07")]

class BigBoss : Boss,IManagable

{

public BigBoss(string name):base(name,1500)

{

}

public BigBoss(string name, decimal salary):base(name,salary)

{

}

public override string SayToWorker()

{

return "Сколько можно бездельничать?";

}

public override string SayToBoss()

{

return "Как работа?";

}

public override string SayToBigBoss()

{

return "Здравствуй.";

}

public override string ToString()

{

return "BigBoss";

}

void RaiseSalary(decimal amount)

{

TextBuffer.WriteLine("Нельзя управлять мной!!!");

}

}

}

Таким образом, мы видим, что использование класса System.Attribute очень полезно и удобно.

Реализация шаблона MVC на примере игры типа Packman – Часть 5

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

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

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

Опишем контроллер управляющий объектами нашей игры.

Namespace PackMan

{

/// <summary>

/// Контроллер пакмена

/// </summary>

class PackManController

{

/// <summary>

/// Обработчик нажатия клавиши

/// </summary>

/// <param name=”sender”>Объект,сгенеривший событие</param>

/// <param name=”e”>Клавиша,которая нажата</param>

public void OnKeyPress(object sender, KeyEventArgs e)

{

if (sender is PackMan)

{

switch (e.KeyData)

{

case Keys.Up:

{

((PackMan)sender).IdentifyDirection((int)Direction.Up);

break;

}

case Keys.Down:

{

((PackMan)sender).IdentifyDirection((int)Direction.Down);

break;

}

case Keys.Left:

{

((PackMan)sender).IdentifyDirection((int)Direction.Left);

break;

}

case Keys.Right:

{

((PackMan)sender).IdentifyDirection((int)Direction.Right);

break;

}

default:

break;

}

}

}

}

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

public partial class GameForm : Form

{

public static Random rand = new Random();

private readonly ViewGame viewGame;

/// <summary>

/// Количество танков

/// </summary>

public static int countTank = 10;

/// <summary>

/// Количество стен

/// </summary>

public static int countWall = 0;

/// <summary>

/// Количество яблок

/// </summary>

public static int countApples = 4;

/// <summary>

/// Размеры поля по X

/// </summary>

public static int MAXX;

/// <summary>

/// Размеры поля по Y

/// </summary>

public static int MAXY;

/// <summary>

/// Массив расстановки стен

/// </summary>

public static string[] map = {

"********************",

"*..................*",

"*..................*",

"*...*************..*",

"*.........*........*",

"*.........*........*",

"*...*..*.....*..*..*",

"*...*..*.....*..*..*",

"*...*..*******..*..*",

"*...*...........*..*",

"*...*...........*..*",

"*...*.....*.....*..*",

"*...*.....*.....*..*",

"*...****..*..****..*",

"*......*..*........*",

"*......*..*........*",

"*.........*..*.....*",

"*.........*..*.....*",

"*......*..*..*.....*",

"********************"

};

public GameForm()

{

InitializeComponent();

MAXX = p_Map.Width;

MAXY = p_Map.Height;

//Расстановка стен по массиву map

for (int i = 0; i < map.Length; i++)

for (int j = 0; j < map[0].Length; j++)

countWall += map[i][j] == '*' ? 1 : 0;

p_Map.Controls.Add(l_points);

Game game = new Game();

viewGame = new ViewGame(p_Map,l_points);

viewGame.SubscribeKeyPress();

viewGame.Model = game;

viewGame.Model.Start();

}

private void button1_Click(object sender, EventArgs e)

{

viewGame.Model.Dispose();

}

private void GameForm_KeyDown(object sender, KeyEventArgs e)

{

viewGame.OnKeyPress(e.KeyCode);

}

private void GameForm_FormClosing(object sender, FormClosingEventArgs e)

{

viewGame.Model.Dispose();

}

}

Теперь мы можем посмотреть, что у нас получилось и насладиться нашей игрой.

clip_image002

Реализация шаблона MVC на примере игры типа Packman – Часть 4

В этойстатье мы опишем самые главные компоненты игры, это модель самой игры и представление игры. После чего мы, я надеюсь, с удовольствием сможем поиграть в нашу игру типа Packman полностью основанную на паттерне MVC.

Модель в шаблоне Model-View-Controller

Начнём с программирования модели игры. Модель является «сутью» нашей игры и отвечает за непосредственные алгоритмы, расчёты и тому подобное внутреннее устройство игры. Обычно под Моделью понимается, часть содержащая в себе функциональную логику приложения, иными словами то, что мы обычно называем «Business Layer», «Бизнес-слой» или «Слой бизнес логики». Как именно организован этот слой, по большому счету не важно, однако есть ряд ключевых моментов, на которых мы остановимся позднее. Основная цель паттерна - сделать так, чтобы Модель была полностью независима от остальных частей и практически ничего не знала об их существовании, что позволило бы менять и Контроллер и Представление модели, не трогая саму Модель и даже позволить функционирование нескольких экземпляров Представлений и Контроллеров с одной Моделью одновременно. Вследствие чего, Модель ни при каких условиях не может содержать ссылок на объекты Представления или Контроллера.

Обычно различают несколько типов паттернов в зависимости от роли модели.

Passive Model (пассивная модель) - Модель не имеет вообще никаких способов воздействовать на Представление или Контроллер и только используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются Контроллером и он же отвечает за перерисовку Представления, если это необходимо.

Active Model (активная модель) - Модель имеет возможность оповестить Представление о том, что в ней произошли некие изменения, и Представление может эти изменения отобразить. Как правило, механизм оповещения реализуется на основе паттерна Observer (обозреватель), Модель просто бросает сообщение, а Представления, которые заинтересованы в оповещении, подписываются на эти сообщения, что позволяет сохранить независимость Модели, как от Контроллера так и от Представления, не нарушая тем самым основного свойства паттерна. Классической реализацией паттерна MVC принято считать версию именно с активной Моделью.

Программирование модели игры типа Packman

Мы будем программировать модель нашей игры как активную модель. Для начала создадим массивы объектов игры, таких как яблоки, танки, стены и объект пакмена. Все классы этих сущностей описаны в статьях ранне. В техническом задании было сказано, что каждый объект должен существовать в своём потоке. Поэтому далее мы создаём для каждого движущегося объекта свой поток. Затем наконец-то пишем самый главный метод Start(). Также нам необходимо написать метод проверки на пересечение объектов, и обработчик события перемещение объекта в заданные координаты, соответственно нам нужно подписать каждый элемент с каждым на пересечение и написать. Размещение стен происходит в методе PlaceWalls() в соответствии с картой, заданной массивом GameForm.map. А теперь посмотрим на сам класс.

namespace PackMan

{

/// <summary>

/// Класс игры.

/// </summary>

class Game

{

#region Объекты елементов и их свойства

/// <summary>

/// Список танков

/// </summary>

List<Tank> arrTank = new List<Tank>();

/// <summary>

/// Список стен

/// </summary>

List<Wall> arrWall = new List<Wall>();

/// <summary>

/// Список яблок

/// </summary>

List<Apple> arrApple = new List<Apple>();

/// <summary>

/// Пакмен.

/// </summary>

PackMan packMan;

/// <summary>

/// Свойство списка яблок,доступное только для чтения

/// </summary>

public List<Apple> ArrApple

{

get { return arrApple; }

}

/// <summary>

/// Свойство списка стен,доступное только для чтения

/// </summary>

public List<Wall> ArrWall

{

get { return arrWall; }

}

/// <summary>

/// Свойство пакмена,доступное только для чтения

/// </summary>

public PackMan PackMan

{

get { return packMan; }

}

/// <summary>

/// Свойство списка танков,доступное только для чтения

/// </summary>

public List<Tank> ArrTank

{

get { return arrTank; }

}

#endregion

#region Вспомогательные поля класса

/// <summary>

/// Список потоков,в которых запускаются объекты

/// </summary>

List<Thread> arrThread = new List<Thread>();

/// <summary>

/// Поток в котором запускается пакмен.

/// </summary>

private Thread packManThread;

/// <summary>

/// Очки.

/// </summary>

private static int score = 0;

/// <summary>

/// Свойство очков.

/// </summary>

public static int Score

{

get { return Game.score; }

set { Game.score = value; }

}

#endregion

/// <summary>

/// Конструктор

/// </summary>

public Game()

{

//Создание стен

for (int i = 0; i < GameForm.countWall; i++)

{

Rectangle rect = new Rectangle();

arrWall.Add(new Wall(rect.Location));

}

PlaceWalls();

//Создание танков

for (int i = 0; i < GameForm.countTank; i++)

{

Rectangle rect = new Rectangle();

do

{

rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Tank()).Width, (new Tank()).Height);

} while (Collides(rect));

arrTank.Add(new Tank(rect.Location));

Thread thread = new Thread(new ThreadStart(arrTank[i].Run));

arrThread.Add(thread);

}

//Создание яблок

for (int i = 0; i < GameForm.countApples; i++)

{

Rectangle rect = new Rectangle();

do

{

rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Apple()).Width, (new Apple()).Height);

} while (Collides(rect));

arrApple.Add(new Apple(rect.Location));

}

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new PackMan()).Width, (new PackMan()).Height);

} while (Collides(rect2));

//Создание пакмена.

packMan = new PackMan(rect2.Location);

packManThread = new Thread(new ThreadStart(PackMan.Run));

SubscribePos();

}

/// <summary>

/// Начало игры

/// </summary>

public void Start()

{

foreach (Thread thread in arrThread)

{

thread.Start();

}

packManThread.Start();

}

/// <summary>

/// Размещение стен в соответствии с картой,заданной массивом GameForm.map

/// </summary>

private void PlaceWalls()

{

int cur = 0;

for (int i = 0; i < GameForm.map.Length; i++)

for (int j = 0; j < GameForm.map[0].Length; j++)

if (GameForm.map[i][j] == '*')

{

arrWall[cur].Position = new Point(i * arrWall[cur].Width, j * arrWall[cur].Height);

cur++;

}

}

/// <summary>

/// Проверка на пересечение объектов

/// </summary>

/// <param name="rect"></param>

/// <returns></returns>

private bool Collides(Rectangle rect)

{

if (rect.Left < 0 || rect.Right >= GameForm.MAXX || rect.Top < 0 || rect.Bottom >= GameForm.MAXY)

return true;

for (int i = 0; i < arrTank.Count; i++)

{

if (arrTank[i].CollidesWith(rect)) return true;

}

for (int i = 0; i < arrWall.Count; i++)

{

if (arrWall[i].CollidesWith(rect)) return true;

}

for (int i = 0; i < arrApple.Count; i++)

{

if (arrApple[i].CollidesWith(rect)) return true;

}

if (packMan != null && packMan.CollidesWith(rect)) return true;

return false;

}

/// <summary>

/// Уничтожение объектов

/// </summary>

public void Dispose()

{

for (int i = 0; i < arrThread.Count; i++)

{

arrThread[i].Abort();

}

packManThread.Abort();

}

/// <summary>

/// Подписка каждого элемента с каждым на пересечение

/// </summary>

public void SubscribePos()

{

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrTank.Count; j++)

{

if (i != j)

{

arrTank[i].CheckPosition += new EventHandler(arrTank[j].OnCheckPosition);

}

}

}

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrWall.Count; j++)

{

arrTank[i].CheckPosition += new EventHandler(arrWall[j].OnCheckPosition);

}

}

for (int j = 0; j < arrWall.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrWall[j].OnCheckPosition);

}

for (int j = 0; j < arrTank.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrTank[j].OnCheckPosition);

}

for (int j = 0; j < arrTank.Count; j++)

{

arrTank[j].CheckPosition += new EventHandler(packMan.OnCheckPosition);

}

for (int j = 0; j < arrApple.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrApple[j].OnCheckPosition);

}

packMan.ReplaceNeeded += new EventHandler(packMan_ReplaceNeeded);

for (int j = 0; j < arrApple.Count; j++)

{

arrApple[j].ReplaceNeeded += new EventHandler(apple_ReplaceNeeded);

}

}

/// <summary>

/// Обработчик события перемещение объекта в заданные координаты

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void apple_ReplaceNeeded(object sender, EventArgs e)

{

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new Apple()).Width, (new Apple()).Height);

} while (Collides(rect2));

(sender as Apple).Position = rect2.Location;

}

/// <summary>

/// Обработчик события перемещение объекта в заданные координаты

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void packMan_ReplaceNeeded(object sender, EventArgs e)

{

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new PackMan()).Width, (new PackMan()).Height);

} while (Collides(rect2));

packMan.Position = rect2.Location;

}

/// <summary>

/// Отписка каждого с каждым от событий пересечения

/// </summary>

private void UnSubscribePos()

{

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrTank.Count; j++)

{

if (i != j)

{

arrTank[i].CheckPosition -= new EventHandler(arrTank[i].OnCheckPosition);

}

}

}

}

}

}

Теперь создадим представление игры. Для этого в классе создаём представление для каждого объекта нашей игры и связываем ссылки представлений объектов с реальными объектами игры. А также создаём событие нажатия на клавишу, описываем его обработчик и подписываем контроллер на событие нажатие клавиши.

namespace PackMan

{

class ViewGame:View<Game>

{

#region Создание представлений и их свойств

//Game model = new Game();

//internal Game Model

//{

// get { return model; }

// set { model = value; }

//}

/// <summary>

/// Спмсок представлений танков

/// </summary>

List<ViewTank> arrViewTank = new List<ViewTank>();

/// <summary>

/// Представление пакмена

/// </summary>

ViewPackMan viewPackMan;

/// <summary>

/// Список представлений стен

/// </summary>

List<ViewWall> viewWall = new List<ViewWall>();

/// <summary>

/// Список представлений яблок

/// </summary>

List<ViewApple> viewApple = new List<ViewApple>();

/// <summary>

/// Контроллер пакмена

/// </summary>

PackManController controller = new PackManController();

/// <summary>

/// Поле на котором происходит игра

/// </summary>

Panel panelMap;

/// <summary>

/// Очки

/// </summary>

Label point;

#endregion

/// <summary>

/// Конструктор

/// </summary>

/// <param name="panelMap"></param>

public ViewGame(Panel panelMap,Label point)

{

this.panelMap = panelMap;

this.point = point;

panelMap.Controls.Add(point);

}

#region Событие нажатие на клавишу

/// <summary>

/// Событие нажатия на клавишу

/// </summary>

private event KeyEventHandler keyPress;

/// <summary>

/// Обработчик нажатия на клавишу

/// </summary>

/// <param name="key">Клавиша,которая нажата</param>

public virtual void OnKeyPress(Keys key)

{

if (keyPress != null)

keyPress(viewPackMan.Model, new KeyEventArgs(key));

}

/// <summary>

/// Подписка на событие нажатие клавиши

/// </summary>

public void SubscribeKeyPress()

{

this.keyPress += new KeyEventHandler(controller.OnKeyPress);

controller.OnKeyPress(viewPackMan, new KeyEventArgs(Keys.Right));

}

/// <summary>

/// Отписка на событие нажатие клавиши

/// </summary>

public void UnsubscribeKeyPress()

{

this.keyPress -= new KeyEventHandler(controller.OnKeyPress);

}

#endregion

/// <summary>

/// Обновить.

/// </summary>

protected override void Update()

{

Refresh();

}

/// <summary>

/// Создание всех элементов игры,

/// связывание ссылок представлений объектоа с реальными объектами игры

/// </summary>

private void Refresh()

{

arrViewTank = new List<ViewTank>();

viewWall = new List<ViewWall>();

viewApple = new List<ViewApple>();

viewPackMan = new ViewPackMan(panelMap);

viewPackMan.Model = Model.PackMan;

viewPackMan.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewPackMan.Subscribe();

for (int i = 0; i < Model.ArrTank.Count; i++)

{

ViewTank viewTanktemp = new ViewTank(panelMap);

viewTanktemp.Model = Model.ArrTank[i];

viewTanktemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewTanktemp.Subscribe();

arrViewTank.Add(viewTanktemp);

}

for (int i = 0; i < Model.ArrWall.Count; i++)

{

ViewWall viewWalltemp = new ViewWall(panelMap);

viewWalltemp.Model = Model.ArrWall[i];

viewWalltemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewWall.Add(viewWalltemp);

}

for (int i = 0; i < Model.ArrApple.Count; i++)

{

ViewApple viewAppletemp = new ViewApple(panelMap);

viewAppletemp.Model = Model.ArrApple[i];

viewAppletemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewAppletemp.Subscribe();

viewApple.Add(viewAppletemp);

}

}

}

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

Реализация шаблона MVC на примере игры типа Packman – Часть 3

В прошлой статье были описаны модели основных сущностей нашей игры типа Pаckman. В этой статье мы поговорим о таком элементе паттерна MVC как “представление”.

Понятие “представления” в шаблоне Model-View-Controller

Представление – это абстрактный суперкласс View реализует общие поведения представления. Встроенный набор компонентов подкласса представления (например, StandardSystemView – стандартное окно и TextEditorView – текстовый редактор). Представления могут быть вложенными для разработки сложного пользовательского интерфейса. Например, StandardSystemView (т.е. окна) содержит компонент представления, называемые TextEditorView.

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

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

Использование обобщенного MVC паттерна сводится к следующим шагам. Во-первых, разработчик определяет свойство модели – любой класс, представляющий данные предметной области и наполненный определенной бизнес-логикой приложения, так называемую модель предметной области (Domain Model). В дальнейшем данный класс будет подставляться в качестве параметра P класса Model – модель готова. Во-вторых, разработчик определяет представление, которое будет отображать данную модель. Класс представления наследуется от класса View, которому в качестве параметра P подставляется свойство модели, определенное на предыдущем шаге. Если необходимо работать с представлением предметной области, разработчик определяет представление, которое наследуется от класса ObjectView<P>. В качестве параметра P класса ObjectView подставляется тот же тип – свойство модели. Остальные задачи: выбор экранной формы отображения данных модели, инициализация ее, а также реакция экранной формы на изменение отображаемой модели полностью лежат на разработчике.

Свойство модели, которое необходимо модели, программист в любом случае формирует – это данные предметной области и логика разрабатываемого приложения. А предлагаемый framework со своей стороны по умолчанию побуждает разработчика выделять данные предметной области и по необходимости бизнес-логику приложения в отдельный компонент, чем по умолчанию уменьшает связность. С одной стороны, обобщенный MVC паттерн оставляет задачи создания визуальных форм представлений программисту, но, с другой стороны, обеспечивает двустороннюю связь отображения с данными приложения (отображение не только может изменять данные, но и обновляется при изменении данных) не обременяя разработчика тонкостями относительно сложной событийной модели MVC.

Приведём пример кода модели и его представления, суть которых описывалась выше.

/// <summary>

/// Класс модели

/// </summary>

/// <typeparam name="P">Тип модели</typeparam>

public class Model<P> where P : new()

{

private P property = new P();

/// <summary>

/// Свойство типа модели

/// </summary>

public virtual P Property

{

get

{

return property;

}

set

{

property = value;

}

}

}

/// <summary>

/// Класс представления модели

/// </summary>

/// <typeparam name="P">Тип модели</typeparam>

public abstract class View<P> where P : new()

{

private P model = new P();

/// <summary>

/// Свойство

/// </summary>

public P Model

{

get { return model; }

set

{

model = value;

Update();

}

}

/// <summary>

/// Обновить

/// </summary>

protected abstract void Update();

/// <summary>

/// Деструктор

/// </summary>

~View()

{

}

}

/// <summary>

/// Контроллер

/// </summary>

/// <typeparam name="P">свойство модели</typeparam>

public class Controller<P> where P: new() {

}

Проектирование и программирование представлений моделей игры

Теперь давайте напишем базовое представление уже для наших сущностей. Как и в проектировании моделей, мы создадим представление статического объекта, т.е. не двигающегося по полю игры и представление динамических объектов. Начнём с класса ViewMapObject, описывающего представление для статических объектов. В данном классе создаётся главное свойство типа PictureBox, содержащее в себе картинку объекта и конструктор, который размещает данную картинку на игровом поле. Также в этом классе осуществляется подписка на событие изменение координат и описан обработчик изменения координат, котором вызывается метод Show (рисование картинки объекта). Ещё описан виртуальный метод изменения картинки в соответствии с его направлением, который реализуем в более конкретных классах.

namespace PackMan

{

/// <summary>

/// Предсталение статического объекта

/// </summary>

class ViewMapObject:View<MapObject>

{

/// <summary>

/// Картинка объекта

/// </summary>

protected PictureBox picBox = new PictureBox();

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Игровое поле</param>

public ViewMapObject(Panel map)

{

map.Controls.Add(picBox);

picBox.Height = Model.Height;

picBox.Width = Model.Width;

}

/// <summary>

/// Подписка на событие изменение координат

/// </summary>

public void Subscribe()

{

this.Model.PositionChanged += new EventHandler(OnPositionChanged);

OnPositionChanged(this, new EventArgs());

}

/// <summary>

/// Отписка от события изменение координат

/// </summary>

private void Unsubscribe()

{

this.Model.PositionChanged -= new EventHandler(OnPositionChanged);

}

/// <summary>

/// Обработчик события изменение координат

/// </summary>

/// <param name="sender">Объект,сгенеривший событие</param>

/// <param name="e">Аргументы события</param>

private void OnPositionChanged(object sender, EventArgs e)

{

Show();

}

/// <summary>

/// Нарисовать картинку объекта

/// </summary>

private void Show()

{

SetImage(Model.Position);

}

delegate void SetImageCallback(Point p);

/// <summary>

/// Подвинуть картинку элемента в соответствии с его координатами

/// </summary>

/// <param name="p"></param>

private void SetImage(Point p)

{

if (this.picBox.InvokeRequired)

{

SetImageCallback d = new SetImageCallback(SetImage);

picBox.Invoke(d, new object[] { Model.Position });

}

else

{

ChangePicture();

this.picBox.Location = p;

}

}

/// <summary>

/// Изменить картинку в соответтствии с его направлением

/// </summary>

protected virtual void ChangePicture() { }

/// <summary>

/// Обновить.

/// </summary>

protected override void Update()

{

Show();

}

}

}

Теперь реализуем класс представление двигающегося объекта ViewDynamicMapObject. Класс очень похож на предыдущий но в шаблон View уже передаётся DynamicMapObject.

namespace PackMan

{

/// <summary>

/// Представление дви жущегося объекта

/// </summary>

class ViewDynamicMapObject:View<DynamicMapObject>

{

/// <summary>

/// Картинка объекта

/// </summary>

protected PictureBox picBox = new PictureBox();

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Игровое поле</param>

public ViewDynamicMapObject(Panel map)

{

map.Controls.Add(picBox);

picBox.Height = Model.Height;

picBox.Width = Model.Width;

}

/// <summary>

/// Подписка на событие изменение координат

/// </summary>

public void Subscribe()

{

this.Model.PositionChanged += new EventHandler(OnPositionChanged);

OnPositionChanged(this, new EventArgs());

}

/// <summary>

/// Отписка от события изменение координат

/// </summary>

private void Unsubscribe()

{

this.Model.PositionChanged -= new EventHandler(OnPositionChanged);

}

/// <summary>

/// Обработчик события изменение координат

/// </summary>

/// <param name="sender">Объект,сгенеривший событие</param>

/// <param name="e">Аргументы события</param>

private void OnPositionChanged(object sender, EventArgs e)

{

Show();

}

/// <summary>

/// Нарисовать картинку объекта

/// </summary>

private void Show()

{

SetImage(Model.Position);

}

delegate void SetImageCallback(Point p);

/// <summary>

/// Подвинуть картинку элемента в соответствии с его координатами

/// </summary>

/// <param name="p"></param>

private void SetImage(Point p)

{

if (this.picBox.InvokeRequired)

{

SetImageCallback d = new SetImageCallback(SetImage);

picBox.Invoke(d, new object[] { Model.Position });

}

else

{

ChangePicture();

this.picBox.Location = p;

}

}

/// <summary>

/// Изменить картинку в соответтствии с его направлением

/// </summary>

protected virtual void ChangePicture() { }

/// <summary>

/// Обновить.

/// </summary>

protected override void Update()

{

Show();

}

}

}

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

/// <summary>

/// Класс пакмена

/// </summary>

class ViewPackMan:ViewDynamicMapObject

{

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Поле игры</param>

public ViewPackMan(Panel map):base(map)

{

picBox.Image = Resources.PackManD;

}

/// <summary>

/// Смена картинки при смене направления движения

/// </summary>

protected override void ChangePicture()

{

switch (Model.DirectionNow)

{

case (int)Direction.Up:

{

picBox.Image = Resources.PackManU;

break;

}

case ((int)Direction.Down):

{

picBox.Image = Resources.PackManD;

break;

}

case (int)Direction.Right:

{

picBox.Image = Resources.PackManR;

break;

}

case (int)Direction.Left:

{

picBox.Image = Resources.PackManL;

break;

}

default:

break;

}

}

}

/// <summary>

/// Представление танка

/// </summary>

class ViewTank : ViewDynamicMapObject

{

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Поле игры</param>

public ViewTank(Panel map):base(map)

{

picBox.Image = Resources.TankU;

}

/// <summary>

/// Смена картинки при смене направления движения

/// </summary>

protected override void ChangePicture()

{

switch (Model.DirectionNow)

{

case (int)Direction.Up:

{

picBox.Image = Resources.TankU;

break;

}

case ((int)Direction.Down):

{

picBox.Image = Resources.TankD;

break;

}

case (int)Direction.Right:

{

picBox.Image = Resources.TankR;

break;

}

case (int)Direction.Left:

{

picBox.Image = Resources.TankL;

break;

}

default:

break;

}

}

}

Более простыми являются представление яблока и стены так как они статические объекты. Разница заключается в том что яблоко появляется на поле при его создании (т.е. в конструкторе описано добавление картинки на игровое поле map.Controls.Add(picBox)), а стена добавляется на поле с помощью массива символов, который я опишу в следущих статьях.

/// <summary>

/// Представление яблока

/// </summary>

class ViewApple : ViewMapObject

{

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Поле игры</param>

public ViewApple(Panel map):base(map)

{

picBox.Image = Resources.Apple;

map.Controls.Add(picBox);

}

}

/// <summary>

/// Класс представления статического объекта

/// </summary>

class ViewWall: ViewMapObject

{

/// <summary>

/// Конструктор

/// </summary>

/// <param name="map">Игровое поле</param>

public ViewWall(Panel map):base(map)

{

picBox.Image = Resources.Wall;;

}

}

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

Реализация шаблона MVC на примере игры типа Packman – Часть 2

В первой статье мы рассмотрели понятие паттернов проектирования и определились с заданием, которое нужно будет реализовать. В этой статье я приведу более подробное описание шаблона MVC и уже начну программирование базовых объектов игры типа Packman.

Шаблон Model-View-Controller

Шаблон Model-View-Controller (в переводе на русский Модель – Представление– Контроллер) представляет собой способ написания программы, при котором вся программа делится на отдельные независимые элементы, которые работают вне зависимости от состояния соседнего элемента, но работают совместно. Как это может быть, спросите Вы, ведь невозможно написать программу, чтобы события не влияли одно на другое, ведь в программе все взаимосвязано, и если нужно что-то изменить, то необходимо переписывать огромное количество программного кода. Именно чтобы этого не делать и была придумана структура паттерн MVC.

Если обратиться к Википедии то мы увидим следующее описание паттерна.Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента:

· Модель (Model). Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контроллера), изменяя своё состояние.

· Представление (View). Отвечает за отображение информации (пользовательский интерфейс).

· Поведение (Controller). Интерпретирует данные, введённые пользователем, и информирует модель и представление о необходимости соответствующей реакции.

Важно отметить, что как представление, так и поведение зависят от модели. Однако модель не зависит ни от представления, ни от поведения. Это одно из ключевых достоинств подобного разделения. Оно позволяет строить модель независимо от визуального представления, а также создавать несколько различных представлений для одной модели. Стандартная схема архитектуры «Модель-Вид-Контроллер» изображена на следующем рисунке: (схема заимствована из книги «Ajax in action» издательского дома «Вильямс»)

clip_image002

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

Проектирование и программирование моделей игры

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

Начнём с описания моделей приложения. У меня в приложении есть несколько сущностей, таких как яблоко, танк, стена и сам пакмэн. Напишем для начала базовый класс для всех этих объектов приложения.

В конструкторе базового класса объектов определяется позиция объекта на карте рандомно, либо передавая координаты параметрами. Далее в классе я определяю два события PositionChanged и ReplaceNeeded, которые сообщают представлению об изменении позиции объекта и о необходимости перемещения объекта в другую позицию экрана. Также в классе реализован обработчик события пересечения объектов и соответствующие необходимые функции, которые определяют, пересекаются ли объекты и вызывает функцию ухода от препятствия.

using System;

using System.Collections.Generic;

using System.Text;

using System.Drawing;

using System.ComponentModel;

using PackMan.MVC;

namespace PackMan

{

/// <summary>

/// Класс статического объекта

/// </summary>

public class MapObject

{

/// <summary>

/// Координаты объекта

/// </summary>

protected Point position;

/// <summary>

/// Свойство координат

/// </summary>

public Point Position

{

get { return position; }

set

{

position = value;

OnPositionChanged();

}

}

/// <summary>

/// Ширина объекта

/// </summary>

private const int width = 25;

/// <summary>

/// Длина объекта

/// </summary>

private const int height = 25;

/// <summary>

/// Свойство длины объекта

/// </summary>

public int Width

{

get { return width; }

}

/// <summary>

/// Свойство ширины объекта

/// </summary>

public int Height

{

get { return height; }

}

/// <summary>

/// Размеры поля

/// </summary>

private Point mapSize;

/// <summary>

/// Свойство размеров поля

/// </summary>

public Point MapSize

{

get { return mapSize; }

set { mapSize = value; }

}

/// <summary>

/// Конструктор

/// </summary>

public MapObject()

{

this.position = new Point(GameForm.rand.Next(0, GameForm.MAXX - Width - 2), GameForm.rand.Next(0, GameForm.MAXY - Height - 2));

}

/// <summary>

/// Конструктор

/// </summary>

/// <param name="position">Координаты</param>

public MapObject(Point position)

{

this.position = position;

}

/// <summary>

/// Событие перемещения

/// </summary>

public event EventHandler ReplaceNeeded;

/// <summary>

/// Обработчик события перемещения

/// </summary>

public void OnReplaceNeeded()

{

if (ReplaceNeeded != null)

ReplaceNeeded(this, new EventArgs());

}

/// <summary>

/// Проверка лежит ли точка в области объекта this

/// </summary>

/// <param name="p">координаты объекта</param>

/// <returns>true-пересекаются

/// false-не пересекаются</returns>

protected bool CheckCrossing(Point p)

{

if (this.Position.X + this.Width >= p.X && this.Position.X <= p.X)

{

if (this.Position.Y + this.Height >= p.Y && this.Position.Y <= p.Y)

{

return true;

}

}

return false;

}

/// <summary>

/// Проверка на пересечение двух объектов

/// </summary>

/// <param name="rect"></param>

/// <returns></returns>

public bool CollidesWith(Rectangle rect)

{

if (CheckCrossing(new Point(rect.Left, rect.Top)) ||

CheckCrossing(new Point(rect.Right, rect.Top)) ||

CheckCrossing(new Point(rect.Right, rect.Bottom)) ||

CheckCrossing(new Point(rect.Left, rect.Bottom)))

return true;

return false;

}

/// <summary>

/// Обработчик события пересечения объектов

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

public virtual void OnCheckPosition(object sender, EventArgs e)

{

PositionChangedEventArgs positionArgs = e as PositionChangedEventArgs;

if (positionArgs == null)

return;

if (CollidesWith(positionArgs.NewRectangle))

{

((DynamicMapObject)sender).Deviate();

}

}

/// <summary>

/// Событие сменились координаты

/// </summary>

public event EventHandler PositionChanged;

/// <summary>

/// Обработчик события сменились координаты

/// </summary>

/// <param name="position"></param>

protected virtual void OnPositionChanged()

{

if (PositionChanged != null)

PositionChanged(this, new EventArgs());

}

}

}

В следующем классе я опишу поведение уже не статического объекта а движущегося, назову класс . DynamicMapObject, который отнаследую от только что написанного класса MapObject. У класса DynamicMapObject уже есть спецальные переменные, отвечающие за перемещение объекта, такие как dx, dy, delta, и за его направление (directionNow). Также в классе описаны основные методы движения, поворота и ухода от препятствий и самый главный метод который запускает движение объекта это метод Run, который каждые проверяет позицию объекта на столкновение с другими объектами и перемещает его далее каждые 50 милисекунд (Thread.Sleep(50)). Посмотрим как всё это выглядит.

using System;

using System.Collections.Generic;

using System.Text;

using MVC;

using PackMan.MVC;

using System.Drawing;

using System.ComponentModel;

using System.Windows.Forms;

using PackMan.Properties;

using System.Threading;

namespace PackMan

{

public class DynamicMapObject : MapObject

{

int dy;

int dx;

bool flag = false;

protected int delta;

private int directionNow;

public int DirectionNow

{

get { return directionNow; }

}

public DynamicMapObject() : base()

{

}

public DynamicMapObject(Point position) : base (position)

{

delta = 1;

dy = 0;

dx = delta;

Turn();

}

virtual public void Move()

{

if (position.X + dx >= 0 && position.X + this.Width + dx < MapSize.X)

position.X += dx;

else

Deviate();

if (position.Y + dy >= 0 && position.Y + this.Height + dy < MapSize.Y)

position.Y += dy;

else

{

Deviate();

}

flag = true;

OnPositionChanged();

Thread.Sleep(50);

}

public void Stop()

{

dx = 0;

dy = 0;

}

public void Deviate()

{

if (flag == true)

{

dx = -dx;

dy = -dy;

switch (directionNow)

{

case (int)Direction.Left:

{

directionNow = (int)Direction.Right;

break;

}

case (int)Direction.Right:

{

directionNow = (int)Direction.Left;

break;

}

case (int)Direction.Up:

{

directionNow = (int)Direction.Down;

break;

}

case (int)Direction.Down:

{

directionNow = (int)Direction.Up;

break;

}

}

flag = false;

}

}

public void Turn()

{

IdentifyDirection(GameForm.rand.Next(0, 4));

}

public void IdentifyDirection(int direction)

{

switch (direction)

{

case (int)Direction.Down:

{

dy = delta;

dx = 0;

directionNow = (int)Direction.Down;

break;

}

case (int)Direction.Left:

{

dy = 0;

dx = -delta;

directionNow = (int)Direction.Left;

break;

}

case (int)Direction.Right:

{

dy = 0;

dx = delta;

directionNow = (int)Direction.Right;

break;

}

case (int)Direction.Up:

{

dy = -delta;

dx = 0;

directionNow = (int)Direction.Up;

break;

}

default:

break;

}

}

public virtual void Run()

{

while (true)

{

if (GameForm.rand.Next(0, 50) == 1)

Turn();

OnCheck();

Move();

}

}

public event EventHandler CheckPosition;

protected virtual void OnCheck()

{

if (CheckPosition != null)

CheckPosition(this, new PositionChangedEventArgs( new Rectangle(this.position.X + dx, this.position.Y + dy, this.Width, this.Height)));

}

public override void OnCheckPosition(object sender, EventArgs e)

{

PositionChangedEventArgs positionArgs = e as PositionChangedEventArgs;

if (positionArgs == null)

return;

if (CollidesWith(positionArgs.NewRectangle))

{

if (sender is Tank)

{

this.Deviate();

((DynamicMapObject) sender).Deviate();

}

if (sender is PackMan)

{

(sender as PackMan).Die();

}

}

}

}

}

А теперь уже запрограммируем самого пакмена, который является двигающимся объектом, следовательно, будет отнаследован от класса DynamicMapObject. Класс включает в себя количество жизней пакмена, обработчик события смены координат и метод Die(), который вызывается при столкновении пакмена с танком. В данной реализации, для того чтобы играть вечно (и потому что мне очень жалко пакмена), при столкновении с танком наш главный герой просто перемещается в другую точку карты, а не умирает как многие бы могли подумать.

namespace PackMan

{

/// <summary>

/// Класс пакмена

/// </summary>

public class PackMan:DynamicMapObject

{

/// <summary>

/// Количество жизней

/// </summary>

private int life = 3;

/// <summary>

/// Метод запуска

/// </summary>

public override void Run()

{

while (true)

{

OnCheck();

Move();

}

}

/// <summary>

/// Конструктор

/// </summary>

public PackMan() : base()

{

delta = 2;

}

/// <summary>

/// Конструктор

/// </summary>

/// <param name="position">Координаты</param>

public PackMan(Point position) : base (position)

{

delta = 2;

}

/// <summary>

/// Смерть Пакмена.

/// </summary>

public void Die()

{

// viewGame.Model.Dispose();

OnReplaceNeeded();

}

/// <summary>

/// Обработчик события смена координат

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

public override void OnCheckPosition(object sender, EventArgs e)

{

PositionChangedEventArgs positionArgs = e as PositionChangedEventArgs;

if (positionArgs == null)

return;

if (CollidesWith(positionArgs.NewRectangle))

{

if (sender is Tank)

Die();

}

}

}

}

Модели танка и стены ничем не превосходят базовый класс, описывающий динамический и статический объект соответственно. Поэтому это классы просто отнаследованные от DynamicMapObject и от MapObject.

/// <summary>

/// Класс танка

/// </summary>

public class Tank:DynamicMapObject

{

#region Конструкторы

/// <summary>

/// Конструктор

/// </summary>

public Tank()

{

}

/// <summary>

/// Конструктор

/// </summary>

/// <param name="position">Координаты стены</param>

public Tank(Point position) : base(position)

{

}

#endregion

}

/// <summary>

/// Класс стены

/// </summary>

public class Wall:MapObject

{

#region Конструкторы

/// <summary>

/// Конструктор

/// </summary>

/// <param name="position">Координаты стены</param>

public Wall(Point position) : base(position)

{

}

/// <summary>

/// Конструктор

/// </summary>

public Wall()

{

}

#endregion

}

}

А вот класс яблока немного отличается по функциональности от своего базового, поэтому в нём переопределён обработчик события проверки координат. Если яблоко пересекается с пакмэном то пакмену зачисляется 10 очков на счёт, а яблоко перемещается в другое место.

/// <summary>

/// Класс яблока

/// </summary>

public class Apple:MapObject

{

/// <summary>

/// Конструктор

/// </summary>

/// <param name="position">Координаты</param>

public Apple(Point position) : base(position)

{

}

/// <summary>

/// Конструктор

/// </summary>

public Apple()

{

}

/// <summary>

/// Перемещает яблоко при съедании

/// </summary>

private void Replace()

{

OnReplaceNeeded();

}

/// <summary>

/// Обработчик события проверки координат

/// </summary>

/// <param name="sender">Объект,сгенеривщий событие</param>

/// <param name="e">Аргументы события</param>

public override void OnCheckPosition(object sender, EventArgs e)

{

PositionChangedEventArgs positionArgs = e as PositionChangedEventArgs;

if (positionArgs == null)

return;

if (CollidesWith(positionArgs.NewRectangle))

{

if (sender is PackMan)

{

Replace();

Game.Score += 10;

}

}

}

}

В следующей статье я запрограммирую представления описанных сегодня сущностей.

Реализация шаблона MVC на примере игры типа Packman – Часть 1

В этой статье, а может даже в серии статей, я хочу рассказать о паттерне проектирования MVC в классическом его понимании. И реализовать игру, похожую на всем известную игру Packman, на паттерне MVC.

Для начала определимся, что такое паттерны программирования для чего они нужны, а потом подробнее рассмотрим паттерн Model-View-Controller.

Теория

Понятие шаблона проектирования

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

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

С помощью шаблонов проектирования описываются и формализуются типовые задачи и их решения. В результате опыт, который нарабатывается с большим трудом, становится доступным широкому сообществу программистов. Шаблоны это не просто какая-то теория, это в первую очередь практика, практика программистов, которые уже решали похожие задачи и нашли ценой проб и ошибок, размышлений и обмена мнениями то, как нужно проектировать приложение или его отдельные части в конкретном случае для поставленных требований. В английском языке есть такой термин “best practice”(перевод: “лучшие методы” или “наилучшие решения”) я считаю, что это выражение лучшим образом описывает суть паттернов. Знаменитый приверженец шаблонов Мартин Фаулер говорит, что он открывает шаблоны, а не создает их. Поэтому многие шаблоны у программистов вызывают чувство “дежа вю” — ведь часто бывает, что вы используете некоторые методы при проектировании своего приложения и просто не знаете, что они вынесены в шаблоны.

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

Вообще если обратиться к истории, то первые шаблоны проектирования для языка программирования Smalltalk представили в 1987 г. Кэнт Бэк и Вард Каннингем. Но это было только началом, настоящее признание в мире программирования они получили после публикации книги «Приемы объектно-ориентированного проектирования. Паттерны проектирования», написанной программистами Э. Гаммом, Р. Хелмом, Р. Джонсоном и Дж. Влиссидесом. В ней были представлены 23 шаблона, ставших сейчас основными. Данная работа дала толчок к изучению паттернов программистами. Издание «банды четырех» (так в шутку прозвали авторов книги) до сих пор остается одним из ИТ-бестселлеров, и его постоянно публикуют.

Краткий обзор основных паттернов

Рассмотрим кратко основные первые 23 паттерна, с которых все началось и которые сейчас должен знать каждый программист. Они разделены на три группы (для каждого паттерна приводится английское название из книги GoF и устоявшийся русский перевод). Краткое описание и разделение на группы описано в статье Олега Фёдорова в журнале PC Magazine.

«Порождающие шаблоны». В этой группе собраны паттерны, описывающие разные способы создания объектов. Прежде всего, это «Фабричный метод» (Factory Method), прием определения интерфейса создания объектов, при этом выбранный класс воплощается в подклассах. Шаблон «Абстрактная фабрика» (Abstract Factory) определяет интерфейс для создания семейств, связанных между собой или независимых объектов, конкретные классы которых неизвестны. С помощью шаблона «Строитель» (Builder) можно отделить процесс конструирования сложного объекта от его конкретного представления и при этом использовать один и тот же процесс для создания различных представлений. «Прототип» (Prototype) описывает виды разрабатываемых объектов с помощью прототипа и создает новые путем его копирования. Применение шаблона «Одиночка» (Singleton) гарантирует, что некоторый класс может иметь только один экземпляр (и предоставляет глобальную точку доступа к нему).

«Структурные паттерны». В этой группе собраны паттерны, которые позволяют менять структуру взаимодействия классов. «Адаптер» (Adapter) позволяет адаптировать интерфейс класса к конкретной ситуации, средствами шаблона «Мост» (Bridge) можно отделить интерфейс класса и его реализацию, «Компоновщик» (Composite) объединяет объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам единообразно обращаться к отдельным объектам и группам объектов. Паттерн «Оформитель» (Decorator, также известен какWrapper, «Оболочка») позволяет динамически добавлять новое поведение к объекту, «Фасад» (Facade)— скрыть сложность системы путем сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы. Шаблон «Приспособленец» (Flyweight) используется для облегчения работы с большим числом мелких объектов, а «Заместитель» (Proxy)— контролировать доступ к объекту, перехватывая все вызовы к нему.

В группе «Паттерны поведения» собраны шаблоны, ответственные за реализацию поведения объектов. «Цепочка ответов» (Chain of Response) позволяет пропустить запрос через цепочку объектов, «Команда» (Command) инкапсулирует команду в объект, «Интерпретатор» (Interpreter) позволяет создать общее декларативное решение для часто изменяющихся условий задачи. В шаблоне «Итератор» (Iterator) организуется последовательный доступ к коллекции, «Посредник» (Mediator) определяет упрощенный механизм взаимодействия классов, «Напоминание» (Memento) задает принципы, позволяющие записывать и восстанавливать внутреннее состояние объекта. Средствами шаблона «Наблюдатель» (Observer) можно оповещать об изменениях множества объектов, «Состояние» (State)— менять поведение объекта при изменении его состояния. «Стратегия» (Strategy) инкапсулирует алгоритм внутри класса. Паттерн «Шаблонный метод» (Template Method) выделяет конкретные шаги в алгоритме и опирается на подклассы для их реализации. Средствами паттерна «Посетитель» (Visitor) в класс добавляются новые операции без его изменения.

Вот мы очень кратко ознакомились со всеми основными паттернами и определились с тем, что паттерны это очень полезная вещь, и каждый хороший программист должен знать и использовать шаблоны проектирования при разработке своих приложений. Для хорошего изучения паттернов программирования я советую прочитать книгу “Приемы объектно-ориентированного проектирования. Паттерны проектирования”, написанную Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. В данной книге описываются простые и изящные решения типичных задач, возникающих в объектно-ориентированном проектировании. Авторы излагают принципы использования паттернов проектирования и приводят их каталог. Таким образом, книга одновременно решает две задачи. Во-первых, в ней демонстрируется роль паттернов в создании архитектуры сложных систем. Во-вторых, применяя содержащиеся в справочнике паттерны, вы сможете с легкостью разрабатывать собственные приложения. Издание предназначено как для профессиональных разработчиков, так и для программистов, осваивающих объектно-ориентированное проектирование. Как показывает практика, чтобы понять и осознать всё что написано в данной книге, прочитать её придётся не один раз. А для лучшего освоения нужно попробовать применить шаблоны проектирования на практике для решения хотя бы небольших задач. Так далее я попробую применить шаблон MVC (Model-View-Controller) при написании игры типа Packman. Определимся сначала с тем что нам нужно сделать. Т.е. приведу техническое задание.

Практика

Концептуальная модель

Игра состоит из поля с расставленными на нем стенами, на котором взаимодействуют несколько сущностей:

  1. Русский колобок. Начинает постоянное движение при старте игры и управляется пользователем с помощью клавиатуры. Игра заканчивается при столкновении колобка с вражеским танком.
  2. Вражеский танк. Может присутствовать в количестве нескольких штук (по умолчанию 5). Начинает постоянное движение при старте игры. На каждом шаге может изменить движение путем случайного выбора направления следующего шага. При столкновении двух танков каждый из них разворачивается на 180 градусов и продолжает движение
  3. Наливное яблоко. Может присутствовать в количестве нескольких штук (по умолчанию 5). Случайным образом расставляются в свободные для прохода клетки поля при старте игры. При наезде танка на яблоко ничего не происходит – танк проходит через клетку с яблоком и продолжает двигаться дальше. При столкновении с яблоком колобка - яблоко исчезает (съедается), общий счет игры увеличивается на 1 очко, и яблоко снова появляется в случайно выбранном месте на игровом поле.

Объекты при движении должны смещаться на пиксель для создания эффекта плавного перемещения.

Проектирование

1. Уровень представления данных и логики приложения

Приложению в качестве параметров запуска должны передаваться следующие параметры:

  1. Размеры игрового поля (ширина и высота)
  2. Количество танков на поле
  3. Количество яблок на поле
  4. Скорость передвижения объектов (задается для всех объектов сразу).

При запуске приложения должно показываться его окно, состоящее из следующих элементов:

  1. Игровое поле, на котором будут прорисовываться перечисленные концептуальной модели объекты.
  2. Кнопка New Game, при нажатии на которую будет начинаться новая игра.

При старте игры поле представляет собой пространство, со стенами и объектами, расположенными на нем примерно следующим образом:

clip_image002

Рис. 1. Пример интерфейса игры типа Packman

2. Паттерны

При проектировании приложения следует использовать паттерн MVC (Model View Controller).

Конструирование

Сущности колобка и танка должны представлять собой отдельные потоки и реализовываться в отдельных классах Packman и Tank.

Изображение сущностей должно быть реализовано с помощью классов PackmanView и TankView соответственно.

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

Дополнительные задания
  1. Реализовать «прозрачные стены» для игрового поля – т.е. когда танк подходит к границе поля, он не отходит в сторону, а выходит с противоположной стороны поля, то же самое происходит с колобком.
  2. Реализовать второе окно в качестве альтернативного view для игрового поля. Оно будет представлять из себя frame с таблицей со следующими полями:
    - название объекта
    - координаты в следующем виде: (X, Y)
  3. Сделать возможность расстрела колобком танков

В следующих статьях я предложу свою реализацию описанного задания, а также я подробно опишу паттерн проектирования MVC в классическом его понимании. А вы можете попробовать написать сами и потом сравнить с моим решением. Я не буду реализовывать дополнительное задание, и описывать это в своих статьях, так как целью решения этого задания является не создание интересной навороченной игры, а изучение и применение шаблона MVC, событийной модели приложений, применение принципов ООП, использование интерфейсов и шаблонных типов. Также нужно попытаться сделать приложение достаточно производительным, чтобы игра не тормозила на стандартных конфигурациях компьютеров, что легко может быть т.к. по заданию каждая сущность имеет свой поток. Также хочу отметить, что создавать приложение я буду на C# c использованием технологии Windows Form. Все исходники данной игры можно будет скачать по ссылке, приведённой в последней статье.

Разработка приложения поиска строк с заданной маской

Введение

В этой статье я хочу вам предложить задание для начинающих изачать C#. Задание не сложное, но отнеситесь к нему серъёзно. Не забывайте комментировать свой код.,

Немного теории

Создание решения в Visual Sudio .NET:

Пример программы представляет из себя отдельный проект. В Visual Sudio .NET проекты обычно группируются в решения. Для создания нового проекта выберите в меню File|New| Project. В диалоговом окне New Project выберите:

  • Project Types: Visual C# Projects;
  • Templates: Windows Application или Console Application;
  • Name: имя решения;
  • Location: его местоположение на диске.

Cтруктура простейшей программы на C#:

using <ИспользуемоеПространствоИмён1>;

// ...

using <ИспользуемоеПространствоИмёнN>;

namespace <ИмяПространстваИмён>

{

public class <ИмяКласса>

{

// Список переменных

// Методов

// и т.д...

// Конструктор класса

public <ИмяКласса1>()

{

}

static void Main()

{

}

}

public class <ИмяКлассаN>

{

// ...

}

}

Варианты

1. Консольное приложение

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

Требования:

1. Возможность задачи параметров через командную строку.

2. Результаты: имя файла, № строки, содержимое строки с совпадением.

3. Результаты должны выводиться на консоль.

2. Графическое приложение (Windows Forms)

Написать приложение с графическим интерфейсом (Windows.Forms), которое

будет искать в файлах с заданной маской в заданном каталоге указаную строку.

Требования:

1. Возможность задачи параметров через командную строку.

2. Результаты: имя файла, № строки, содержимое строки с совпадением.

3. Результаты должны выводиться в многострочный TextBox.

3. Графическое приложение с дополнительными функциями

Написать приложение с графическим интерфейсом (Windows.Forms), которое

будет искать в файлах с заданной маской в заданном каталоге указанные

строки (аналогично grep в UNIX).

Требования:

1. Локализация формы: по умолчанию - английская, при возможности - русская.

2. Возможность задачи параметров через командную строку.

3. Результаты: имя файла, № строки, содержимое строки с совпадением.

4. Результаты должны выводиться в многострочный TextBox.

5. Применение текущей темы Windows XP.

Полный разбор варианта №1

using System;

using System.IO;

namespace Lab2CSharp_L1

{

class FindFiles

{

private static void FindFile(string findLine, string mask, string

dirName)

{

string[] filesName; // = new string[1];

try

{

filesName = Directory.GetFiles(dirName, (mask.Trim() == "") ?

("*.*") : (mask.Trim()));

}

catch (Exception e)

{

Console.WriteLine("Error: The file could not be read: " +

e.Message);

return;

}

foreach(string oneFileName in filesName)

{

if(findLine.Trim() == "")

{

Console.WriteLine(oneFileName + " \n");

}

else

{

try

{

using (StreamReader sr = new

StreamReader(oneFileName))

{

String line;

long Num = 1;

while ((line = sr.ReadLine()) != null)

{

Console.WriteLine(oneFileName + ",

Line(" + Num + "): \"" + line + "\" \n");

Num++;

}

sr.Close();

}

}

catch (Exception e)

{

Console.WriteLine("Error: The file could not be

read: " + e.Message);

return;

}

}

}

}

[STAThread]

static void Main(string[] args)

{

if(args.Length == 3)

{

FindFile(args[2], args[1], args[0]);

}

else if(args.Length == 0)

{

string findLine;

string mask;

string dirName = "";

Console.WriteLine("Поиск в файлах с заданной маской в

заданном каталоге указанной строки.");

do{

Console.WriteLine("\nВведите путь поиска: ");

dirName = Console.ReadLine();

}while(dirName == "");

Console.WriteLine("\nВведите искомую строку: ");

findLine = Console.ReadLine();

Console.WriteLine("\nВведите маску поиска: ");

mask = Console.ReadLine();

FindFile(findLine, mask, dirName);

Console.Read();

}

else

{

Console.WriteLine("FORMAT: ProgName Path Mask FindWhat");

}

Console.Read();

}

}

}

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

Модификация приложения JobTerrarium

Я предлагаю Вам исправить/дополнить то задание, которое мы делали в прошлой статье, используя интерфейсы.

Техническое задание

А именно:

1) Все объекты, которые участвуют в 'игре', реализуют интерфейс IMovable - он предоставляет возможность позиционировать объект на поле с помощью метода Move(Point p);[Используется для начального позиционирования всех объектов и для перемещения по полю живых существ], реализует свойство IsAlive (только для чтения) показывающее живой ли это организм(например Рабочий) или неживой(работа, прибавка к зарплате, (cтена))

2)Те работники, которые могут управлять другими (Бос, БигБосс, Заказчик) должны реализовывать интерфейс IManage() - он предоставляет возможность 'управлять' сотрудниками с помощью функции Manage(IManagable) - реализация за Вами, сказать что-то… изменить какие то свойства .. вызвать какой то метод интерфейса IManagable - выбирайте сами.

3)Те работники что могут управляться другими (Бос, Рабочий) - должны реализовывать интерфейс IManagable - придумайте его сами , только хорошо закомментируйте, возможен и такой вариант interface IManagable{};

Создайте Аттрибут для хранения информации о девелопере (якобы)измнившем код подследним. Обязательными являются его (фамилия) ник типа string и дата модификации кода. Опциональными - комментарий типа string . Установите его для всех классов ( в какое-то вымышленное значение ).

Также можно исправить SayHello / DoWork / Say в соответствии с концепциями OOП с помощью абстрактных классов и функций, виртуальных функиций, перекрытия(override) функций.

Также у нас появилось новое существо: Customer , он не имеет зарплаты, может не здороваться(а может и здороваться - решайте сами:), но он может раскидывать работы, и управлять персоналом(кастомер всегда прав:). Этот класс не может и не должен быть наследован от Employee , тем не менее он может перемещаться по полю, и взаимодействовать с персоналом описаным выше образом.

Все свойства обьектов (Настроение/Зарплата/Имени/(ОпытРаботы)) - реализуйте через свойства(property), только get или get/set по смыслу:)

Реализуйте (и документируйте) сложное поведение обьекта - например при повышении зарплаты(set) не только повышается зарплата но и настроение. (Можете придумать свои варианты сложного поведения обьектов при изменении свойств).

Переопределите(те кто этого не сделал в предыдущем задании) методы != == для структуры Point, используйте именно эти операторы и эту структуру везде где дело касается координат обьектов.

В качестве ДОПОЛНИТЕЛЬНОГО ЗАДАНИЯ:

· можете реализовать еще какое-то существо или какой-то объект неживой природы, опишите в комментариях его поведение.

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

Разработка и программирование

Опишем сначала интерфейсы, заданные в ТЗ выше. Это интерфейсы ITalk, IMovable, IManage, IManagable определяющие характерные для них методы.

namespace Task_2

{

interface ITalk

{

void Talk(DymanicMapObject employee);

void Say(string WhatToSay);

}

}

namespace Task_2

{

interface IMovable

{

void Place(Point p);

bool isAlive();

}

}

namespace Task_2

{

interface IManage

{

void Manage(IManagable managable);

}

}

namespace Task_2

{

interface IManagable

{

void RaiseSalary(decimal amount);

void MoodWorsen();

}

}

Далее создадим базовый класс MapObject, который может быть движимым, следовательно относледуем его от интерфейса IMovable. Это класс для всех сущностей нашей системы. Класс имеет всего лишь координаты и метод Show() для отображения сущности на поле.

namespace Task_2

{

//Статический объект на поле

abstract class MapObject : IMovable

{

protected Point position = new Point();

public Point Position

{

get { return position; }

set { position = value; }

}

public void Place(Point position)

{

this.position = position;

}

public bool isAlive()

{

return false;

}

public void Show(Graphics g, int cellsize)

{

g.DrawString(this.ToString(), new Font("Arial", 7), new SolidBrush(Color.Red), new RectangleF(position.x * cellsize, position.y * cellsize, cellsize, cellsize));

}

}

}

Далее реализуем класс DynamicMapObject, который будет отвечать за объекты двигающиеся по полю. В этом классе описан только метод Move().

using System;

using System.Collections.Generic;

using System.Text;

namespace Task_2

{

//Объект, который двигается

class DymanicMapObject : MapObject

{

virtual public void Move()

{

int dx = Engine.random.Next(-1, 2);

int dy = Engine.random.Next(-1, 2);

if (position.x + dx >= 0 && position.x + dx < Engine.maxPoint.x)

position.x += dx;

if (position.y + dy >= 0 && position.y + dy < Engine.maxPoint.y)

position.y += dy;

}

public bool isAlive()

{

return true;

}

}

}

Затем реализуем класс Employee, который будет уже базовым для сущностей босс, рабочий и бигбосс. Класс наследуется от DymanicMapObject и интерфейсов IManagable, ITalk. Данный класс уже имеет такие свойства как Salary и Name, а такэе метод Talk() и Say().

using System;

using System.Collections.Generic;

using System.Text;

using Task_2Winforms;

namespace Task_2

{

abstract class Employee : DymanicMapObject, IManagable, ITalk

{

public Employee(string name, decimal salary)

{

this.Name = name;

this.salary = salary;

this.mood = true;

Place(new Point(Engine.random.Next(Engine.maxPoint.x), Engine.random.Next(Engine.maxPoint.y)));

}

public Employee() : this("Noname", 0)

{

}

public Employee(decimal salary) : this("Noname", salary)

{

}

private decimal salary;

private string name;

public string Name

{

get { return name; }

set { name = value; }

}

private bool mood;

private Point position;

public void Say(string WhatToSay)

{

TextBuffer.WriteLine(this.ToString() + " " + name + " say: \"" + WhatToSay+ "\"");

}

public void Talk(DymanicMapObject employee)

{

if (employee is Worker)

Say(SayToWorker());

else

{

if (employee is BigBoss)

Say(SayToBigBoss());

else if (employee is Boss)

Say(SayToBoss());

}

}

public virtual string SayToBoss()

{

return "Hello!";

}

public virtual string SayToBigBoss()

{

return "Good morning!";

}

public virtual string SayToWorker()

{

return "Hi!";

}

public override string ToString()

{

return "Employee";

}

public void RaiseSalary(decimal amount)

{

salary += amount;

this.mood = true;

Say("Спасибо за прибавку " + amount);

}

public void MoodWorsen()

{

this.mood = false;

Say("У меня испортилось настроение!");

}

}

}

Класс Worker наследуется от Employee и переопределяет методы, с помощью которых разговаривает Employee.

using System;

using System.Collections.Generic;

using System.Text;

namespace Task_2

{

class Worker : Employee

{

public Worker():base("Noname",500)

{

}

public Worker(decimal salary): base("Noname",salary)

{

}

public Worker(string name, decimal salary): base(name, salary)

{

}

public override string SayToWorker()

{

return "Привет, как дела?";

}

public override string SayToBoss()

{

return "Здравствуйте, отлично выглядите сегодня.!";

}

public override string SayToBigBoss()

{

return "Здравствуйте!";

}

public override string ToString()

{

return "Woker";

}

}

}

Класс Boss наследуется от Employee и переопределяет методы, с помощью которых разговаривает Employee.

using System;

using System.Collections.Generic;

using System.Text;

namespace Task_2

{

class Boss : Employee, IManage

{

public Boss():base("Noname",1000)

{

}

public Boss(decimal salary): base("Noname",salary)

{

}

public Boss(string name, decimal salary)

: base(name, salary)

{

}

public override string SayToWorker()

{

return "Привет, почему не работаешь?";

}

public override string SayToBoss()

{

return "Привет!";

}

public override string SayToBigBoss()

{

return "Здравствуйте!";

}

public override string ToString()

{

return "Boss";

}

public void Manage(IManagable managable)

{

managable.RaiseSalary(Engine.random.Next(100));

}

}

}

Класс BigBoss наследуется от Boss и переопределяет методы, с помощью которых разговаривает Boss.

using System;

using System.Collections.Generic;

using System.Text;

using Task_2Winforms;

namespace Task_2

{

[Developer("Vasya Ivanov", "23.11.07")]

class BigBoss : Boss,IManagable

{

public BigBoss(string name):base(name,1500)

{

}

public BigBoss(string name, decimal salary):base(name,salary)

{

}

public override string SayToWorker()

{

return "Сколько можно бездельничать?";

}

public override string SayToBoss()

{

return "Как работа?";

}

public override string SayToBigBoss()

{

return "Здравствуй.";

}

public override string ToString()

{

return "BigBoss";

}

void RaiseSalary(decimal amount)

{

TextBuffer.WriteLine("Нельзя управлять мной!!!");

}

}

}

Опишем класс Work, т.к. работа это недвижимый объект на карте, то отнаследуем его от класса MapObject.

using System;

using System.Collections.Generic;

using System.Text;

namespace Task_2

{

class Work : MapObject

{

public override string ToString()

{

return "Work";

}

}

}

Теперь опишем класс Закачик, который всегда прав и который в методе placeWork – размещает работу в левом верхнем углу от того где он сам находится и передвигается в методе Move().

using System;

using System.Collections.Generic;

using System.Collections;

using System.Text;

using Task_2Winforms;

namespace Task_2

{

// Заказчик

class Customer : DymanicMapObject, IManage, ITalk

{

// Портит настроение всем на своём пути

public void Manage(IManagable managable)

{

managable.MoodWorsen();

}

// Размещает работу в левом верхнем углу от того где он сам находится

public Point placeWork()

{

Point p = new Point(Position.x-1,Position.y-1);

return p;

}

public void Say(string WhatToSay)

{

TextBuffer.WriteLine(this.ToString() + " say: \"" + WhatToSay + "\"");

}

public void Talk(DymanicMapObject employee)

{

if (employee is Worker)

Say("Я всегда прав!!!");

else

{

if (employee is BigBoss)

Say("Давайте договоримся!");

else if (employee is Boss)

Say("Позовите мне бигбосса!");

}

}

public override string ToString()

{

return "Customer";

}

//Передвигается рандомно,т.е. может появиться где-угодно

override public void Move()

{

int dx = Engine.random.Next(-Engine.maxPoint.x, Engine.maxPoint.x);

int dy = Engine.random.Next(-Engine.maxPoint.y, Engine.maxPoint.y);

if (position.x + dx >= 0 && position.x + dx < Engine.maxPoint.x)

position.x += dx;

if (position.y + dy >= 0 && position.y + dy < Engine.maxPoint.y)

position.y += dy;

}

}

}

Теперь все сущности описаны, и мы можем перейти непосредственно к программированию самого движка системы. В конструкторе мы создадим и разместим случайным образом все объекты нашей системы. В методе Run() мы будем перемещать объекты а методк Show() отображать их на экране.

using System;

using System.Collections.Generic;

using System.Text;

using System.Collections;

using System.Drawing;

namespace Task_2

{

[Developer("Vasya Petrov", "11.07.10","Класс моделирует JobTerrarium")]

class Engine

{

public readonly static Point maxPoint = new Point(10, 10);

public readonly static Random random = new Random();

ArrayList employee;

ArrayList work;

public Engine()

{

employee = new ArrayList();

employee.Add(new Worker(500));

employee.Add(new Worker(500));

employee.Add(new Worker(500));

employee.Add(new Worker(500));

employee.Add(new Worker(500));

employee.Add(new Boss(1000));

employee.Add(new Boss(1000));

employee.Add(new Customer());

employee.Add(new BigBoss("Dr. Evil", 10000));

work = new ArrayList();

work.Add(new Work());

work.Add(new Work());

work.Add(new Work());

work.Add(new Work());

foreach (DymanicMapObject emp in employee)

emp.Place(new Point(Engine.random.Next(Engine.maxPoint.x), Engine.random.Next(Engine.maxPoint.y)));

foreach (Work wrk in work)

wrk.Place(new Point(Engine.random.Next(Engine.maxPoint.x), Engine.random.Next(Engine.maxPoint.y)));

}

public void Run()

{

foreach (DymanicMapObject emp in employee)

{

emp.Move();

if (emp is Customer)

{

Work tmp=new Work();

tmp.Place(((Customer)emp).placeWork());

work.Add(tmp);

}

}

//Проверяем на столкновение живого объекта с живым

for (int i = 0; i < employee.Count; i++)

for (int j = 0; j < employee.Count; j++)

if (i != j && ((DymanicMapObject)employee[i]).Position == ((DymanicMapObject)employee[j]).Position)

{

((ITalk)employee[i]).Talk((DymanicMapObject)employee[j]);

if (employee[i] is IManage && employee[j] is IManagable) ((IManage)employee[i]).Manage(employee[j] as IManagable);

}

//Проверяем на столкновение живого объекта с неживым

for (int i = 0; i < employee.Count; i++)

for (int j = 0; j < work.Count; j++)

// Работа сделана, удаляем её из списка работ

if (!(employee[i] is Customer) && ((DymanicMapObject)employee[i]).Position == ((MapObject)work[j]).Position)

{

work.RemoveAt(j);

//((MapObject)work[j])).Place(new Point(Engine.random.Next(Engine.maxPoint.x), Engine.random.Next(Engine.maxPoint.y)));

}

}

public void Show(Graphics g, int cellSize)

{

foreach (MapObject emp in employee)

emp.Show(g, cellSize);

foreach (MapObject wrk in work)

wrk.Show(g, cellSize);

}

}

}

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

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Task_2;

namespace Task_2Winforms

{

public partial class JobTerrariumForm : Form

{

private const double workdayTime=15;

private int cellHeight;

private int cellWidth;

private System.Drawing.Pen redPen;

private Engine engine = new Engine();

private Graphics graph;

public JobTerrariumForm()

{

InitializeComponent();

cellHeight=pB_JobTerrarium.Height/Engine.maxPoint.y;

cellWidth=pB_JobTerrarium.Width/Engine.maxPoint.x;

pB_JobTerrarium.Image = new Bitmap(pB_JobTerrarium.Width, pB_JobTerrarium.Height);

graph = Graphics.FromImage(pB_JobTerrarium.Image);

}

private void button1_Click(object sender, EventArgs e)

{

DateTime time = DateTime.Now.AddSeconds(workdayTime);

do

{

graph.Clear(Color.White);

redPen = new Pen(Color.Red, 2);

int x = 0;

int y = 0;

for (int i = 0; i <= 10 - 1; i++)

{

x += cellWidth;

y += cellHeight;

graph.DrawLine(redPen, x, 0, x, this.Width);

graph.DrawLine(redPen, 0, y, this.Height, y);

}

TextBuffer.Clear();

engine.Run();

rTB_WhatToSay.AppendText(TextBuffer.ReadText());

rTB_WhatToSay.Update();

engine.Show(graph, cellWidth);

pB_JobTerrarium.Refresh();

pB_JobTerrarium.Update();

System.Threading.Thread.Sleep(1000);

} while (DateTime.Now < time);

}

private void timer1_Tick(object sender, EventArgs e)

{

// g.Clear(Color.White);

// TextBuffer.Clear();

// engine.Run();

// richTextBox1.AppendText(TextBuffer.ReadText());

// engine.Show(g, 40);

// pictureBox1.Refresh();

// pictureBox1.Update();

}

}

}

Как мы видим реализация системы JobTerrarium в соответствии с концепциями OOП с помощью абстрактных классов и функций, виртуальных функиций, перекрытия(override) функций оптимальнее и локаничнее.