.Net Code Monkey RSS 2.0
 Friday, April 11, 2008

Back to Part 2

Review Of Progress So Far

So, to date we have created an interface for our ShapeFactory and any other shape factory to implement, an enumeration that represents all the available shapes we may wish to build, and a factory class that can build any of our two current shapes. The next step is to create a graphical user interface that will call the factory and receive a couple of shape objects for it's trouble.

The Client Gets What They Deserve

Next we need a client to display the shapes, so for simplicity we are going to create a small Windows Forms project called WindowsApplication and add it to the solution. The project will have a single form called Main (700x550) which will have a picture box (uxPictureBox) and a button, uxDrawButton.

We now need to add a handler for the Draw Button and declare a Bitmap and a Graphics object at the top of the class. In the form constructor we will initialise the bitmap to a new instance which will be the size of the PictureBox. We will then create the Graphics object from the Bitmap.

In the button handler method we will clear the graphics object to a white background.

public partial class Main : Form
 {
     private Bitmap _bitmap;
     private Graphics _graphics;

     public Main()
     {
         InitializeComponent();

         _bitmap = new Bitmap(uxPictureBox.Width, uxPictureBox.Height);
         _graphics = Graphics.FromImage(_bitmap);
         uxPictureBox.Image = _bitmap;
     }

     private void uxDrawButton_Click(object sender, EventArgs e)
     {
         _graphics.Clear(Color.White);
     }
 }

We now need a collection to hold our shapes, so we will declare a list of objects that implement IShape at the top of the class, and to do this we will need to provide a reference to the ShapeInterfaces project. At the same time add a reference to the Factories project and import the namespaces for both projects at the top of the form code.

