учебники, программирование, основы, введение в,

 

Финальный проект

В этой заключительной лекции новый материал появляться не будет, не будет и традиционных вопросов в конце лекции. Лекция особенная - она посвящена описанию финального проекта, в котором объединены многие, надеюсь, уже хорошо знакомые элементы.
В финальном проекте создается семейство классов, описывающих геометрические фигуры. Проектирование начинается с абстрактного класса поведения, который описывает общие свойства и методы, характерные для всех фигур семейства. Затем, используя наследование, создаются классы конкретных геометрических фигур, начиная с простейших, таких, как круги и прямоугольники, до составных, таких, как класс Person. Мы добавим в наш проект динамические структуры данных, такие, как список с курсором, для хранения в нем фигур семейства. Наконец, мы создадим интерфейс, включающий меню с десятками команд и панель с инструментальными кнопками. Интерфейс позволяет конечному пользователю выполнять различные действия над геометрическими фигурами - создавать, рисовать их на форме, перемещать их с помощью команд и перетаскивать их мышью, менять их размеры и цвет, сохранять в списке и удалять из списка, отображать все фигуры списка или очистить его полностью.
Проект может служить образцом полноценного Windows-приложения, примером проектирования в классах с демонстрацией преимуществ, предоставляемых наследованием. Закончим на этом рекламную часть и приступим к делу. Хочу предупредить, вас ждут программные тексты, почти без всяких комментариев. Все нужные комментарии были даны в предыдущих лекциях. С моей точки зрения, наиболее интересная часть программистских книжек - это та, в которой приводится программный код. И значит, эта глава самая интересная.
Абстрактный класс Figure
Приведем код класса:
using System;
using System.Drawing;
namespace Shapes
{
/// <summary>
/// Figure - это абстрактный класс; прародитель семейства
/// классов геометрических фигур. Все фигуры имеют:
/// центр - center, масштаб - scale. статус
/// перетаскивания - dragged center - объект встроенного
/// класса (структуры) Point. Этот объект задает характерную
/// точку фигуры - чаще всего ее центр (тяжести)
/// scale задает масштаб фигуры, первоначально единичный.
/// drugged = true, когда фигура следует за курсором мыши.
/// над фигурами определены операции: параллельный
/// перенос - Move(a,b) масштабирование - Scale(s)
/// Показ фигуры - Show. Область захвата - Region_Capture
/// возвращает прямоугольник, характерный для фигуры,
/// перетаскивание фигуры возможно при установке курсора
/// мыши в области захвата.
/// </summary>
abstract public class Figure
{
/// <summary>
/// закрытые для клиентов атрибуты класса - center, scale
/// </summary>
protected Point center;
protected double scale ;
protected bool dragged;
protected Color color;
//Доступ к свойствам
public Point center_figure
{
get{return(center);}
get {center = value;}
}
public double scale_figure
{
get{return(scale);}
get {scale = value;}
}
public bool dragged_figure
{
get{return(dragged);}
get {dragged = value;}
}
public Color color_figure
{
get{return(color);}
set {color = value;}
}
/// <summary>
/// базовый конструктор фигур
/// </summary>
/// <param name="x">координата X характерной точки
///фигуры</param>
/// <param name="y">Координата Y характерной точки
///фигуры</param>
public Figure(int x, int y)
{
center = new Point(x,y);
scale = 1;
dragged = false;
color = Color.ForestGreen;
}
/// <summary>
/// отложенный метод
/// Параллельный перенос фигуры на (a,b)
/// require : true;
/// ensure : для любой точки фигуры p(x,y):
/// x = old(x) +a; y = old(y) + b;
/// </summary>
/// <param name="a">a - перемещение по горизонтали
///вправо </param>
/// <param name="b">b - перемещение по вертикали
///вниз</param>
/// Замечание: Для того, чтобы фигура при рисовании была 
/// полностью видимой, координаты всех ее точек должны 
/// быть в пределах области рисования.
public void Move (int a,int b)
{
center.X +=a; center.Y += b;
}
/// <summary>
/// изменяет масштаб фигуры
/// </summary>
/// <param name="s">масштаб изменяется в s раз</param>
public void Scale(double s)
{
scale*=s;
}          
/// <summary>
/// рисование фигуры в окне, передающем объекты g и pen
/// </summary>
/// <param name="g"> графический объект, методы которого
/// рисуют фигуру</param>
/// <param name="pen">перо рисования</param>
public abstract void Show(Graphics g, Pen pen,
Brush brush);
public abstract System.Drawing.Rectangle Region_Capture();              
}
Абстрактный класс, относящийся к этапу проектирования системы, вместе с тем является важнейшим элементом заключительного семейства классов. В этом проявляется мощь объектно-ориентированного подхода к разработке программных систем. Заметьте, на данном уровне большая часть текста представляет документацию, являющуюся неотъемлемой частью программного проекта. Документация записана в тегах <summary>, что позволяет автоматически ее извлечь и сохранить в виде XML-отчета.
Классы семейства геометрических фигур
Приведем теперь программные коды классов, являющихся потомками класса Figure.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifКласс Ellipse

Вот программный код этого класса:

using System;
using System.Drawing;
namespace Shapes
{
   /// <summary>
   /// Класс Ellipse - потомок класса Figure.
   /// </summary>
   public class Ellipse: Figure
   {
      int axisA,axisB;
      Rectangle rect;
      public Ellipse(int A, int B, int x, int y ): base(x,y)
      {
         axisA = A; axisB = B;
         rect =Init();
      }
      public override void Show(Graphics g, Pen pen, Brush brush)
      {                
         rect = Init();
         g.DrawEllipse(pen,rect);
         g.FillEllipse(brush, rect);
      }
      public override Rectangle Region_Capture()
      {
         rect = Init();
         return rect;
      }
      Rectangle Init()
      {
         int a  =Convert.ToInt32(axisA*scale);
         int b =Convert.ToInt32(axisB*scale);
         int leftupX = center.X - a;
         int leftupY = center.Y - b;
         return( new Rectangle(leftupX,leftupY,2*a,2*b));
      }
   }
}

Класс Circle

Этот класс является потомком класса Ellipse:

using System;
using System.Drawing;
namespace Shapes
{
   /// <summary>
   /// Класс Circle - потомок класса Ellipse.
   /// </summary>
   public class Circle: Ellipse
   {
      public Circle( int radius,int x, 
         int y):base(radius,radius,x,y)
      {
      //Круг - это эллипс с равными полуосями (радиусом круга)
      }
   }
}

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

Класс LittleCircle

Этот класс, задающие маленькие кружочки фиксированного радиуса, в свою очередь, является наследником класса Circle. Устроен он также чрезвычайно просто:

using System;
namespace Shapes
{
   /// <summary>
   /// Класс LittleCircle - потомок класса Circle.
   /// </summary>
   public class LittleCircle:Circle
   {
      public LittleCircle(int x,int y): base(4,x,y)
      {
         // маленький круг радиуса 4
      }
   }
}

Класс Rect

Этот класс является еще одним прямым потомком класса Figure:

using System;
using System.Drawing;
namespace Shapes
{
   /// <summary>
   /// Класс Rect - потомок класса Figure.
   /// </summary>
   public class Rect:Figure
   {
      int sideA, sideB;
      Rectangle rect;
      public Rect(int sideA,int sideB, int x, int y): base(x,y)
      {
         this.sideA = sideA; this.sideB = sideB;
         rect =Init();
      }
      public override void Show(Graphics g, Pen pen, Brush brush)
      {                
         rect = Init();
         g.DrawRectangle(pen,rect);
         g.FillRectangle(brush,rect);
      }
      public override Rectangle Region_Capture()
      {
         rect = Init();
         return rect;
      }
      Rectangle Init()
      {
         int a  =Convert.ToInt32(sideA*scale);
         int b =Convert.ToInt32(sideB*scale);
         int leftupX = center.X - a/2;
         int leftupY = center.Y - b/2;
         return( new Rectangle(leftupX,leftupY,a,b));
      }
   }
}

Класс Square

Квадрат - это частный случай прямоугольника. Соответствующий класс является потомком класса Rect:

using System;
namespace Shapes
{
   /// <summary>
   /// Класс Square - потомок класса Rect.
   /// </summary>
   public class Square:Rect
   {
      public Square(int side, int x, int y): base(side,side,x,y)
      {
         //квадрат - это прямоугольник с равными сторонами
      }
   }
}

http://localhost:3232/img/empty.gifКласс Person

Этот класс является прямым потомком класса Figure. Вместе с тем, класс является клиентом трех других классов семейства - Circle, Rect и LittleCircle, поскольку элементы фигуры, составляющие человечка, являются объектами этих классов%

namespace Shapes
{
   /// <summary>
   /// Класс Person - потомок класса Figure, 
   /// клиент классов Circle, Rect, LittleCircle.
   /// </summary>
   public class Person:Figure
   {
      int head_h;
      Circle head;
      Rect body;
      LittleCircle nose;
      public Person(int head_h, int x, int y): base(x,y)
      {
         //head_h - радиус головы, x,y - ее центр.
         //остальные размеры исчисляются относительно 
         //размера головы.
         this.head_h = head_h;
         head = new Circle(head_h,x,y);
         int body_x = x;
         int body_y = y + 3*head_h;
         int body_w =2*head_h;
         int body_h = 4*head_h;
         body = new Rect(body_w, body_h, body_x,body_y);
         nose = new LittleCircle(x+head_h +2, y);
      }
      public override void Show(System.Drawing.Graphics g, 
         System.Drawing.Pen pen, System.Drawing.Brush brush)
      {
         int h = Convert.ToInt32(head_h*scale);
         //head
         int top_x = center.X - h;
         int top_y = center.Y - h;
         g.DrawEllipse(pen, top_x,top_y, 2*h,2*h);
         g.FillEllipse(brush, top_x,top_y, 2*h,2*h);
         //body
         top_y += 2*h;
         g.DrawRectangle(pen, top_x,top_y, 2*h,4*h);
         g.FillRectangle(brush, top_x,top_y, 2*h,4*h);
         //nose
         top_y -=h;
         top_x += 2*h;
         g.DrawEllipse(pen, top_x,top_y, 8,8);
         g.FillEllipse(brush, top_x,top_y, 8,8);
      }
      public override System.Drawing.Rectangle 
         Region_Capture()
      {
         int h = Convert.ToInt32(head_h*scale);
         int top_x = center.X - h;
         int top_y = center.Y - h;
         return new 
            System.Drawing.Rectangle(top_x,top_y,2*h,2*h);
      }
   }
}

Список с курсором. Динамические структуры данных

Добавим в проект классы, задающие динамические структуры данных. Конечно, можно было бы воспользоваться стандартными... Но для обучения крайне полезно уметь создавать собственные классы, задающие такие структуры данных. Список с курсором - один из важнейших образцов подобных классов%:

using System;
namespace Shapes
{
   /// <summary>
   /// Класс TwoWayList(G)  описывает двусвязный список с 
   /// курсором. Элементами списка являются объекты 
   /// TwoLinkable, хранящие, помимо указателей на двух 
   /// преемников, объекты типа G.Курсор будет определять    /// текущий (активный) элемент списка. Класс будет
   /// определять симметричные операции по отношению к 
   /// курсору.
   /// Конструкторы: 
   /// Конструктор без параметров будет создавать пустой 
   /// список
   /// Запросы:
   /// empty: require: true; возвращает true для 
   /// непустого списка item: require: not empty();
   /// возвращает активный элемент типа G; count:
   /// require: true; возвращает число элементов списка; 
   /// count in[0,n]
   /// (count == 0) eqviv empty(); 
   /// index: require: not empty(); возвращает индекс  
   /// активного элемента.
   /// search_res: require: true; возвращает true, 
   /// если последний поиск был успешным.
   /// Команды:
   /// put_left(elem): require: true; 
   /// ensure: добавить новый элемент (elem) слева от курсора;
   /// put_right(elem): require: true; 
   /// ensure: добавить новый элемент (elem) справа от 
   /// курсора;
   /// remove: require: not empty(); 
   /// ensure: удалить активный элемент;
   /// особо обрабатывается удаление последнего и 
   /// единственного элементов
   /// операции с курсором:
   /// start: require: true; 
   /// ensure: сделать активным первый элемент;
   /// finish: require: true; 
   /// ensure: сделать активным последний элемент; 
   /// go_prev: require: not (index = 1); 
   /// ensure: сделать активным предыдущий элемент;
   /// go_next: require: not (index = count); 
   /// ensure: сделать активным последующий элемент;
   /// go_i(i): require:  (i in [1, count]); 
   /// ensure: сделать активным элемент с индексом i;
   /// операции поиска:
   /// search_prev(elem): require: not (index = 1); 
   /// ensure: сделать активным первый элемент elem 
   /// слева от курсора;
   /// Успех или неуспех поиска сохранять в булевской 
   /// переменной search_res
   /// search_next: require: not (index = count); 
   ///  ensure: сделать активным первый элемент elem 
   /// справа от курсора;
   /// успех или неуспех поиска сохранять в булевской 
   /// переменной search_res
   /// </summary>
   public class TwoWayList
   {
      public TwoWayList()
      {
         first = cursor = last = null;
         count = index = 0;
         search_res = false;
      }//конструктор
      /// <summary>
      /// first, cursor, last - ссылки на первый,
      /// активный и последний элементы списка
      /// Запросы count, index search_res также 
      /// реализуются атрибутами.
      /// Запросы empty, item реализуются функциями 
      /// </summary>
      protected TwoLinkable first, cursor, last;
      protected int count, index;
      protected bool search_res;
      //доступ на чтение к закрытым свойствам;
      public int Count
      {
         get
         {
            return(count);
         }
      }
      public int Index
      {
         get
         {
            return(index);
         }
      }
      public bool Search_res
      {
         get
         {
            return(search_res);
         }
      }
      /// <summary>
      /// require: true; возвращает true для непустого списка 
      /// </summary>
      /// <returns></returns>
      public bool empty()
      {
         return(first == null);
      }//empty
      /// <summary>
      /// require: not empty(); возвращает активный 
      /// элемент типа G;
      /// </summary>
      /// <returns></returns>
      public Figure item()
      {
         return(cursor.Item);
      }//item
      /// <summary>
      /// require: true; 
      /// ensure: добавить новый элемент (elem) слева 
      /// от курсора;
      /// </summary>
      /// <param name="elem">Тип Figure играет роль  
      /// родового типа G
      /// хранимого элемента elem</param>
      public void put_left(Figure elem)
      {
         TwoLinkable newitem = new TwoLinkable();
         newitem.Item = elem;
         newitem.Next = cursor;
         if (empty())  //список пуст 
         {
            first = cursor = last = newitem;
            index =1; count = 1;
         }
         else 
         {
            if (index == 1) 
               first =newitem;
            else
               cursor.Prev.Next = newitem;
            newitem.Prev = cursor.Prev; cursor.Prev = newitem;
            count++; index++;
         }
      }//put_right
      /// <summary>
      /// require: true; 
      /// ensure: добавить новый элемент (elem) справа 
      /// от курсора;
      /// </summary>
      /// <param name="elem">Тип Figure играет роль  
      /// родового типа G
      /// хранимого элемента elem</param>
      public void put_right(Figure elem)
      {
         TwoLinkable newitem = new TwoLinkable();
         newitem.Item = elem;
         newitem.Prev = cursor;
         if (empty())  //список пуст 
         {
            first = cursor = last = newitem;
            index =1; count = 1;
         }
         else
         {
            if (index == count) 
               last =newitem;
            else
               cursor.Next.Prev = newitem;
            
            newitem.Next = cursor.Next; cursor.Next = newitem;
            count++; 
         }
      }//put_right
      public void remove()
      {
         if(count == 1)
         {
            first = last = cursor = null;
            index=0;
         }
         else if(index==1)
         {
            first = cursor.Next;
            cursor.Prev = null;
            cursor = cursor.Next;
         }
         else if(index == count)
         {
            last = cursor.Prev;
            cursor.Next = null;
            cursor = cursor.Prev;
            index--;
         }
         else
         {
            cursor.Prev.Next = cursor.Next;
            cursor.Next.Prev = cursor.Prev;
            cursor = cursor.Next;
         }
         count--;
      }//remove
      /// операции с курсором:
      /// <summary>
      /// start: require: true; 
      /// ensure: сделать активным первый элемент;
      /// </summary>
      public void start()
      {
         cursor = first; index = 1;
      }//start
      /// <summary>
      /// finish: require: true; 
      /// ensure: сделать активным последний элемент; 
      /// </summary>
      public void finish()
      {
         cursor = last; index = count;
      }//finish
      /// <summary>
      /// go_prev: require: not (index = 1); 
      /// ensure: сделать активным предыдущий элемент;
      /// </summary>
      public void go_prev()
      {
         cursor = cursor.Prev; index--;
      }// go_prev
      /// <summary>
      /// go_next: require: not (index = count); 
      /// ensure: сделать активным последующий элемент;
      /// </summary>
      public void go_next()
      {
         cursor = cursor.Next; index++;
      }// go_next
      /// <summary>
      /// go_i(i): require:  (i in [1, count]); 
      /// ensure: сделать активным элемент с индексом i;
      /// </summary>
      /// <param name="i"></param>
      public void go_i(int i)
      {
         if(i >index)
            while (i>index)
            {
               cursor = cursor.Next; index++;
            }
         else if(i<index)
            while (i<index)
            {
               cursor = cursor.Prev; index--;
            }
      }// go_i
      /// операции поиска:
      /// <summary>
      /// search_prev(elem): require: not (index = 1); 
      /// ensure: сделать активным первый элемент elem 
      /// слева от курсора;
      /// </summary>
      /// <param name="elem">искомый элемент</param>
      public virtual void search_prev(Figure elem)
      {
         bool found = false;
         while (!found && (index !=1))
         {
            cursor = cursor.Prev; index--;
            found = (elem == item());
         }
         search_res = found;
      }// search_prev
   /// <summary>
   /// успех или неуспех поиска сохранять в булевской 
   /// переменной search_res
   /// search_next: require: not (index = count); 
   /// ensure: сделать активным первый элемент elem 
   /// справа от курсора;
   /// успех или неуспех поиска сохранять в булевской 
   /// переменной search_res
   /// </summary>
   /// <param name="elem"></param>
      public virtual void search_next(Figure elem)
      {
         bool found = false;
         while (!found && (index !=count))
         {
            cursor = cursor.Next; index++;
            found = (elem == item());
         }
         search_res = found;
      }//search_next   
   }
}

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

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifКлассы элементов списка

Рассмотрим классы, описывающие элементы списков - элементы с одним и с двумя указателями:

using System;
namespace Shapes
{
   /// <summary>
   /// Класс Linkable(T)задает элементы списка,включающие:
   /// информационное поле типа T - item 
   /// ссылку на элемент типа Linkable - next
   /// Функции:
   /// конструктор new: -> Linkable
   /// запросы: 
   /// Get_Item: Linkable -> T
   /// Get_Next: Linkable -> Linkable 
   /// процедуры:
   /// Set_Item: Linkable*T -> Linkable
   /// Set_Next: Linkable*Linkable -> Linkable 
   /// Роль типа T играет Figure
   /// </summary>
   public class Linkable
   {
      public Linkable()
      {
         item =null; next = null;
      }
      /// <summary>
      /// закрытые атрибуты класса 
      /// </summary>
      Figure item;
      Linkable next;
      /// <summary>
      /// процедуры свойства для доступа к полям класса
      /// </summary>
      public Figure Item{
      get{
         return(item);
         }
      set{
         item = value;
         }
      }
      public Linkable Next{
      get{
         return(next);
         }
      set{
         next = value;
         }
      }
   }//class Linkable
   /// <summary>
   /// Класс TwoLinkable задает элементы с двумя ссылками 
   /// </summary>
   public class TwoLinkable 
   {
      
      public TwoLinkable()
      {
         prev = next = null;
      }
      /// <summary>
      /// закрытые атрибуты класса 
      /// </summary>
      TwoLinkable prev, next;
      Figure item;
      /// <summary>
      /// процедуры свойства для доступа к полям класса
      /// </summary>
      public Figure Item
      {
         get
         {
            return(item);
         }
         set
         {
            item = value;
         }
      }
      public TwoLinkable Next
      {
         get
         {
            return(next);
         }
         set
         {
            next = value;
         }
      }
      public TwoLinkable Prev
      {
         get
         {
            return(prev);
         }
         set
         {
            prev = value;
         }
      }
   }//class TwoLinkable
}
 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика