I am creating a strong binding of an XML specification and I am essentially trying to "flatten" the specification as much as possible, so that it won't feel like you are manipulating an XML tree.
There are many elements in the XML specification that are optional. Here is a contrived example.
<person>
<name>Fred</name>
<favorite-color>Blue</favorite-color>
</person>
In this the "name" element might be required, but the "favorite-color" element might say minOccurs=0 in the xsd making it optional.
I have two ideas of how to handle this, neither of which seems particularly elegant.
In Version1 you must test for nullptr before dereferencing getFavoriteColor's return to see if FavoriteColor is being used by your Person object. This seems like a bad approach because it makes it ridiculously easy to crash your program, especially because it might be hard to keep track of which members are optional and which are required. You may end up having to test everything for null all the time. This seems terrible.
In Version2 you can assume that the getters will not return nullptr's. You must check the "HasFavoriteColor" bool to find out whether or not FavoriteColor exists. But the tradeoff is that it seems to violate the rule of least surprise. You may setFavoriteColor( "Blue" ) then be surprised when your XML output doesn't show Blue because you forgot to setHasFavoriteColor( true ).
Which of these designs seems better, or at least more idiomatic? Is there a better solution?
Research: this is similar but doesn't fit https://stackoverflow.com/questions/3027067/optional-member-objects This is similar, but again doesn't seem to fit Checking for the presence of optional properties of an object
Also, I discovered that boost::optional exists, but that doesn't seem like a fit and I am not using anything but std and STL for my project.
(Assume "Name" and "FavoriteColor" below are user classes and not just typedefs of std::string.)
using Name = std::string;
using NamePtr = std::shared_ptr<Name>;
using FavoriteColor = std::string;
using FavoriteColorPtr = std::shared_ptr<FavoriteColor>;
/* Version 1: nullptr indicates FavoriteColor is absent,
but Name will never be null because it is required */
class Person
{
public:
Person()
:myName( std::make_shared<Name>() )
,myFavoriteColor()
{}
NamePtr getName() const { return myName; }
void setName( const NamePtr& value )
{
if( value )
{
myName = value;
}
}
FavoriteColorPtr getFavoriteColor() const { return myFavoriteColor; }
void setFavoriteColor( const FavoriteColorPtr& value ) { myFavoriteColor = value; }
private:
NamePtr myName;
FavoriteColorPtr myFavoriteColor;
};
/* Version 2: FavoriteColor and Name can both
be trusted to not be null, but you have to
check HasFavoriteColor bool to see whether
the optional data is legit */
class Person2
{
public:
Person2()
:myName( std::make_shared<Name>() )
,myFavoriteColor( std::make_shared<FavoriteColor>() )
{}
NamePtr getName() const { return myName; }
void setName( const NamePtr& value )
{
if( value )
{
myName = value;
}
}
FavoriteColorPtr getFavoriteColor() const { return myFavoriteColor; }
void setFavoriteColor( const FavoriteColorPtr& value )
{
if ( value )
{
myFavoriteColor = value;
}
}
bool getHasFavoriteColor() const { return myHasFavoriteColor; }
void setHasFavoriteColot( const bool value ) { myHasFavoriteColor = value; }
private:
NamePtr myName;
FavoriteColorPtr myFavoriteColor;
bool myHasFavoriteColor;
};
mHasFavoriteColor = true;
in thesetFavoriteColor()
function. The advantage of setters over direct member access is that functions can maintain class invariants.