using System.Text;
using System.Windows.Forms;
using Playground.ShapeMaker.Factories;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.WindowsApplication
{
    public partial class Main : Form
    {
        private Bitmap _bitmap;
        private Graphics _graphics;
        private List<IShape> _shapes;

        public Main()
        {

Next we need to add two method stubs that are void of return values, AddShapes() and DrawShapes(). Surprisingly, AddShapes() will take responsibility for adding new shapes to our list and DrawShapes() will draw those shapes. We will add a line to create an instance of our list in the constructor and below that we will place a call to the AddShapes() method. We will place a call in the button handler to the DrawShapes() method, below the line that clears the graphics to a white background. Follow this with a refresh of the PictureBox.

public Main()
 {
     InitializeComponent();
     _bitmap = new Bitmap(uxPictureBox.Width, uxPictureBox.Height);
     _graphics = Graphics.FromImage(_bitmap);
     uxPictureBox.Image = _bitmap;

     _shapes = new List<IShape>();
     AddShapes();
 }

 /// <summary>
 /// Handles the draw button pressed event.
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void uxDrawButton_Click(object sender, EventArgs e)
 {
     _graphics.Clear(Color.White);
     DrawShapes();
uxPictureBox.Refresh(); }

In the AddShapes() method we need to create a new instance of our ShapeFactory object. Then using the ShapeFactory object add two shapes, a circle and a rectangle to the list.

 /// <summary>
 /// Adds shapes to the list
 /// </summary>
 private void AddShapes()
 {
     ShapeFactory shapeFactory = new ShapeFactory();
     _shapes.Add(shapeFactory.CreateShape(Shape.Circle));
     _shapes.Add(shapeFactory.CreateShape(Shape.Rectangle));
 }

Because we know that the shapes Implement the IShape interface we have a contract that says we can call the Draw() methods on any of these shapes. These objects will then take the responsibility for drawing them selves. So in the DrawShapes() method all we need to do is iterate through all the shapes in the list and call each shapes Draw() method.

 /// <summary>
 /// Draws the shapes in the list
 /// </summary>
 private void DrawShapes()
 {
     foreach(IShape shape in _shapes)
     {
         shape.Draw();
     }
 }

And that would be it... apart from we haven't added any working code into the Draw() methods of each concrete object yet!

A Change In The Contract

Apart from the fact there is no code in the concrete objects to actually draw them selves, I have just noticed the need to be able to pass the Graphics object into each shape object, so we need to go back to the interfaces and change the signature for the Draw() method.

So if we take a look in the IShape interface we need to be able to pass in a Graphics object as a method parameter.

public interface IShape
 {
     /// <summary>
     /// Represents the center of the shape
     /// </summary>
     Point Center { get; set;}

     /// <summary>
     /// The method used to draw the shape
     /// </summary>
     /// <param name="graphics">A reference to the Graphics object to draw the shape on.</param>
     void Draw(Graphics graphics);
 }

And while we are in this project I think we will take the opportunity to extend the CreateShape() method signature in the IShapeFactory interface, too. We are going to add a center point parameter; this will allow us to position the shape where we like.

public interface IShapeFactory
 {
     /// <summary>
     /// Use this method to create a shape.
     /// </summary>
     /// <param name="shape">Indicates the type of shape to be made.</param>
     /// <param name="center">Indicates the center point of the shape.</param>
     /// <returns>An object that implements the IShape interface.</returns>
     IShape CreateShape(Shape shape, Point center);
 }

A quick build of the project will highlight where our change in interface has broken the contract between it and the objects that implement it so in the BaseShape object we need to add a Graphics parameter into the Draw() method. This will be the same in the Rectangle and the Circle class.

public abstract void Draw(Graphics graphics);

In the ShapeFactory class we need to extend the CreateShape(), CreateCircle() and CreateRectangle() methods signatures to include the center point parameter. Then in the CreateShape() method we need to pass the center point parameter on to the worker method calls.

/// <returns>A shape object that implements IShape.</returns>
public IShape CreateShape(Shape shape, Point center)
{

In the two worker methods, CreateCircle() and CreateRectangle() we will populate the object's Center property with the value from the center parameter passed in.

 /// <summary>
 /// This method creates a circle object.
 /// </summary>
 /// <returns>A shape object that implements ICircle.</returns>
 private ICircle CreateCircle(Point center)
 {
     Circle circle = new Circle();
     circle.Center = center;
     circle.Diameter = 50;
     return (ICircle)circle;
 }

 /// <summary>
 /// This method creates a rectangle object.
 /// </summary>
 /// <returns>A shape object that implements IRectangle.</returns>
 private IRectangle CreateRectangle(Point center)
 {
     BusinessEntity.Rectangle rectangle = 
         new BusinessEntity.Rectangle();
     rectangle.Center = center;
     rectangle.Size = new Size(400, 200);
     return (IRectangle)rectangle;
 }

Then back in the form code we need to alter the calls to the ShapeFactory CreateShape() methods to include a center position

 /// <summary>
 /// Adds shapes to the list
 /// </summary>
 private void AddShapes()
 {
     ShapeFactory shapeFactory = new ShapeFactory();
     _shapes.Add(shapeFactory.CreateShape(Shape.Circle, new Point(250,250)));
     _shapes.Add(shapeFactory.CreateShape(Shape.Rectangle, new Point(350, 200)));
 }

 /// <summary>
 /// Draws the shapes in the list
 /// </summary>
 private void DrawShapes()
 {
     foreach(IShape shape in _shapes)
     {
         shape.Draw(_graphics);
     }
 }

Back to the Drawing Board

So finally we can get back into the concrete shape classes and write the code that will draw the shape.

So let us look at the Circle's Draw() method first. We'll add the code to allow the Circle to draw it's self.

 /// <summary>
 /// Use this method to draw the shape
 /// </summary>
 public override void Draw(Graphics graphics)
 {
     Pen pen = new Pen(Color.DarkGray, 0);
     int left = (int)Center.X - (Diameter/2);
     int top = (int)Center.Y - (Diameter/2);

     System.Drawing.Rectangle outlineRectangle =
         new System.Drawing.Rectangle(left, top, Diameter, Diameter);
     graphics.DrawEllipse(pen, outlineRectangle);
 }

And the same for the Rectangle.

 /// <summary>
 /// Use this method to draw the shape
 /// </summary>
 public override void Draw(Graphics graphics)
 {
     Pen pen = new Pen(Color.Red, 0);
     int left = (int)Center.X - (Size.Width / 2);
     int top = (int)Center.Y - (Size.Height / 2);

     System.Drawing.Rectangle outlineRectangle =
         new System.Drawing.Rectangle(left, top, Size.Width, Size.Height);
     graphics.DrawRectangle(pen, outlineRectangle);
 }

Now we need to make sure we have set the start up project to be the windows application and we should be good to go!

Conclusions

Now I'm certain that the application architecture and the code may not be laid out to best practises, and I'm aware there are plenty of parts that could have been better thought out and implemented, but really I just wanted to get a simple factory based application down on paper, so to speak, to get it a little clearer in my mind.

If any of these articles have helped you or planted the seed of inquisitiveness in your mind to find out more about factories then that can only be a good thing. If there is anything I have done in this article that is quite clearly incorrect or bad practise, please feel free to leave a comment. Any constructive feed back is appreciated.

The next step will be to make the objects prinatable. But until then, have fun!

Part 1 | Part 2

2008-04-11_ShapeMaker.zip (103.25 KB) <- Solution files all zipped up.
Friday, April 11, 2008 8:44:42 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | Classes | Factory Pattern | Inheritance | Interfaces
 Thursday, April 10, 2008

Back to Part 1

Review Of Progress So Far

So far we have two concrete shape classes, Circle and Rectangle that derive from a base shape class. The base shape class implements the IShape interface and the concrete classes implement in turn the IShape interface as well as their respective shape interfaces. The next step is to create a factory class to create our shapes.

Working In The Factory

So, we need to add to the solution, another new class library project which we will call Factories. This will house our ShapeFactory class. The ShapeFactory class will be responsible for creating our shape objects. It will be able to create and return an object that implements the IShape interface. So far we have ensured that both our circle and rectangle inherently implement the IShape interface for two reasons. One, because they both derive from the BaseShape object and we know that implements IShape, and two, because they implement ICircle and IRectangle which in turn implements IShape, as well. (Maybe some one with a little more experience than I, could advise if this is an acceptable practise. -> I can't see why not!)

But before we create our ShapeFactory we have one more thing to do. Because we may want to use more than one factory in the future to create our shapes we are first going to create another interface, which will describe what a ShapeFactory need to create some shape objects. This will be the IShapeFactory interface, and we are going to add that for the sake of simplicity to our ShapeInterfaces project

using System;

namespace Playground.ShapeMaker.ShapeInterfaces
{
    public interface IShapeFactory
    {
        /// <summary>
        /// Use this method to create a shape.
        /// </summary>
        /// <returns>An object that implements the IShape interface.</returns>
        IShape CreateShape();
    }
}

As you can see we have determined that any factory that implements IShapeFactory will have to contain a method that returns an object implementing IShape. The problem is, how do we determine what sort of shape we want to get? Well even though we only have two shapes at the moment for future expansion we will need an enumeration to represent these. As enumeration is to be used in every class that implements IShapeFactory and will need to be a parameter in the CreateShape method, it seems logical to me to place the enumeration in the ShapeInterfaces project. If any one can advise whether this is good practise or not, please do! So, let us add the Shape enumeration.

using System;

namespace Playground.ShapeMaker.ShapeInterfaces
{
    /// <summary>
    /// Represents the types of shapes that can be made
    /// </summary>
    public enum Shape
    {
        /// <summary>
        /// Indicates the shape is to be a circle
        /// </summary>
        Circle,

        /// <summary>
        /// Indicates the shape is to be a rectangle
        /// </summary>
        Rectangle
    }
}
We can now modify the CreateShape method of the IShapeFactory interface to accept a Shape enumerated value.
public interface IShapeFactory
 {
     /// <summary>
     /// Use this method to create a shape.
     /// </summary>
     /// <param name="shape">Indicates the type of shape to be made.</param>
     /// <returns>An object that implements the IShape interface.</returns>
     IShape CreateShape(Shape shape);
 }

So it's back to the Factories project now to create the ShapeFactory class that will provide us with some shapes. To create some shapes we need to add a reference to both the BusinessEntity and the ShapeInterfaces projects into the Factories project. After adding our new ShapeFactory class into the Factories project we need to import the BusinessEntity and the ShapeInterfaces namespaces into the top of it. We now need to make the ShapeFactory class implement IShapeFactory.

using System;
using Playground.ShapeMaker.BusinessEntity;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.Factories
{
    public class ShapeFactory : IShapeFactory
    {

        #region IShapeFactory Members

        public IShape CreateShape(Shape shape)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }
}

We are now going to create a pair of private methods that will create our two shapes. Although these methods will create two concrete objects the methods will return not the objects but their interfaces. First though we will just create the method stubs. Then we will modify the CreateShape() method to call one or the other of these two methods depending upon the enumerated value passed in.

#region IShapeFactory Members

 /// <summary>
 /// Creates a shape and returns it.
 /// </summary>
 /// <param name="shape">Indicates the shape to create.</param>
 /// <returns>A shape object that implements IShape.</returns>
 public IShape CreateShape(Shape shape)
 {
     IShape createdShape = null;
     switch (shape)
     {
         case Shape.Circle:
             createdShape = CreateCircle();
             break;

         case Shape.Rectangle:
             createdShape = CreateRectangle();
             break;

         default:
             throw new Exception("Unknown shape encountered in CreateShape() method.");
     }
     return createdShape;
 }

 #endregion

 /// <summary>
 /// This method creates a circle object.
 /// </summary>
 /// <returns>A shape object that implements ICircle.</returns>
 private ICircle CreateCircle()
 {

 }

 /// <summary>
 /// This method creates a rectangle object.
 /// </summary>
 /// <returns>A shape object that implements IRectangle.</returns>
 private IRectangle CreateRectangle()
 {

 }

We have used switch / case to select which method will create our shape object. Notice that we use the default case to throw an exception if we encounter a shape that we haven't catered for. This is in case at any time the shape enumeration is extended to provide more shapes. Notice also that although the CreateShape() method returns an object that implements IShape, the two worker methods return ICircle and IRectangle. This works because both ICircle and IRectangle both implement the IShape interface, and that is what CreateShape() will return.

Anyway let us crack on and 'flesh out' the CreateCircle() worker method! Because our shapes using the System.Drawing namespace, our factory that is going to create these will need to also. So we need to add a reference to and import that namespace for the ShapeFactory. (Incidentally have you noticed the more you look at the word "circle" the more wrong it looks!)

 /// <summary>
 /// This method creates a circle object.
 /// </summary>
 /// <returns>A shape object that implements ICircle.</returns>
 private ICircle CreateCircle()
 {
     Circle circle = new Circle();
     circle.Center = new Point(250,250);
     circle.Diameter = 50;
     return (ICircle)circle;
 }

Now I would normally use constructor in my code, but this example isn't to show best practise of constructing objects, so I am going to construct my objects using the properties today. You will probably also notice that I am casting the newly constructed Circle object to the type of ICircle before returning it. You don't need to do this but I believe it is good practise. (Can any one confirm this please?)

At this point I have just realised an error with choosing a rectangle for a shape, due to a conflict with the System.Drawing.Rectangle object. Well we could change the shape to a square or a triangle.... but we have got this far, so I am going to stick with it. It will however mean there will be some ugly code with the BusinessEntity namespace forced in here and there!!!

 /// <summary>
 /// This method creates a rectangle object.
 /// </summary>
 /// <returns>A shape object that implements IRectangle.</returns>
 private IRectangle CreateRectangle()
 {
     BusinessEntity.Rectangle rectangle = 
         new BusinessEntity.Rectangle();
     rectangle.Center = new Point(300, 300);
     rectangle.Size = new Size(400, 200);
     return (IRectangle)rectangle;
 }

Now that should be it for our ShapeFactory for the moment. If all has gone well for you then your project should build without errors.

To sum up we have created an interface for this and any other shape factory to implement, an enumeration that represents all the available shapes we may wish to build, and a factory class that can build any of our two current shapes. the next step is to create a graphical user interface that will call the factory and receive a couple of shape objects for it's trouble.

Part 3

Thursday, April 10, 2008 6:28:13 PM (GMT Standard Time, UTC+00:00)  #    Comments [2] -
.Net | C# | Classes | Factory Pattern | Inheritance | Interfaces

Background

Recently I have been reading quite a bit about Factory Patterns and Inversion of Control (IOC). However like most principles that are new to me I often find them very hard to visualise at first, and even harder to put them into practise. So I am going to try a simple application using what I believe to be a Factory Pattern. The aim of the game will be to use a factory to produce shapes that will be drawn using .Net's GDI classes in a Windows form application.

This article assumes that the reader is already comfortable with the use of interfaces and inheritance in classes.

The files created in this article can be downloaded from here: 2008-04-11_ShapeMaker.zip (103.25 KB) 

Interfaces

The first thing I'm going to do is to create a Blank Solution, using Visual Studio 2005, which I am going to call ShapeMaker. To this solution I am going to add a New Project which will be a Visual C# Class Library which I am going to call ShapeInterfaces.

I'll close and delete the default Class1.cs class file before I proceed. I shall now add a new Interface called IShape and remove the two unused using statements. To the IShape interface I am going to add a System.Drawing.Point property called Center and import it's namespace after first adding a reference to the System.Drawing assembly. I shall then add a method that is void of a return type and call it Draw().

using System;
using System.Drawing;

namespace Playground.ShapeMaker.ShapeInterfaces
{
    public interface IShape
    {
        /// <summary>
        /// Represents the center of the shape
        /// </summary>
        Point Center { get; set;}

        /// <summary>
        /// The method used to draw the shape
        /// </summary>
        void Draw();
    }
}

All shapes that I intend to create will implement this interface. The Center property will define the center point of the shape, and the Draw() method will allow any user interface to know that when it is required to draw the shape it can call this method and the shape will be responsible for drawing it self.

I'm now going to add two more interfaces which will both implement the IShape interface, the ICircle which will also have a integer Diameter property...

using System;
using System.Drawing;

namespace Playground.ShapeMaker.ShapeInterfaces
{
    public interface ICircle : IShape
    {
        /// <summary>
        /// Represents the diameter of the circle
        /// </summary>
        int Diameter { get; set; }
    }
}

...and the IRectangle which will have a Size property.

using System;
using System.Drawing;

namespace Playground.ShapeMaker.ShapeInterfaces
{
    public interface IRectangle : IShape
    {
        /// <summary>
        /// Represents the size of the rectangle
        /// </summary>
        Size Size { get; set;}
    }
}

Base Classes

I'm now going to add another class library project to the solution which will be my BusinessEntity layer, from which I will also delete the default Class1 class. To the BusinessEntity project I am going to add a folder called BaseClasses. I would probably normally use a separate project for my base classes but for clarity of this example I shall place them in this folder.

To the BaseClassses folder I am going to add a class that all my shape entities will inherit from. I shall call this BaseShape. As I intend for BaseShape to implement the interface IShape I need to add a reference to the ShapeInterfaces project from the BusinessEntity project, and then import the namespace into the BaseShape class.

using System;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.BusinessEntity.BaseClasses
{
    public abstract class BaseShape : IShape
    {

    }
}

We have declared the class as abstract as we do not want this class to be able to be constructed. It will just be used a s a base for other classes to derive (inherit) from. Right clicking on the interface name and selecting Implement Interface from the context menu...

... should inject the following code into the class.

#region IShape Members

 public System.Drawing.Point Center
 {
     get
     {
         throw new Exception("The method or operation is not implemented.");
     }
     set
     {
         throw new Exception("The method or operation is not implemented.");
     }
 }

 public void Draw()
 {
     throw new Exception("The method or operation is not implemented.");
 }

 #endregion

We will now need to add a reference to the System.Drawing assembly and import the name space before we can build or we will receive an error. The next task to do is to declare a private field, _center, which will hold our center point data and then we will modify the Center property to access that field. Then we will declare the draw method as abstract so it must be overridden by any derived class.

        private Point _center;

        #region IShape Members

        /// <summary>
        /// Gets or sets the center point of the shape.
        /// </summary>
        public Point Center
        {
            get { return _center; }
            set { _center = value; }
        }

        /// <summary>
        /// All inherited classes will contain this method.
        /// The method will be used to draw it's self.
        /// </summary>
        public abstract void Draw();

        #endregion

Derived classes

Next up is to create two shape classes that will derive from the base class. The first shape we will create will be a Circle. We will add it to the root of the BusinessEntity project, and make it both derive from the BaseShape class and implement the ICircle interface. To make this class derive correctly from the BaseShape class we will need to declare a Draw() method and mark it as an override.

using System;
using Playground.ShapeMaker.BusinessEntity.BaseClasses;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.BusinessEntity
{
    public class Circle : BaseShape, ICircle
    {

        #region ICircle Members

        public int Diameter
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        #endregion

        /// <summary>
        /// Use this method to draw the shape
        /// </summary>
        public override void Draw()
        {
        }
    }
}

We can now create a private field _diameter and set the Diameter property to access it fully. The next shape we will add will be the Rectangle, which we will do in much the same way, except that we will create a field to hold the size data and access it from the Size property.

using System;
using System.Drawing;
using Playground.ShapeMaker.BusinessEntity.BaseClasses;
using Playground.ShapeMaker.ShapeInterfaces;

namespace Playground.ShapeMaker.BusinessEntity
{
    public class Rectangle : BaseShape, IRectangle
    {
        private Size _size;

        #region IRectangle Members

        /// <summary>
        /// Gets or sets the size of the rectangle.
        /// </summary>
        public Size Size
        {
            get { return _size;  }
            set { _size = value; }
        }

        #endregion

        /// <summary>
        /// Use this method to draw the shape
        /// </summary>
        public override void Draw()
        {
        }
    }
}

Summing up so far...

So far we have two concrete shape classes, Circle and Rectangle that derive from a base shape class. The base shape class implements the IShape interface and the concrete classes implement in turn the IShape interface as well as their respective shape interfaces.

Part 2 will show the next step, which is to build a factory class which will be used to create and return our shape objects at runtime.

Thursday, April 10, 2008 4:03:42 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
.Net | C# | Classes | Factory Pattern | Inheritance | Interfaces
 Wednesday, April 09, 2008

Outline

Background

You may wonder why write an article on classes in VBScript when there are hundreds of articles already on the web about classes? Well, it's because when I first tried to learn about classes I found it hard to visualise and to "get my head around" the concept of using classes let alone finding basic articles showing how to use them within ASP VBScript.

What I hope to acheive with this article is to provide the reader with a basic understanding of classes in a manner that a complete novice programmer, like my self at the time, would find useful and easy to follow. I also hope to provide a first insight into a more object-orientated programming (OOP) approach in VBScript.

Introduction

So what is a class? A class is basically the coding framework we need to enable an object to be created at runtime. A class it's self does not represent any thing, but the object it lets us create can represent any element or entity that we choose, from a real world object like a car, to a virtual object like an XmlCreator for creating XML data files. A class also allows us to encapsulate any code directly relating to the object we wish to create, and abstract this code out of the "presentation layer" (the page HTML).

In our example we are going to create a class which when instantiated will create an object that will represent a car. The code inside the class is split into two main areas; Properties and Methods. A property is a thing that is owned and in a class we treat one exactly as we would a variable. A method is a way of carrying out an action, possibly on values held in the properties, and you will be familiar with these as Functions or Sub Procedures.

In our car example some properties we may have are:

  • Number of wheels
  • Colour of paint
  • Is engine running?
  • Is clutch engaged?
  • Current selected gear

Some of the methods we may have are:

  • Start engine
  • Stop engine
  • Depress Clutch
  • Release Clutch
  • Select gear

So now we have a basic outline of what a class is let's create one!

Creating our first class

Open your favourite text editor / developement tool, create a new file, save it as classCar.asp and enter the following two lines between the script tags.

<%
Class Car

End Class
%>
"Class Car" defines the name of the class much in the same way that you would define a method "Function AddTax()" but without the parenthesis.  As you will no doubt expect "End Class" defines the end of the class, again as you would with a sub or function; "End Sub". With these two lines created we could create our car object using this class in the code of a page, but as there is no functionality within the class yet we will save that for a little later.

Defining Properties

Next we need to define some properties for the object we wish to create but before we can do that we need to define some member level fields to hold the values for the properties. Add the follwing declarations into the top of the class.

<%
Class Car

    'Member level fields
    Private m_intNumberOfWheels 'As Integer
    Private m_intCurrentSelectedGear 'As Integer
    Private m_strColourOfPaint 'As String
    Private m_blnIsEnfgineRunning 'As Boolean
    Private m_blnIsClutchEngaged 'As Boolean

End Class
%>

I have used an "m_"  prefix to my variable names to signify that they are defined at member level, the same level as the properties and methods for the class. You may wish to define your fields with a more current way of thinking as just "_" or maybe without. (Your choice entirely but I found at the time that it helped me keep track of the scope of my variables!) I have also declared the field as "Private" as I do not want them to be "visible" from out side my class. I.e. the page the instantiates the class has no access to these fields.

Now we are going to declare a property for the "Number of wheels"

'Properties
 Public Property Get NumberOfWheels()
     NumberOfWheels = m_intNumberOfWheels
 End Property
 Public Property Let NumberOfWheels(ByVal Value)
     m_intNumberOfWheels = Value
 End Property

You can see that there are two parts to our "Number of wheels" property the first "Get" allows us to retrieve the value stored in it and the second allows us to populate the property with a value.  You can see that I have declared both parts of the property as "Public" as I do want this property to be visible from out side the class. Each part of the property is declared just as you would a Function or Sub, along with "End Property" to define the end of the properties functionality. I say functionality, because unlike the static fields that we have declared at the top of the class, a property can hold functionality just like a Sub or Function. At present all the "Get NumberOfWheels" property does return the value held in our m_intNumberOfWheels field. The "Let NumberOfWheels" property allows us to populate the m_intNumberOfWheels field with a value.

Lets add the rest of the properties and see what the code looks like so far.

<%
Class Car

    'Member level fields
    Private m_intNumberOfWheels 'As Integer
    Private m_intCurrentSelectedGear 'As Integer
    Private m_strColourOfPaint 'As String
    Private m_blnIsEngineRunning 'As Boolean
    Private m_blnIsClutchEngaged 'As Boolean

    'Properties
    Public Property Get NumberOfWheels()
        NumberOfWheels = m_intNumberOfWheels
    End Property
    Public Property Let NumberOfWheels(ByVal Value)
        m_intNumberOfWheels = Value
    End Property

    Public Property Get CurrentSelectedGear()
        CurrentSelectedGear = m_intCurrentSelectedGear
    End Property
    Public Property Let CurrentSelectedGear(ByVal Value)
        m_intCurrentSelectedGear = Value
    End Property
   
    Public Property Get ColourOfPaint()
        ColourOfPaint = m_strColourOfPaint
    End Property
    Public Property Let ColourOfPaint(ByVal Value)
        m_strColourOfPaint = Value
    End Property
   
    Public Property Get IsEngineRunning()
        IsEngineRunning = m_blnIsEngineRunning
    End Property
    Public Property Let IsEngineRunning(ByVal Value)
        m_blnIsEngineRunning = Value
    End Property

    Public Property Get IsClutchEngaged()
        IsClutchEngaged = m_blnIsClutchEngaged
    End Property
    Public Property Let IsClutchEngaged(ByVal Value)
        m_blnIsClutchEngaged = Value
    End Property
   
End Class
%>

Now we have some properties for the class we need to create some methods to hold functionality.

Adding functionality

So far we have created a class, defined some private fields to hold values, some public properties to access these values and now we need to add some methods to provide functionality in the class and to work with these values. Methods within a class are written in the same format as you would write one normally within the ASP code of your page, however it is wise to take care of the scope (Public/Private) of the method to restrict it's access from out side of the class where necessary. Anyway, before we start adding our own functionality, willy-nilly, there are two important methods we must first take a look at.

Initialisation and Termination methods

Any code that is present within the "Sub Class_Initialize" method will be fired upon instanciation (creation) of the object. Any code that is present in the "Sub Class_Terminate" method will fire just before the object is destroyed. The "Initialize" method is very useful for instanciating other objects or populating fields with values as soon as the object is created. The "Terminate" method can be used to ensure that any objects instanciated within the class are disposed of correctly before the class is destroyed.

In our Car example we are just going to instanciate the state and values of a few of our private member fields. Add the following code below the class properties.

'Class Initialisation and Termination
 Sub Class_Initialize()
     m_intNumberOfWheels = 4
     m_intCurrentSelectedGear = 0
     m_strColourOfPaint = "Blue"
     m_blnIsEngineRunning = False
     m_blnIsClutchEngaged = True
 End Sub
 Sub Class_Terminate()
 '
 End Sub

As you can see we have given our car 4 wheels, the colour is blue, we have set the current gear as neutral (well zero!), and the engine is not running and the clutch is engaged.

Right, so now lets give our car some functionality! Add the following methods below the first two.

'Public methods
 Public Sub StartEngine()
     If (m_intCurrentSelectedGear = 0) Or (m_blnIsClutchEngaged = False) Then
         m_blnIsEngineRunning = True
         Response.Write "<p>The engine started."
     Else
         Response.Write "<p>You cannot start the engine if car is in gear unless clutch is dis-engaged!"
     End If
 End Sub
 
 Public Sub StopEngine()
     m_blnIsEngineRunning = False
     Response.Write "<p>The engine stopped."
 End Sub
 
 Public Sub DepressClutchPedal()
     m_blnIsClutchEngaged = False
 End Sub
 
 Public Sub ReleaseClutchPedal()
     m_blnIsClutchEngaged = True
 End Sub
 
 Public Sub SelectGear(ByVal gearNumber)
     If IsNumeric(gearNumber) Then
         If (gearNumber > -1) And (gearNumber < 6) Then
             If (m_blnIsClutchEngaged = False) Then
                 m_intCurrentSelectedGear = gearNumber
                 Response.Write "<p>You selected gear #" & gearNumber
             Else
                 Response.Write "<p>You must select dis-engage the clutch to select a gear"
             End If
         Else
             Response.Write "<p>You car doesn't have that gear!"
         End If
     End If
 End Sub

We are not really worried about what the actions of the methods are more just to show how they are implemented into the class. The next step is to build a page with that will use our class to create a car object.

Build a page

<%@LANGUAGE="VBSCRIPT" CODEPAGE="1252"%>
<%Option Explicit%>
<!-- #Include File="classCar.asp" -->
<html>
<head>
<title>Asp Class Example</title>
</head>
<body>
<%
Call RenderCar()
%>
</body>
</html>

<%
Private Sub RenderCar()
   '
End Sub
%> 

Instanciating an object

The first thing you should notice that we reference the class file by using the "#Include File" method. This "imports" the code from our class file into our page. In the RenderCar method add the following code, then we'll go through it bit by bit.

 Dim MyCar 'As Car
 Set MyCar = New Car

 Response.Write "<p>My car's colour is " & MyCar.ColourOfPaint
 Response.Write "<p>My car has " & MyCar.NumberOfWheels & " wheels"
 Response.Write "<p>If I now paint my car red..."
MyCar.ColourOfPaint = "Red" Response.Write "<p>My car's colour is now " & MyCar.ColourOfPaint With MyCar .StartEngine() .SelectGear(6) .SelectGear(1) .DepressClutchPedal() .SelectGear(1) .ReleaseClutchPedal() .StopEngine() .StartEngine() .DepressClutchPedal() .StartEngine() .StopEngine() End With If Not(MyCar Is Nothing) Then Set MyCar = Nothing End If

Now breaking it down into bite sized chunks... First we declare a new variable "MyCar" to hold an instance of our "Car" object and then set the variable to reference a newly created instance of that object. Note the syntax for setting the reference; unlike setting a value of an integer where we would use intThis = 12, we must use the Set word so Set ThisVariant = ThatObject.

Dim MyCar 'As Car
Set MyCar = New Car

As we used the "Sub New" method in the class to populate some values into the properties as soon as we instanciate the object we can retrieve them right away with... [MyObjectName].[PropertyName] in this case MyCar.ColourOfPaint and MyCar.NumberOfWheels.

Response.Write "<p>My car's colour is " & MyCar.ColourOfPaint
Response.Write "<p>My car has " & MyCar.NumberOfWheels & " wheels"

Now we can change the value of one of our objects properties with [MyObjectName].[PropertyName] = Value. In this case we will change the paint colour to red. MyCar.ColourOfPaint = "Red" and then display the value of the property as before.

Response.Write "<p>If I now paint my car red..."
MyCar.ColourOfPaint = "Red"
Response.Write "<p>My car's colour is now " & MyCar.ColourOfPaint

Next we will call some of the methods of the object.  To do this we use [ObjectName].[MethodName]. Also to make the code clearer to read you can use With [ObjectName] and End With blocks. This saves writing out [ObjectName].[MethodName] for each method call. Then you just need to use dot[MethodName] or dot[propertyName]

With MyCar
     .StartEngine()
     .SelectGear(6)
     .SelectGear(1)
     .DepressClutchPedal()
     .SelectGear(1)
     .ReleaseClutchPedal()
     .StopEngine()
     .StartEngine()
     .DepressClutchPedal()
     .StartEngine()
     .StopEngine()
End With

I wont go into the details of what each method does as this should be clear from the method name. What is important is how you access the methods from the page. Please note that any class methods that were declared Private will not be available for use on the page.

Last of all we have to dispose of the object properly to release the memory that the object resided in.

If Not(MyCar Is Nothing) Then
     Set MyCar = Nothing
End If

We do this by setting the variable that references the object to reference of Nothing (after of course, checking it was set in the first place).

Multiple instances on one page

So what if we need to have multiple instances on one page? Well all we need to do is declare a different variable to reference each instance.

Dim MyCar 'As Car
Dim YourCar 'As Car
Set MyCar = New Car
Set YourCar = New Car
MyCar.ColourOfPaint = "Black"
YourCar.ColourOfPaint = "White"
Response.Write "<p>My car's colour is " & MyCar.ColourOfPaint
Response.Write "<p>Your car's colour is " & YourCar.ColourOfPaint

You can have as many instances on the page as you like, but each must be referenced from a variable. Try it and see. Remember to clear up all your objects at the end by setting all their references to nothing.

I hope this gives many novice programmers who have not yet used classes in classic asp the inspiration to get on and try it. To me when I started using them it seemed like a lot of effort for what seemed very little gain. But there are many advantages to programming this way, code maintenance of my apps is now much less troublesome, with it being a lot easier to add additional functionality. The code in the pages are now easier to read through as there is little "business logic" embedded within in them, and with all my classes in one folder its easy to find the relevant class and copy it to a new aplication, so developing new applications is getting much quicker. Lastly, but not least is that the transition to ASP.Net if you choose to do it later, will be a little smoother.

Hopefully in the not to distant fututre I will publish an article on using objects wrapped up inside a dictionary object as an alternative to bringing a recordset back to your page or to using GetRows() and arrays. It will also use a dataAccess class to abstract the dataAccess out of the business logic, and get a tiny bit closer to the 'N' tier programming approach.

A point to note

Although in this case I have used Response.Write within the class to output a result, in practice this is not a good idea. One of the advantages of using classes is to abstract out the "Business Logic" of the page from the "Presentation Code" (html) so it is much better practice to populate a property with the results of your method calls and retrieve the value from the property as shown with Response.Write MyCar.ColourOfPaint in the above example.

Please note this is in fact a reprint of the "Classic ASP - Introduction to VBScript classes" article I wrote for Developer Fusion a while back. Thanks for reading this, I hope some of you find it useful. Your comments, good or bad, are as always, very welcome as this was my first ever article.

Wednesday, April 09, 2008 7:27:22 PM (GMT Standard Time, UTC+00:00)  #    Comments [0] -
Classic Asp | VB Script | Object Orientation | Classes
Archive
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
Blogroll
 Clemens Vasters
 Harry Pierson
Passion * Technology * Ruthless Competence
 Joshua Flanagan
A .NET Software Developer
 Michael Schwarz's Blog
Developing applications on the Microsoft platform since Windows 3.1!
 Omar Shahine
Yet another Microsoft blogger
 Scot GU
Scott Guthrie lives in Seattle and builds a few products for Microsoft
 Scott Hanselman
Programming Life and the Zen of Computers
 Tom Mertens
Tom's corner
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
Duane Wingett
Sign In
Statistics
Total Posts: 32
This Year: 6
This Month: 1
This Week: 1
Comments: 35
Themes
Pick a theme:
All Content © 2010, Duane Wingett
DasBlog theme 'Business' created by Christoph De Baene (delarou)