I have been stuck on this problem for two weeks now, any help would mean a lot.
Suppose I have a base class called request
and another called reply
:
class request
{
public:
virtual ~request() = default;
request() = default;
};
class reply
{
public:
virtual ~reply() = default;
reply() = default;
};
Now I have two classes inheriting from request
:
class temperature_request : public request
{
public:
temperature_request() = default;
};
class tweets_request : public request
{
public:
tweets_request() = default;
};
Each request has its own type inheriting from reply
:
class temperature_reply : public reply
{
public:
temperature_reply() = default;
};
class tweets_reply : public reply
{
public:
tweets_reply() = default;
};
I have another class called replier
that distributes the requests on the APIs and caches the responses from the API, the API inherit from class api:
class api
{
public:
virtual ~API() = default;
virtual reply* get_reply( request* req) const = 0;
}
class temperature_api : public api
{
virtual reply* get_reply( request* req) const override
{
temperature_request* req_casted = static_cast<temperature_request*>(req);
const std::string& units = req_casted->units();
temperature_reply* rep = new temperature_reply(20, req);
return temperature_reply;
}
}
class tweets_api : public api
{
virtual reply* get_reply( request* req) const override
{
tweet_request* req_casted = static_cast<tweets_request*>(req);
const std::string& user = req_casted->user();
const std::string msg = "This is too much code"
const std::size_t followers = 9028923;
tweet_reply* rep = new tweet(msg, followers, req);
return tweet_reply;
}
}
class replier
{
public:
virtual ~replier() = default;
replier() = default;
reply* get_reply(const request* req)
{
const std::string source = req->source_api();
if(source == "temperature_api")
return m_temperature_api->get_reply(req);
if(source == "tweets_api")
return m_tweets_api->get_reply(req);
throw("nan");
};
private:
std::vector<request*> m_already_requested;
std::vector<reply*> m_already_replied;
temperature_api* m_temperature_api; //suppose initialized
tweets_api* m_tweets_api; //suppose initialized
};
Now even to use this I have to cast:
const std::string user = "XYZ ABC"
tweets_request* req = new tweets_request(user);
reply* rep = replier->get_reply(req);
tweets_reply* real_rep = static_cast<tweets_reply*>(rep);
const std::string msg = real_rep->message();
I am using shared pointers, so don't worry about memory leaks and all other implementation problems, please focus on casting and design :).
My problem is that, with this design, I will be casting all over the place, any reply
should be casted to the correct type (temperature_reply
or tweets_reply
) in order to process it further (as in my last example), depending on the request
type given to replier::get_reply()
, which I cache (not impelemented for now).
I personally hate casting (my opinion), so please, could you help me redesign this so I have no casting, and support caching?
Please, I am stuck on this, I feel that inheritance go hand in hand with casting at this time, which I hope is not true. Could you please, please help?
CodePudding user response:
The crux of the problem is that you are simply not utilizing polymorphism to its full potential. Yes, you have virtual destructors, good for you. That is important. But, you are not using virtual methods to tie your related classes together in a polymorphic way.
Try something more like this instead:
class reply;
class request
{
public:
request() = default;
virtual ~request() = default;
virtual std::shared_ptr<reply> create_reply() = 0;
};
class reply
{
public:
reply() = default;
virtual ~reply() = default;
};
class reply_type_one : public reply
{
public:
reply_type_one() = default;
};
class request_type_one : public request
{
public:
request_type_one() = default;
std::shared_ptr<reply> create_reply() override {
return std::make_shared<reply_type_one>();
}
};
class reply_type_two : public reply
{
public:
reply_type_two() = default;
};
class request_type_two : public request
{
public:
request_type_two() = default;
std::shared_ptr<reply> create_reply() override {
return std::make_shared<reply_type_two>();
}
};
class replier
{
public:
replier() = default;
virtual ~replier() = default;
std::shared_ptr<reply> get_reply(const std::shared_ptr<request> &req)
{
auto iter = std::find(m_already_requested.begin(), m_already_requested.end(), req);
if (iter != m_already_requested.end())
return *iter;
auto reply = req->create_reply();
m_already_requested.push_back(req);
m_already_replied.push_back(reply);
return reply;
}
private:
std::vector<shared_ptr<request>> m_already_requested;
std::vector<shared_ptr<reply>> m_already_replied;
};
Though, in the latter case, a std::unordered_map
would make more sense than 2 std::vector
s:
class replier
{
public:
replier() = default;
virtual ~replier() = default;
std::shared_ptr<reply> get_reply(const std::shared_ptr<request> &req)
{
auto iter = m_cache.find(req);
if (iter == m_cache.end())
{
auto reply = req->create_reply();
iter = m_cache.insert(std::make_pair(req, reply)).first;
}
return *iter;
}
private:
std::unordered_map<std::shared_ptr<request>, std::shared_ptr<reply>> m_cache;
};
CodePudding user response:
Cast is inevitable since you don't know the type until runtime.
However if you just want to get rid of cumbersome casts, then you may use CRTP to make an getDerivedType()
to reduce the code.
At least you don't have to code the static_cast()
part when you need to create another derived class.
Note: CRTP bloats the code. If possible, I suggest to use a simple helper class to append this helper function instead of adding it to your original Base
class.
#include <iostream>
#include <vector>
#include <string>
class BaseReq{
};
class TweetReq: public BaseReq{
public:
int getFollowers(){
return 42;
}
};
template<typename DerivedReply>
class BaseReplier{
public:
virtual std::string somework(BaseReq* req) = 0;
protected:
DerivedReply* getDerivedReq( BaseReq* reply ){
return static_cast<DerivedReply*>(reply);
}
};
class TweetReplier: public BaseReplier<TweetReq>{ // <--- inject the derived request type you want to cast.
public:
std::string somework(BaseReq* req) override {
int followers = getDerivedReq(req)->getFollowers();
return std::to_string(followers);
}
};
int main()
{
TweetReq req;
TweetReplier replier;
std::cout << replier.somework(&req) << "\n";
return 0;
}