Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

private-implementation pattern #3254

@jfirebaugh

Description

@jfirebaugh

With the styles API, a lot more API surface area will be publicly exposed:

  • Style
  • Source and future subclasses for specific source types
  • StyleLayer and subclasses for specific layer types
  • Paint and layout properties for all layer types

So far, we've been using the following technique for separating public APIs from private implementation:

  • Public headers live in include, private headers live in src
  • Public headers use forward declarations and pointers to avoid needing to include private headers

For the styles API, I don't think this is going to be enough. For example, I don't think we want to simply make style_layer.hpp a public include file -- it would cascade into making other headers public as well, and expose implementation details we want to be free to change. The current StyleLayer and subclasses depend on rapidjson headers (not easily forward declared) and have methods that are conceptually public to other mbgl internal classes, but private to API consumers (e.g. cascade, recalculate, createBucket).

Therefore we need to use more heavy-duty techniques to separate the public API from private implementation. I propose we adopt a particular implementation of the opaque pointer pattern, and follow this convention throughout portions of the codebase exposed to the public API:

  • Public header files expose objects in the mbgl namespace. (Already doing this.)
  • These objects hold a pointer to a corresponding instance of a class with the same name, but in the mbgl::impl namespace.
  • All implementation code moves to the mbgl::impl namespace and src/mbgl/impl directory tree.

Example:

include/mbgl/map.hpp

namespace mbgl {
namespace impl { class Map; }

class Map {
public:
    void doSomething();

private:
    std::unique_ptr<impl::Map> impl;
};

}

src/mbgl/map.cpp

namespace mbgl {

void Map::doSomething() {
   impl->a.doSomething();
   impl->b.doSomethingElse();
}

}

src/mbgl/impl/map.hpp

#include <mbgl/map.hpp>
#include <mbgl/impl/foo.hpp>
#include <mbgl/impl/bar.hpp>

namespace mbgl {
namespace impl {

class Map {
public:
    Foo a;
    Bar b;
};

}
}

In this example I've shown implementing mbgl::Map::doSomething in src/mbgl/map.cpp. Another option is to forward to mbgl::impl::Map::doSomething and put the real implementation in src/mbgl/impl/map.cpp. But that's another level of indirection and I don't know if there's any benefit.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions