OOP Puzzle
This is a complex OOP puzzle. Part of the complexity is that only you can define for yourself, by means of your integrity and understanding, what exactly the problem is with the example. Like in the real world, it all depends on how you grasp the deficiency, the inherent ugliness.
The code here is often fragmentary, to avoid showing anything but what's necessary for understanding that step as the explanation develops. It won't compile, but the clarity is better.
With all that being said, the situation seems to boil down to 2 classes,
Ok, there's a
Now the
We don't want to force the user to build alternative worker classes (derived from some common worker interface) if the original
Now assume that the overridden
In a way this might be sort of misleading, chicken or the egg; so let's try again - alternate universe. Maybe you can as perfectly well say that there's this
Whichever way you think about it,
Ok, now the
But here's the crux: the
One way of screwing things up still further is by putting fake write-accessors (which do absolutely nothing) into the
A worse solution is to try substituting functions which look like the complex methods from
You could go for broke and do both bad solutions. You could vote forGWB, too.
This just seems evil. Calling fake functions is bad. Trying to turn a simple write-accessor into a complex process of function calls is wicked. It would be most excellent if
That's where the puzzle ends. Pick your windmill.
The code here is often fragmentary, to avoid showing anything but what's necessary for understanding that step as the explanation develops. It won't compile, but the clarity is better.
With all that being said, the situation seems to boil down to 2 classes,
Worker and State, of which each appears to have 2 interfaces (read-accessors & active methods; read-accessors & write-accessors), but only 1 of each pair of interfaces is the same (the read-accessors). And that is only one possible explanation of 3 that I've come up with so far for the while scenario! Ok, there's a
Client class (the UI, say) and a Worker class. They are not inherited one from another. No worry.
class Client;
class Worker;
Worker has a very complex set of methods to do something intricate, and a few read-accessors to let the world check 'the state' of the Worker, some assorted bunch of private variables. The Client calls the Worker's methods to make things happen (thus altering the state in some complex manner thereby) and later reads the Worker's internal state via the accessors as things change. Everything's jolly.
class Worker {
public:
bool getStatePart1() const;
bool getStatePart2() const;
bool getStatePart3() const;
bool getStatePart4() const;
void doStep1();
void doStep2();
void doStep3();
void doStep4();
void doStep5();
void doStep6();
};
Now the
Client would sometimes like to be able to have other code doing the Worker's task, so the Client is not wired directly to the Worker class. The Worker object just inserts nicely when the Client gets overridden. Specifically: in a new subclass of Client, Client's pure virtual functions are sometimes written to call the methods of the Worker object, but if and only if the Worker class gets used. We don't want to force the user to build alternative worker classes (derived from some common worker interface) if the original
Worker class itself is not used: we'll just let the user throw some appropriate code into the Client virtual functions to get the job done. Things are mostly still good.
class Client{
public:
protected:
virtual void OverrideMe1() = 0;
virtual void OverrideMe2() = 0;
virtual void OverrideMe3() = 0;
virtual void OverrideMe4() = 0;
virtual void OverrideMe5() = 0;
};
class MyClient : public Client{ // this is vague. but the idea is that the virtuals
// in Client don't match up nicely to the things in Worker
private:
Worker * theWorker; // could get new()'d in the constructor,
// or inserted from the outside: MyClient(Worker * )
protected:
void OverrideMe1(){
theWorker->doStep3(); // if we're doing it without Worker,
// there'd be something else here
}
// etc.
};
Now assume that the overridden
Client isn't using the lovely Worker class this time. It still needs the state stuff to keep track of what's going on, even if the Client only provides that information itself as part of doing the work. So we'll make a simple little State class. The State class also has those nice accessors to let the Client read it (this is where the interface of State class and Worker class overlap), but it also has a new group of write-accessors to set and change the complex state in lieu of the actual functional methods in Worker. This time, the overridden Client uses the write-accessors, instead of all the complex methods that Worker had (and which the State class doesn't have), to set the state from the outside as it goes along.
class State {
protected:
bool b1, b2, b3, b4;
public:
bool getStatePart1() const;
bool getStatePart2() const;
bool getStatePart3() const;
bool getStatePart4() const;
void setStatePart1(bool);
void setStatePart2(bool);
void setStatePart3(bool);
void setStatePart4(bool);
};
class MyClient : public Client{
protected:
void OverrideMe1(){
// do something clever her
b2 = false;
// more cleverness
if(thingsWork()) b3 = true;
// still more
}
// etc.
};
In a way this might be sort of misleading, chicken or the egg; so let's try again - alternate universe. Maybe you can as perfectly well say that there's this
Client that dabbles with a complex state, and so here's an elegant State class to use: read and write. And if on a certain Tuesday you do indeed want to use a Worker class - which coincidently keeps track of the same state even better than State does - the Worker object can wiggle itself into the State object so that the reads on State actually read Worker, and the direct writes do nothing at all (because Worker relies on its complex operations to change state). Weird, but possible still.
class State {
private:
Worker * SubstituteThis;
public:
bool getStatePart1() const{
if(SubstituteThis){ // pointer to a possible Worker object
return(SubstituteThis->getStatePart1());
} else {
return(b1);
}
}
void setStatePart1(bool b){
if( ! SubstituteThis){ // pointer to a possible Worker object, which if
// present prevents this function from doing anything at
// all
b1 = b;
}
}
void SubstituteState(Worker * w){ // this causes State to defer to a Worker thereafter
SubstituteThis = w;
}
};
Whichever way you think about it,
Worker first or State first, it'll still come to the same puzzle. And we're almost there.Ok, now the
State class and the Worker class both share the read-accessors. So let's make a base class of that common stuff, and inherit it by both classes. The Client class can now reliably use the read-accessors because it knows the ReadAccessors parent we just made, and both Worker and State are now guaranteed to provide read-accessors. Which is to say: Client can be wired fairly tight (the Client no longer has to be overridden in these particulars) to call the read-accessors through a pointer to the common superclass, and not care if the pointer is actually aimed at a Worker or a State. Fine.
class ReadAccessors {
protected:
bool b1, b2, b3, b4;
public:
bool getStatePart1() const;
bool getStatePart2() const;
bool getStatePart3() const;
bool getStatePart4() const;
};
class State : public ReadAccessors;
class Worker : public ReadAccessors;
class Client {
protected:
ReadAccessors * reads; // with -> can be used to call the read-accessors always
public:
void SetStateHandler(ReadAccessors * r){ // pass in a State or Worker reference
reads = r;
}
};
But here's the crux: the
Worker still doesn't have write-accessors, and the State class still doesn't have all the complex method stuff - this is where it doesn't overlap. Ultimately, you can't treat one class like the other, even though a certain logic says you should be able to, with respect to the general way that these two classes insert into Client.One way of screwing things up still further is by putting fake write-accessors (which do absolutely nothing) into the
Worker class. Now at least you have more overlap: all the things in the State class would now be present in the Worker (even if they don't do anything) and so you could have a common interface or even inherit State into Worker. But Client still has to call the complex methods in Worker anyway to get anything useful done (and change the state meanwhile, since the write-accessors are now placebos). A worse solution is to try substituting functions which look like the complex methods from
Worker into the State class. You'd get a common interface that way, again, but matching Worker. Yet the whole thing might not even be possible if Worker outlines a really bad and hairy process, where changes to the state might have to do with the success or failure of other things - anything other than just the simple call to the Worker-like method.You could go for broke and do both bad solutions. You could vote for
This just seems evil. Calling fake functions is bad. Trying to turn a simple write-accessor into a complex process of function calls is wicked. It would be most excellent if
Worker and State were interchangeable one for the other. But it seems like we're doomed. The interfaces aren't the same, they will never overlap completely, and because OOP is all about that common interface idea, it all goes to hell.That's where the puzzle ends. Pick your windmill.
