Home > Mobile >  Separated (decoupled) API (a creator class) for the classes of a library with constructots having di
Separated (decoupled) API (a creator class) for the classes of a library with constructots having di

Time:12-08

I have a library for 3D geometry. The library has classes like Point, Vector, Axis, Plane etc. The library has an interface and implementation hierarchy but simply all classes inherits from GeometryObject class.

class Point : public GeometryObject;
class Vector : public GeometryObject;
class Axis : public GeometryObject;
class LinePiece : public GeometryObject;
class Plane : public GeometryObject;

Each class has a numbber of constructors, for example:

Point::Point(){}
Point::Point(std::array<double, 3> theCoords){}
Vector::Vector(std::array<double, 3> theComponents){}
Axis::Axis(const Point& thePoint1, const Point& thePoint2){}
Axis::Axis(const Point& thePassingPoint, const Vector& theDirectionVector){}
Plane::Plane(const Point& thePoint1, const Point& thePoint2, const Point& thePoint3){}
Plane::Plane(const Point& thePassingPoint, const Vector& theNormalVector){}

As seen, the constructors have different signature. Only Point class has a default ctor.

Currently, the library does not have an API. The users of the library must include the header for each geometry object they intent to use. But i want to access the library through a single interface.

#include<cs.hxx>
#include<Point2D.hxx>
#include<Point3D.hxx>
#include<Vector2D.hxx>
#include<Vector3D.hxx>
#include<Axis.hxx>
#include<LinePiece.hxx>
#include<Circle.hxx>
#include<Plane.hxx>

int main()
{
    Point3D point1 { Point3D(std::array<double, 3>{ 11., 12., 13. }) };
    // Other objects
}

The user code if i have a Creator would be:

#include<Creator.hxx>

int main()
{
    Point3D point1 { Creator::createPoint3D(std::array<double, 3>{ 11., 12., 13. }) };

    // OR with a builder for example
    Builder builder = Builder("Point3D", std::array<double, 3>{ 11., 12., 13. }) // Just an example
    GeometryObject point2 { Creator::create(builder) };
    
    // Other objects
}

Hence, i want to create a creator class to act as the API of the library.

Starting from here, i will use static creator functions for simplicity. Depending on the design pattern (abstract factory, or factory method or builder,..), the design will be different.

The creator class would simply be defined:

class Creator {
    static GeometryObject create(...);
}

or

class Builder {
    Builder(...);
}

class Creator2 {
    static GeometryObject create(const Builder&);
}

I left "..." for the parameters as the constructors vary for each object. A direct solution is to create a static member function for each constructor:

class Creator {
    static Axis createAxis(const Point& thePoint1, const Point& thePoint2);
    static Axis createAxis(const Point& thePassingPoint, const Vector& theDirectionVector);
}

However, the creator class is fully coupled with the library implementation. Any change in the library must be reflected to Creator too. For example, when a new constructor is added to a class in the library, the Creator must be updated. I studied abstract factory, factory method and builder design patterns. As i understand, a creator class must be independent on the definitions of the classes to be created. But i could not manage the varying constructor problem.

Am i right to have a creator design pattern as my API? How can i create the Creator class which is separated (decoupled) from the implementation of the geometry library? Which design pattern should i use?

Note: Only the Point has a default ctor as i mentioned. The other objects dont as physically not possible. For example a Vector cannot have all components zero. I could assign default values. For example, for the Plane, a default value would be the x-y plane of the global coordinate system. But, a Plane object has two members: a Point and a Vector. Hence, when i create the default Plane, i have to create a Point and a Vector. However, the user of the library would not be aware of these Point and Vector objects which is i think not a good design. Another solution is to have deault constructors which create uncomplete objects. But this breakes RAII and is not preferable. Hence, i removed default ctors accept for the Point.

CodePudding user response:

Currently, the library does not have an API.

Yes your library does have an API. The user can use the classes directly. You seem to have a certain unclear idea of what an "API" should look like or not look like.

The users of the library must include the header for each geometry object they intent to use.

Thats completely fine. We are used to that. Its what the majority of libraries does: You need to include what you use, you do not want to include what you do not use.

But i want to access the library through a single interface.

That is what you want, but it is not like you need to do anything extra before your library has an API. The API is the public interface. In a nutshell: The stuff users can work with. Don't overthink this. If a user wants to use Point they include corresponding header and use it. If they want to use a different class they include a different header and use it.

Hence, i want to create a creator class to act as the API of the library.

And here your problem starts. You want to write some extra code that serves no real purpose (the purpsose is "I want an API", but there is already one). Moreover you want this extra code to not depend on stuff that it does depend on. And thats not possible. No matter what pattern you choose and how the interface of some Builder or Creator or Factory looks, its implementation will need to create the objects hence its implementation depends on how the objects are created. Something cannot be responsible for a task but be completely independent of that task. You can separate concerns, but separating a concern from itself makes no sense.

If your classes have different constructors then the user needs to read the manual / the header to see what constructor the class has and call it accordingly. This won't go away when instead of calling the constructor they need to call a different function with the same arguments that are then forwarded to the constructor.

Am i right to have a creator design pattern as my API?

No.

Which design pattern should i use?

None. Design patterns are to solve problems. You have no problem to be solved. The user can do the same by calling the constructors directly as they can by calling something else that then forwards the arguments to the constructor.

Using a string to select what type to create might look like a factory can help. Though I fail to see the application of this. In your example Builder builder = Builder("Point3D", std::array<double, 3>{ 11., 12., 13. }) will eventually create a Point3D for the user. I see no application of polymorphism here, so the user can simply create a Point3D: Point3D p{{11.,12.13.}};.

  • Related