I have a class that is supposed to fetch an object from the server.
// T types
struct RequestLicense
{
// arbitrary data
};
struct RequestTrial
{
// arbitrary data
}
// U types
struct LicenseData
{
// arbitrary data
};
struct TrialData
{
// arbitrary data
}
struct Fetcher
{
template <typename T>
std::wstring FetchBlob(T requestParameters);
template <typename U>
std::unique_ptr<U> DeserializeBlob(const std::wstring& serializedBlob);
template <typename U>
bool ValidateResponse(U* deserializedResponse);
// Many different virtual, helper functions that make use of the types T and U
}
struct LicenseFetcher : Fetcher
{
// overrides for the different helper functions mentioned in Fetcher
}
struct TrialFetcher : Fetcher
{
// overrides for the different helper functions mentioned in Fetcher
}
// specialization FetchBlob with T=RequestLicense
// specialization for DeserializeBlob and ValidateResponse with U=LicenseData
// specialization FetchBlob with T=RequestTrial
// specialization for DeserializeBlob and ValidateResponse with U=TrialData
// specializations for many of the helper functions. 2 specializations for each of the functions--one RequestLicense/LicenseData and one for RequestTrial/TrialData.
Normally I would make the functions virtual, but I can't since they are templated. As I see it, I have two options: 1) proceed with the route I am on or 2) remove the templating and leverage downcasting.
I was thinking about creating LicenseBase
that LicenseData
and TrialData
would inherit from. As LicenseData
and TrialData
have nothing in common, LicenseBase
would be empty. I would then replace all references to the template type U
with a pointer to LicenseBase
. Since each derived class would be operating on a known type, it would be safe to downcast within each overriden function. e.g. LicenseFetcher::ValidateResponse(LicenseBase* r) { const LicenseData& data = static_cast<const LicenseData&>(r); }
I would do something similar for the Request*
types.
I know downcasting is frowned upon and it also wouldn't help when I am returning one of these types (like with DeserializeBlob
) but I was wondering about future development. All this templatization seems a bit hard to follow, and downcasting would make it easier to read. Also, with the templated approach, Fetcher.h
will grow quite large as it will contain a specialization for every function for each derived class. I already have a half-dozen functions that would need specialization and a half-dozen derived versions of Fetcher
. As I type this out, templating seems like the right way to go, but I've been doing some templatization recently and a bit worried I'm starting to "view every problem as a nail."
Are there any options I'm missing? Or should I forge ahead with templating?
Edit: Regardless of the approach I take, I plan on having non-member functions like GetLicense
and GetTrial
that would look something like
LicenseData GetLicense()
{
const RequestLicense params = RequestLicense(/*arbitrary parameters*/);
std::wstring response = FetchBlob(params);
std::unique_ptr<LicenseData> data = DeserializeBlob<LicenseData>(response);
if (ValidateResponse<LicenseData>(data))
{
return data;
}
return {};
}
CodePudding user response:
I think I would make the whole Fetcher
structure a template, with the member functions being abstract virtual functions:
// R is the request type
// D is the data type
template <typename R, typename D>
struct Fetcher
{
virtual std::wstring FetchBlob(R const& requestParameters) = 0;
virtual std::unique_ptr<D> DeserializeBlob(const std::wstring& serializedBlob) = 0;
virtual bool ValidateResponse(D const& deserializedResponse) = 0;
// ...
};
Then when inheriting from it pass the specific types:
struct LicenseFetcher : public Fetcher<RequestLicense, LicenseData>
{
std::wstring FetchBlob(RequestLicense const& requestParameters) override
{
// TODO: Implementation
}
std::unique_ptr<LicenseData> DeserializeBlob(const std::wstring& serializedBlob) override
{
// TODO: Implementation
}
bool ValidateResponse(LicenseData const& deserializedResponse) override
{
// TODO: Implementation
}
// ...
};
Would be much cleaner than having a multitude of specializations.