During 2nd year at university there was a team project about using knowledge of Object-Oriented Programming to design a data system that works well with basic geometry shapes.

sourcecode.

An overview

We were asked to build an SVG Interpreter. SVG is basically an XML file that represents a vector image. You can try it yourself by copying the code below, paste it in Notepad, save it with a “.svg” ending then open it up with a browser.

<svg xmlns="http://www.w3.org/2000/svg"> <ellipse cx="150" cy="100" rx="200" ry="100" stroke="rgb(255, 255, 0)" stroke-width="3" fill="rgb(0, 255, 0)" stroke-opacity="0.7" fill-opacity="0.5" transform="rotate(-30) translate(-200, -30)" /> </svg>

What you should get is a bright green quarter of a circle on the top left of the screen. So there are essentially 3 things we need to do: Parse the XML file for object data Store the data in an organized way for latter usage Draw the image using stored data For parsing, there we already have rapidxml, a library that makes parsing xml simple and sweet. The library had functions and classes for parsing an XML into “nodes” and “attributes” that we need. For the drawing, our teacher recommended using GDI+ library. It already contains specify draw functions for a few basic shapes we need, all that’s left was to pass in the values accordingly. So our main task was to implement a data design for these geometry objects to function effectively.

Designing the data structure

A somewhat brute force approach would be to create individual classes for every shape. Each with their own set of attributes, and a dedicated draw function. The downside to this is the tedious copy-paste and switch-case, if-else statements that would be required to implement. Imagine a long switch-case for the object name, in every case, you have to manually type in some functions to set attributes and some other functions to draw the image on screen. It’s possible to do it this, but imagine if there’s an upgrade or an update the any of the objects, you would have to look through lines and lines of code to fix each one of them.

The idea

No! We want to have a loop outside that goes through each object, call 1 function to set the contents, a nested loop and in it, we call 1 function to set the attributes provided the index number of the attribute. That sounded a little confusing, but here’s what it looks like.

for (xml * node < > * i = root * node -> first_node(); i; i = i -> next_sibling()) {
  a = a -> makeObject(i -> name());
  if (a != NULL) {
    a -> setContent(i);
    for (xml_attribute < > * at = i -> first_attribute(); at; at = at -> next_attribute()) {
      a -> setAttribute(at -> name(), at -> value());
    }
    a -> Draw(hdc);
  }
}

In order for that to happen we must use a newly acquired knowledge called Polymorphism. Essentially polymorphism allows an instant of an “abstract” class to “become” any object that was based on that class. In other words, a Shape can be a Circle, a Rectangle or a Square, whenever you need it to be. In the code, “a” is a Shape, after parsing the xml, we turn “a” into our desired object using the method “makeObject” passing in the name of the shape we need as a parameter. “a” is now a specific Shape and “a->Draw(hdc)” will draw itself on the screen. So if we write something like this, we would get a circle on screen.

a = a -> makeObject("Circle"); //specify some attributes for "a" here a->Draw(hdc);

The implementation

To implement the idea, we create an abstract class called Shapes, with a vector of Attributes, setAttribute functions and a Draw functions. Latter on we implement each of those shapes and override each Draw function correspondingly.

Storing

Now here’s a little problem, we don’t want to use many if-else or switch-case statements, but we only know what specific shape “a” was going be at run time so we can’t type-cast it in our code. This is where the Factory Pattern comes into play. Inside our base class, we will have a static list of all the shapes we use. These we called Flyweights in the code as it was kind of like an implementation of the Flyweight Pattern. Anyways, each of our Shape-based classes had a function called Clone() which instantiate a new instance of itself and returns the pointer to it. In “makeObject()” we will compare the names provided and return a clone of the object needed. Simply put, “a” has a list of Flyweights which are molds or prototypes, then whenever makeObject is called, it returns us an object based on which prototypes we wanted.

