Design Patterns – Factory vs Builder Pattern for Reading Finite Element Model Data

cclassdesignrelationships

A follow up to another question (Making a design decision about reading model data from an input file).

I wish to ask another question regarding builder or factory pattern. (I read that builder is more complex than factory, and I may not need to use builder for now). So here is the data I have to read:

TABLE: "DEGREES OF FREEDOM"
   X=Yes   Y=Yes   Z=Yes   R1=Yes   R2=Yes   R3=Yes

TABLE:  "ANALYSIS OPTIONS"
   Solver=Advanced   SolverProc=Advanced    

TABLE:  "COORDINATE SYSTEMS"
   Name=GLOBAL   Type=Cartesian   X=0   Y=0   Z=0   

TABLE:  "GRID LINES"
   CoordSys=GLOBAL   AxisDir=X   GridID=A   XRYZCoord=-720     LineColor=Gray8Dark   Visible=Yes    
   CoordSys=GLOBAL   AxisDir=X   GridID=B   XRYZCoord=-432     LineColor=Gray8Dark   Visible=Yes   
   CoordSys=GLOBAL   AxisDir=X   GridID=C   XRYZCoord=-144     LineColor=Gray8Dark   Visible=Yes   
   CoordSys=GLOBAL   AxisDir=X   GridID=D   XRYZCoord=144      LineColor=Gray8Dark   Visible=Yes   
   CoordSys=GLOBAL   AxisDir=X   GridID=E   XRYZCoord=432      LineColor=Gray8Dark   Visible=Yes   
   CoordSys=GLOBAL   AxisDir=X   GridID=F   XRYZCoord=720      LineColor=Gray8Dark   Visible=Yes   
   ...

There are many more tables like these. Some tables have parent, child relationships (Each coordinate system has grid line of its own). I have made structures that represent each table, like this:

struct ActiveDOF
{
  bool Ux;
  bool Uy;
  bool Uz;
  bool Rx;
  bool Ry;
  bool Rz;
};

struct GridLine
{
  enum Direction{X, Y, Z};
  enum Type{PRIMARY, SECONDARY};
  enum Location{START, END};
  std::string coodSystem;
  Direction direction;
  std::string gridID;
  float XRYZ;
  Type lineType;
  QColor color;
  bool visible;
  Location bubleLoc;
  bool allVisible;
  float bubleSize;
};

struct CoordinateSystem
{
 std::string name;
 std::string type;
 QVector3D center; // center x, center y, cetner z
 QVector3D about; // aboutx, about y, about z
 std::vector<GridLine> gridLines;
};

these data structures are incorporated to the model class, which will make a huge class since there are 50 odd data structures like this:

class Model
{
private:
ActiveDOF activeDOF;
CoordinateSystem coordinateSystem;
....

public:
Model() {} ...

}

each table has to have its set method and get method. This design worries me because if I decide to change it, it is going to be very time consuming. I appreciate any suggestion. I think the information here also will put the earlier question in a better light.

Now, I don't know where the builder or factory method should go, given the situation. I looked at some code and UML charts but I was not able to understand how to implement the factory, or builder to make structures required for my design. I have to have access to each table by name, because they might be subject to change inside the model, so for the time being I am avoiding making each of them a subclass of a virtual base class, so that I can store them in a container.

Also, does it make sense that instead of declaring an instance of the data struct, I should keep a pointer to them ?
if all data structures are derived from a virtual base class called Record, then the model will be something like this:

class Model
{
private:
ActiveDOF* activeDOF;
CoordinateSystem* coordinateSystem;
....

std::Vector<Record*> data;

public:
Model() {} ...

}

I think it is extra work to allocate, dislocate memory for them, but it can help in managing the data and keep extra typing ? am I right in assuming that ?

Best Answer

You are trying to solve a data access problem, and it requires a data access solution.

First, let's examine your proposed solutions, and why they don't solve the problems you have.

The Factory Pattern

The Factory Design Pattern (also called the Factory Method Pattern) is useful when you want to utilize polymorphism and you want to separate object construction completely from the implementing classes.

Wikipedia:

In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.

(emphasis, mine)

And further down it elaborates on the problems this pattern solves:

The Factory Method design pattern solves problems like:

  • How can an object be created so that sub classes can redefine which class to instantiate?
  • How can a class defer instantiation to sub classes?

The problems you describe in your question are different from these. You aren't dealing with sub classes or polymorphism. The factory pattern isn't a solution.

The Builder Pattern

The Builder Pattern (from Wikipedia):

The Builder is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation.

(emphasis, mine)

Here, again, we see a mismatch between the problem you are describing and the intended use of this pattern.

The Builder design pattern solves problems like:

  • How can a class (the same construction process) create different representations of a complex object?
  • How can a class that includes creating a complex object be simplified?

(emphasis, mine)

The key here is the construction of an object (an object = 1 object) is complex. This could be due to a large number of constructor arguments, or due to the complexity of assembling its dependencies.

Individually, each struct or class is pretty simple, so the builder pattern is not a good fit either.

Data Access Patterns (The Solution)

The problems you are actually trying to solve are how to perform data access, and you may be looking for a Data Access Object.

In computer software, a data access object (DAO) is an object that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, the DAO provides some specific data operations without exposing details of the database.

In your case, replace "database" with "text file". A related design pattern is the Repository Design Pattern, which breaks data access into 3 main solutions:

  • Repository — the public interface for "data access stuff"
  • Gateway — knows how to talk to an Oracle database or read/write to a text file. This would include serializing objects and structs to SQL or the data format expected in the text file.
  • Factory — maps data returned by the Gateway to the objects and structs used by the rest of your application

Ownership of Memory

Since you don't have the advantage/limitation of automatic management of memory, ownership of the memory allocated to these objects and structs is definitely a concern. Here you must make a decision:

  • Is the data access object responsible for cleaning up this memory?
  • Does the data access object expect the caller (client code) to take responsibility for freeing this memory?

If the data access object is created when the application starts, and lives until the application is shut down, the data access object could take ownership of this memory.

If the data access object is created on demand, and then disposed of, client code needs to make sure this memory is cleaned up.

Define this in the comments or documentation for the data access object, and enforce this in code review.

Related Topic