In fact, you did not describe the design problem. You described some of the implementation options that you encountered, and the roadblock that you have but we don’t know the reasons for choosing.
You tell us that Algo takes control of the business with a pointer to the AbstractBusiness polymorphic interface and must provide a getter for the business data, although it does not know the specific type of this data (since it does not know the specific type of business).
None of these questions have explicit answers: -
- Why would
Algo acquire a business through a polymorphic interface? - Why
Algo provide the recipient with information about its activities?
But the solution to this should be in a way that leads to a checkpoint.
Polymorphic pothole and how to get out
Q1. makes us wonder what is the motivation for AbstractBusiness ? Along with this, it is safe to say that you want it to provide a single interface for manipulating and polling all kinds of specific types that can be determined at run time.
To be fully suitable for this purpose, AbstractBusiness will encapsulate the necessary and sufficient interface to perform all operations and requests in specific enterprises that can reasonably be expected from applications (including, but not limited to). Call this plan A. What you discovered is that it is not completely suitable for plan A. If the application occasionally needs to manipulate or request the “data” of the business presented to it through AbstractBusiness , then the AbstractBusiness interface should provide polymorphic methods for executing of all these manipulations and queries, and each particular business class should implement them accordingly for the type of data it contains.
If your AbstractBusiness has problems:
?unknownType? result();
you need to encode virtual methods that answer all convincing answers to the question: what can the application know about conditional result() or do with it?
In this light, the proposal, which was tested to introduce another polymorphic interface, AbstractData , the ancestors for all specific data types of all specific enterprises, can be considered as a proposal to compensate for the necessary methods that are absent in AbstractBusiness , separately encapsulating them in the abstraction of salvation. Better finish the unfinished AbstractBusiness .
This is all possible and possible in the Bible, but perhaps what really stopped you from completing AbstractBusiness , there is already the perception that BusinessX data can differ significantly from BusinessY data, so it is impossible to develop one set of polymorphic methods that necessary and sufficient to control both.
If so, then this suggests that business management cannot be carried out using a single abstract interface. AbstractBusiness cannot be fully suitable for this purpose and, if it has a role, its role can only be for managing polymorphic objects that represent more specialized abstractions, BusinessTypeX , BusinessTypeY , etc., in each of which there is diversity, if any specific types can be performed using a single polymorphic interface.
AbstractBusiness will only present an interface that is shared by all enterprises. It will not have result() and the caller who receives a pointer to AbstractBusiness with the intention of doing something with the thing returned by BusinessTypeX::result() will continue by dynamically translating the source pointer to BusinessTypeX * and calling result() through target pointer only if its value is not null.
We still do not know what is the motivation of AbstractBusiness . We simply pursued a rather plausible idea that you have the ambitions of a “textbook” for her - plan A - and either did not realize that you simply did not finish, or you realized that the variety of data that you work with does not allow you complete it on one plan A and not have plan B. Plan B: Extend the polymorphic hierarchy and use dynamic_cast<LowerType *>(HigherType *) to provide secure access to the LowerType interface when it is ahead of HigherType alone. [1]
Turn Q2. Now. Most likely, the reason for Algo::result() is simple: because it is a done thing for the class to provide recipients who directly respond to the customer’s natural requests, in which case the natural request is for data belonging to the Algo owned business. But if Algo knows his business only as AbstractBusiness , then he simply cannot return the data belonging to his business, because already visible reasons mean that AbstractBusiness cannot return the "data" to Algo or anything else.
Algo::result() is incorrectly understood as AbstractBusiness::result() is incorrectly understood. Given that BusinessX and BusinessY data can be requested either through some repertoire of virtual methods that are still TODO in AbstractBusiness (plan A), or perhaps using BusinessX and BusinessY methods that are not inherited from AbstractBusiness at all (Plan B), the only query that Algo , of course, can and should be maintained in respect of his company - is to return a pointer AbstractBusiness through which he owns his business, giving the caller a request through a pointer or lowering it, if they can, to the interface of a lower class, which needed. Even if you can finish AbstractBusiness with Plan A, the idea is that the missing method report should be duplicated in the Algo interface just so that the caller never has to get and drop the AbstractBusiness uncompelling pointer. Will every type that controls the AbstractBusiness pointer follow?
To summarize so far, if AbstractBusiness has good reason to exist, you need to either finish it on one plan A, or work as a result of such actions, or limit it to insufficient to be a sufficient interface for managing all enterprises and strengthening it with an enriched polymorphic The hierarchy maintained by customers through dynamic casting for plan B and in any case, you should be satisfied with Algo and similar jobs in the AbstractBusiness trade in order to return their AbstractBusiness index to customers who specialize in it.
Better not there
But the question of whether AbstractBusiness compelling reason to exist is still dangling, and if you find yourself involved in Plan B, which in itself will make the question more acute: when it turns out that the abstract interface, the root class of a single inheritance hierarchy, cannot deliver plan A, then there is doubt about the wisdom of the architecture that he depicts. Dynamic casting for detecting and receiving interfaces is a clumsy and expensive flow control mode, especially unpleasant when, as you say, this is your situation - the area that will need to perform downcasting rigmarole already knows what type it should "go out" type, which he "invested". Should all types that are of imperfect origin from root abstraction have a single ancestor for a reason different from the uniformity of the interface (since this does not give them)? Saving common interfaces is an ongoing goal, but is run-time polymorphism right or even one of the right ways to implement them in the context of your project?
Your AbstractBusiness code sketch does not use an end goal, but for a type that can evenly fill certain slots in the Algo class, so that Algo can work correctly on any type that has certain traits and behaviors. As stated, Algo only requires a qualification type requirement that it must have a result() method that returns something: it doesn't care. But the fact that you express Algo requirements for a qualification type, indicating that it must be AbstractBusiness , prevents it from caring that result() returned: AbstractBusiness cannot make this result() method, although any of its descendants.
Suppose in this case you put AbstractBusiness out of the job of providing common type attributes that Algo can run on, and let Algo do this instead, creating a template? - since it looks like what AbstractBusiness does for Algo , serves for the template parameter, but sabotages this goal:
#include <memory> template<class T> class Algo { std::unique_ptr<T> concreteBusiness_; public: explicit Algo(T * concreteBusiness) : concreteBusiness_{concreteBusiness}{}; auto result() { return concreteBusiness_->result(); } }; #include <valarray> #include <algorithm> struct MathBusiness { std::valarray<float> data_{1.1,2.2,3.3}; float result() const { return std::accumulate(std::begin(data_),std::end(data_),0.0); } }; #include <string> struct StringBusiness { std::string data_{"Hello World"}; std::string result() const { return data_; } }; #include <iostream> int main() { Algo<MathBusiness> am{new MathBusiness}; auto ram = am.result(); Algo<StringBusiness> as{new StringBusiness}; auto ras = as.result(); std::cout << ram << '\n' << ras << '\n'; return 0; }
You see, that in such a way as to transfer general information from AbstractBusiness to Algo , the first one remains completely redundant and therefore deleted. This is a brief illustration of how the introduction of patterns has changed the game of the root and branches of the C ++ design, which makes polymorphic designs inspectors for most of their previous applications for creating common interfaces.
We work from the sketch of your problematic context: there may be good reasons why AbstractBusiness cannot exist. But even if they exist, they alone are not grounds for Algo not to be a template or to have any dependence on AbstractBusiness . And, perhaps, they can be individually eliminated by similar treatment methods.
Creating Algo in a template may still be an impractical solution for you, but if it is not, then the problem is much larger than we saw. And somehow cancel this rule: Templates for common interfaces; polymorphism for adapting interface behavior in real time.
[1] What might look like another plan is to encapsulate the "data" of each specific business in boost::any or std::experimental::any . But you can probably see right away that this is essentially the same as the idea of encapsulating data in an abstraction for salvation using the abbreviation of the Swiss army, rather than creating your own. In any guise, the idea still leaves the defiant to reduce the abstraction to a type of real interest, to find out if this is what they have, and in this sense is a variant of plan B.