static vector < Shape * > flyweights; // contains unique prototype for all shapes
static Shape * makeObject(char_ className) {
  int fs = flyweights.size();
  for (int i = 0; i < fs; i++) {
    if (flyweights[i] != NULL) {
      if (!strcmp(flyweights[i] -> className(), className)) {
        return flyweights[i] -> Clone();
      }
    }
  }
  return NULL;
};
Shape_ Clone() {
  Shape * a = new Circle();
  return a;
};

Note that these are snippets from different places in the source code and are only grouped up here for illustration purposes. And in the main program we provide the prototype before hand.

//Intialize flyweights
Shape * a = NULL;
Shape::createFlyweight(new UserDef::Recta());
Shape::createFlyweight(new UserDef::Circle());
Shape::createFlyweight(new UserDef::Line());
Shape::createFlyweight(new UserDef::Ellipse());
Shape::createFlyweight(new UserDef::Polygon());
Shape::createFlyweight(new UserDef::Polyline());
Shape::createFlyweight(new UserDef::Text());
Shape::createFlyweight(new UserDef::Group());

Et voilĂ , we got ourselves a “shape factory”. There was one more problem: the “Group” object. “Group” is not really a shape, but it can contain other shapes and itself. The special thing about “Group” is that it passes down its common attributes to all of it’s child (sub-nodes). For this particular object we implemented what’s called Composite Pattern. A friendly example of this pattern is how files and folders work. Folders can contain files and folders, passing on its “access” and “visibility” attributes to sub-folders. To set the attributes of all child, which may or may not contain sub-groups, I made “setContent” for this class into a recursive function that will “setContent” and “setAttribute” for all of it’s child. For other classes, “setContent” is empty, except for Text class. “setAttribute” is the same for all Shape-based classes.

void setAttribute(char * name, char * value) {
  int n = members.size();
  for (size * t i = 0; i < n; i++) {
    members[i] -> setAttribute(name, value);
  }
};
void setContent(xml_node < > * root) {
  for (xml * node < > * i = root -> first * node(); i; i = i -> next_sibling()) {
    Shape\ * a = a -> makeObject(i -> name());
    a -> setContent(i);
    for (xml_attribute < > \ * at = i -> first_attribute(); at; at = at -> next_attribute()) {
      if (a != NULL) {
        a -> setAttribute(at -> name(), at -> value());
      }
    }
    members.push_back(a);
  }
}

Retrieving

Next up is the data retrieval process. There are many attributes, and there are many data types. Again we want them to be structured, uniform. My goals was to have a function called “get()” and it can return whatever attributes needed, provided the correct name, despite having different data types such as float, or int. The design for this part is quite flawed and is not recommended. What I did was split different data types into different classes based on another abstract class Attribute. Then I created another class called Data to contain all return values for the “get()” function. Each Attribute-based class has a “getVal()” function that will put corresponding data into the Data class and return it. So in order to use what “get()” returned, the developer has to call specific “return functions” from the class Data for the correct data type and values. Below are a few examples of how it is used:

// retVec to recieve a predefined vector of integers used for RGB color values
vector<int> rgb = this -> get("stroke").retVec();
// retFloat for float
Pen pen(col, this -> get("stroke-width").retFloat());
// retInt for integer
int x1 = this -> get("x1").retInt();
// retFVec to recieve a predefined vector of floats
vector<float> transform = this -> get("transform").retFvec();

In the end, it made the “get()” function looks better, but overall cost a bit more memory and didn’t help the developer that much in coding since he would still need to call another function depending on the data types he needs. Here is a full class diagram for everything done

The results

Well… It worked! The program draw the correct image on screen depending on which file we assigned to it. However, it didn’t really do anything else, we didn’t even made a UI for user to specify the file name, it was hard-coded in. Nonetheless, we got perfect marks for it, fulfilling all the requirements, most importantly, we understood the importance of data design and experienced hands-on how it influence all of our decisions involving the code. It taught us to think more about data abstraction, code maintenance, scalability. SVG text for this lion image