Hi, I've been wondering if anyone would give some advice on an
OOP-related question. Assume there's an external library (module
c_library) that handles IDs for groups and datasets and we want
to wrap it in a high-level D API. Groups can contain datasets
identified by names, and each dataset has a parent group; there
are external functions get_dataset/get_group that provide the
corresponding ids.
The Group class needs to have a "dataset(name)" method that
returns a dataset by name and the Dataset needs to have a
"group()" method that returns the parent group. However
constructors of Group and Dataset that take an id are really
meant to be completely internal (i.e. private or protected) which
leads to the problem: how would those methods be able to access
those constructors?
--------------------------
Solution 1: set protection level for constructors of
Group/Dataset to "package" so they are callable from anywhere in
the package. This feels a bit wrong though as they are really
meant to be protected; this is also an exploit of the D-specific
"package" qualifier so e.g. one wouldn't be able to do something
like this in C++.
/* id.d */
class ID {
protected int m_id;
protected this(int id) {
m_id = id;
}
int id() @property const {
return m_id;
}
}
/* group.d */
import c_library : get_dataset;
import id : ID;
import dataset : Dataset;
class Group : ID {
package this(int id) { super(id); } // <-- package
public this(...) { // high-level public ctor }
Dataset getDataset(string name) const {
int dataset_id = get_dataset(this.id, name);
return Dataset(dataset_id);
}
/* dataset.d */
import c_library : get_group;
import id : ID;
import group : Group;
class Dataset : ID {
package this(int id) { super(id); } // <-- package
public this(...) { // high-level public ctor }
Group group() const {
int group_id = get_group(this.id);
return Group(group_id);
}
}
--------------------------
Solution 2: use UFCS and swap the group() / dataset(name)
functions between the two modules. This way, e.g. group() will
have access to protected Group.this(id) due to being in the same
module. However, now there's a different problem: if you "import
group", you won't be able to do a "group.dataset(name)" due to it
being in a different module, so a public import is required to
fix that -- which also feels a bit ugly (what if there are 15
different modules and not 2, would they all have to
cross-public-import each other?). This is also a D-specific
exploit so it again wouldn't be possible in C++.
/* group.d */
import c_library : get_group;
import id : ID;
public import dataset; // <-- public import due to UFCS
class Group : ID {
protected this(int id) { super(id); } // <-- protected
}
Group group(in Dataset dataset) {
return Group(get_group(dataset.id));
}
/* dataset.d */
import c_library : get_dataset;
import id : ID;
public import group; // <-- public import due to UFCS
class Dataset : ID {
protected this(int id) { super(id); } // <-- protected
}
Dataset dataset(in Group group, string name) {
return new Dataset(get_dataset(group.id, name));
}
--------------------------
Solution 3: add static methods like Dataset::fromGroup(name) and
Group::fromDataset() and then do something like this:
/* group.d */
class Group : ID {
...
static typeof(this) fromDataset(in Dataset dataset) {
return new typeof(this)(get_group(dataset.id));
}
}
/* dataset.d */
class Dataset {
...
Group group() @property const {
return Group.fromDataset(this);
}
}
However, this essentially leads to duplications of every such
lookup methods, so if there are many such entities, there will be
a whole bunch of such static methods..
--------------------------
Is this a completely normal situation or is the design flawed and
there's a clean way around it?
Thanks!