2

I'm currently developing an application using SFML. My biggest concern at the moment is making a layer of abstraction over the library, so I can easily change it to something else if needed. What I'm struggling with is designing an abstraction that's intuitive and not leaky.

An example: drawing in SFML is done using drawable objects (implementing sf::Drawable) and objects capable of rendering them (sf::RenderTarget).

Say I want to wrap those and create my own IDrawable:

class IDrawable {
public:
    virtual void draw(IRenderTarget& target) const = 0;
};

Even though it looks nice, in order for the concrete classes to achieve their goal, they somehow still need to exchange information specific to SFML, i.e. IRenderTarget has to declare an abstraction breaking method, such as

virtual sf::RenderTarget& impl() = 0;

The only solution I can think of is creating concrete classes that require all library-specific parts to be passed via constructor, so there's no cross-class communication of library-specific data. This way, IDrawable would change to:

class IDrawable {
public:
    virtual void draw() = 0;
}; 

Another solution would be abusing the friending mechanism, but I guess that's not great, either.

In other words -- is there an approach for wrapping a 3rd party library, so that the created abstraction is not leaky (doesn't require public accessor methods that break the encapsulation)?

  • 1
    I do not fully understand. The concrete subclass Box of IDrawable implements its draw method by calling methods on IRenderTarget, e. g. target.line(a,b,c,d). Where does the need to break the abstraction arise? Could you please clarify? Of course, the concrete implementation of IRenderTarget will know about SFML's RenderTarget classes, But that is okay, because it IS the wrapper. – Oliver Meyer Jun 14 at 7:58
  • 2
    You might like to reconsider creating the wrapper in the first place. How likely is it actually, that you will change SFML to something else? How much effort will the initial wrapper be? It is easy to hide away the concrete implementation. It is very hard to hide away the concepts of SFML. If you eventually replace SFML your wrapper's concepts must be mapped to the concepts of that library. That library might not have Drawables nor Renderers. – Oliver Meyer Jun 14 at 8:05
  • "Where does the need to break the abstraction arise?" The abstraction would get broken at the very call to target.line(a,b,c,d), because most likely, either one of the parameters would be an "SFML type" or it would expose such types in its API. I mean - no matter how many wrapper classes I create, there has to be this one spot in where SFML code is actually used. So either I encapsulate it all within one class, or I make my interfaces dependent on SFML API (but this way the code structure gets more modular). – TuRtoise Jun 14 at 15:35
  • 1
    “I fail to see any problem, sir.” You write _ in order for the concrete classes to achieve their goal_. Please provide a pseudo code example of that concrete class. Then we can tell you how to avoid the problem. – Oliver Meyer Jun 15 at 4:49
  • 1
    @TuRtoise: don't bury explanations here in comments; instead, use the edit button to improve it. And the answer to your question is: those SFML types need to be wrapped as well. – Doc Brown Jun 15 at 5:41
3

I think you're well into YAGNI territory here. When you know what your second library looks like, then is the time to look at the abstractions it exposes, compare those with the abstractions from SFML and only then make some decisions as to what your wrapper should look like. Otherwise you are just guessing what you think the common abstractions are, and at risk making a wrapper which doesn't match your second library anyway.

Even given all that:

  • This assumes you'll ever actually replace SFML. Replacing the guts of a system is something software engineers love to talk about but much less often actually do.
  • I'd argue that a lot of the value of a wrapper is actually only when you want to run two implementations simultaneously - if you're actually replacing SFML with something else, just replace it. It's only if you want one version of your code to be easily able to talk to both SFML and Hypothetical Library Two that a wrapper is necessary.
| improve this answer | |
  • 1
    I totally agree. Yet, I do not upvote, because it does not answer the actual question. – Oliver Meyer Jun 15 at 4:42
2

Avoiding Leakage

To avoid leakage, wrap everything that is exposed. Create wrapper classes until you only expose basic types. This means you will have to come up with an abstraction not only for each class, but for all concepts of SFML.

In your example you are wrapping 1:1, i. e. each class in SFML is wrapped to one class of your wrapper: sf::Drawable is mapped to turtoise::IDrawable, sf::RenderTarget is mapped to turtoise::IRenderTarget. If you continue doing that, the user of turtoise::IDrawable will not see any SFML classes. Leakage has been avoided.

Replacing SFML

Yet, it will be a lot of work and you will not be able to replace SFML with anything but another version of SFML. Any non-SFML library will use different concepts than your wrapper layer. Your wrapper does not abstract from SFML.

Abstraction

If you truely like to abstract away from SFML, you must first define your own Turtoise Multimedia Library on the interface level. Then you can create an implementation of your new library using SFML.

When you define your library you might restrict yourself to basic functions. If your library is much simpler than SFML, you will be able to reimplement it using another library. Your replacement will work. Of course you have to restrict yourself when writing your application.

As an alternative you can predict which library you most likely will replace SFML with. Then derive the common core of these two (or three) libraries and combine it in your Tortoise Multimedia Library. Because every concept your libray contains is present in all the libraries, you will be able to create wrappers.

Last Words

I agree with @Phillip Kendall: I doubt you need the abstraction at all.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.