0

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.

Baris
  • 13
  • 4
  • 1
    Why you want a factory for simple mundane things that can make themselves just fine with few lines of code? What is its purpose and responsibility? – Öö Tiib Dec 07 '22 at 08:55
  • its the factory that decouples the code using the factory from the implementation of the constructors. I dont understand why a dependency of the factory on the objects it creates is a problem – 463035818_is_not_an_ai Dec 07 '22 at 09:07
  • and I agree with Öö a factory would typically have a single function whose parameters determine what type is created and then returned as pointer to base class. Having many different static methods for each type of object means the caller now has to call different static methods rather than different constructors. Whats the benefit? – 463035818_is_not_an_ai Dec 07 '22 at 09:09
  • 1
    my tip: Consider the use case first. A good way to not overthink stuff is to write tests first, then only implement what is needed to make them pass. It is really unclear what use case of your library would need or benefit from this `Creator` class. Maybe you have some use case in mind, then you should include it in the question – 463035818_is_not_an_ai Dec 07 '22 at 09:12
  • Can a factory be decoupled from the constructors? I can't even wrap my head around how to achieve that. – Ignacio Gaviglio Dec 07 '22 at 09:42
  • @IgnacioGaviglio By decoupling i mean decoupling the factory from the library. For example, library does not have a 2-point ctor for an Axis. When i add that ctor into the library, i need to update the creator class too. When i study the design patterns, an important point is to separate the creator class from the definitions of the classes to be created that the creator should remain the same even the created classes change in time. Do i misunderstand the point? – Baris Dec 07 '22 at 10:01
  • @ÖöTiib The point is to have an API to my geometry library. For example, instead of including many headers (point.h, vector.h, ...) the user will use the API (#include ). Additionally, i study design patterns in detail and the geometry library looked ssuitable ffor this purpose. But after your and 463035818's comments, i am confused. – Baris Dec 07 '22 at 10:07
  • @463035818_is_not_a_number i will add the use case into my question. Thx :) – Baris Dec 07 '22 at 10:09
  • ".... Do i misunderstand the point? " yes. The point of the factory is that the code using the factory does not depend on the way the objects are created – 463035818_is_not_an_ai Dec 07 '22 at 10:11
  • @463035818_is_not_a_number I editted my question following your comments. Am i still wrong to have a creator pattern? By the way you talk about a factory but it doesn't have to be a factory. It can be a builder. My question is about using creator pattern as an interface to my library. By the way, can you please suggest me a book or something to study creator patterns? – Baris Dec 07 '22 at 10:56
  • you already have an interface to your library. Users can use `Point`, `Vector` and the other classes. Sorry, but I do not understand what problem you are trying to solve. Whats the benefit for the user when they have to write `Point3D point1 { Creator::createPoint3D(std::array{ 11., 12., 13. }) };` rather than `Point3D point {{11.,12.13.}};` to me the latter looks much simpler, why do you want the former? – 463035818_is_not_an_ai Dec 07 '22 at 12:33
  • for book recomendations there is the canonical https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list, and for design patterns the gang of four is an obvious choice – 463035818_is_not_an_ai Dec 07 '22 at 12:40
  • You need a creation pattern if your objects are created differently but used identically after creation. Think network connections. There is a lot of different kinds but in the end of the day you send and receive messages. You don't care if you have TCP or UDP or IPV6 ir IPV4 or TLS or ... (or if you do care, this can be encapsulated in virtual functions). Thus it makes sense to abstract away creation in this case. – n. m. could be an AI Dec 07 '22 at 15:46
  • ... However in your case a Triangle and a Circle have very little in common. If your user creates a Circle, then they are going to need a Circle, not some abstract Shape. So they need to know about a concrete class, and then they may just as well use its constructor directly. – n. m. could be an AI Dec 07 '22 at 15:47

1 Answers1

1

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.}};.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thanks alot for your quick answers. I think now i am clear about what an API is. I need to work on another project to study creational patterns :) – Baris Dec 07 '22 at 14:14