Why RTTI is considered as a bad design?

N

Noah Roberts

Rest of it makes sense but get_locks() makes interface that forces users of it to assume that there may be lot of them and iterate there. When actually either one or none are required then it adds unneeded complexity?

How so? You need to explain what you mean by "complexity". If you're talking order complexity I don't see how it's added unless it's actually needed.. If you're talking about verbosity then I have two things to say:

1) verbosity is not necessarily a bad thing
2) it's quite simple and easy to write a reusable function that works for the generic, "unlock_door" functionality.

Furthermore, this is not "unneeded complexity" regardless of how you definecomplexity. It's needed to have a clean, extensible and robust design. This kind of design is always better than one that is not clean, extensible,or robust. It's intuitive in that it matches the real world fairly well, and there's nothing about it that would make it difficult for a programmer to use, extend, and maintain. All of these are more important than almost every other concern most of the time. There are times when you've really got to worry about performance or something like that, but you should profile before trying to second guess. I don't believe this design would show upon a profile as any kind of bottleneck as it doesn't add any order complexity when there's 1 or fewer locks.

One could easily argue for a composite lock solution that puts off adding the multiple lock interface to some time in the future when and if it's needed. It's a reasonable approach that just puts the "unlock_door" loop within a lock composite. Going this route one might be tempted to add a "null lock" that's never engaged. Lots of directions one could go from the point of deciding upon composition here.

Inheritance just seems like the wrong choice here that needlessly locks thefuture of the code down, provides no added benefit, and requires cross-casting, which should make any good designer question it...it's just not the right way to use interfaces.
 
Ö

Öö Tiib

How so? You need to explain what you mean by "complexity". If you're talking order complexity I don't see how it's added unless it's actually needed. If you're talking about verbosity then I have two things to say:

1) verbosity is not necessarily a bad thing
2) it's quite simple and easy to write a reusable function that works forthe generic, "unlock_door" functionality.

I mean complexity of a relation in general. Lets let the doors rest for a second. If the relation is one_to_one then it is most simple to implement and most safe and efficient to use. One_to_maybe_one is slightly more complexrelation. Unless it is clear compile time (if it is one or none) it adds some overhead to traverse it. One_to_any_number is more complex relation than previous. Anything traversing that relation has to support the whole complexity. Why all relations are not any_number_to_any_number? Because it is unneeded complexity.
Furthermore, this is not "unneeded complexity" regardless of how you define complexity. It's needed to have a clean, extensible and robust design. This kind of design is always better than one that is not clean, extensible, or robust. It's intuitive in that it matches the real world fairly well, and there's nothing about it that would make it difficult for a programmer to use, extend, and maintain. All of these are more important than almost every other concern most of the time. There are times when you've reallygot to worry about performance or something like that, but you should profile before trying to second guess. I don't believe this design would show up on a profile as any kind of bottleneck as it doesn't add any order complexity when there's 1 or fewer locks.

Again, better let the doors and their problem domain rest. I brought it as an example just to avoid having abstract base class A's and base class B's.It typically happens that there is functionality of A that suddenly needs to take into account that descendents of it have gained functionality B that somehow interfere with that. The requirement is that A can not just fail but has to try to overcome it. Given RTTI example was how the situation is sometimes resolved with few lines of code. Nothing criminal there. Maybe try to pack up your words with code example?

I know that it can not be resolved elegantly by adding lists of members into A instead of its inheritance in general several classes have to be rewritten. Also I have seen the best case examples from books and mess that it produces in practice. It will not be efficient. Most flexible thing is probably "a graph of optional anythings", but it is never used. Why? Because it is unneeded complexity for any problem domain not clean and robust solution.
One could easily argue for a composite lock solution that puts off addingthe multiple lock interface to some time in the future when and if it's needed. It's a reasonable approach that just puts the "unlock_door" loop within a lock composite. Going this route one might be tempted to add a "nulllock" that's never engaged. Lots of directions one could go from the point of deciding upon composition here.

One can go from simple code actually in lot more directions. Present unneeded one_to_any_number relation makes changing it more complex.
Inheritance just seems like the wrong choice here that needlessly locks the future of the code down, provides no added benefit, and requires cross-casting, which should make any good designer question it...it's just not theright way to use interfaces.

How can it lock something down? All code is written like drawn on sand, nothing about it is like carved onto rock so nothing can lock any future down.Lot of code (big drawing) will make changing it more time consuming and raise unneeded difficulties and inefficiencies and that is it.
 
N

Noah Roberts

Maybe try to pack up your words with code example?

struct lock {
virtual ~lock() {}
virtual void unlock() = 0;
virtual bool engaged() const = 0;
};

struct null_lock : lock {
void unlock() {}
bool engaged() const { return false; }
};

struct single_lock : lock {
void unlock() { state = UNLOCKED; }
bool engaged() const { return state == LOCKED; }
};

// can be added later...
struct multi_lock : lock {
// probably have this syntax wrong...
void unlock() { for ( lock & current_lock : locks) current_lock.unlock();}

bool engaged() const {
return find_if(locks.begin(), locks.end(), bind(&lock::engaged, _1)) != locks.end();
}
iterator begin();
iterator end();
private:
?? locks;
};

struct door {
virtual ~door() {}
bool open() {
if (lock()->engaged()) return false;
state = OPEN;
return true;
}
lock* lock();

private:
lock my_lock;
door_state state;
};


Use code:

void go_through_door(door & entry) {
entry.lock()->unlock();
entry.open();
// go through...
}

Looks pretty darn simple to me. Compared to:

// may be a few lines of code smaller...but not much.
struct door { ... };
struct lockable { .... };
struct lockable_door : door, lockable { ... };

// but you pay for it here...
void go_through_door(door & entry) {
if (auto lock = dynamic_cast<lockable*>(&entry))
lock->unlock();
entry.open();
};

I contend that the first version is easier to read, easier to work with, results in simpler client code, does not fall prey to ANY design policy violations as it pertains to current requirements, and should be preferred underany condition I can conceive of. It's even possible that it would outperform the second, though I don't envision by much.
How can it lock something down? All code is written like drawn on sand, nothing about it is like carved onto rock so nothing can lock any future down. Lot of code (big drawing) will make changing it more time consuming and raise unneeded difficulties and inefficiencies and that is it.

I follow SOLID principles in design. I'd suggest reading them and reading about why they result in better designs, particularly the open closed principle. Basically code very often DOES end up being carved into rock becausethe cost of change becomes too high. The more closely you can adhere to SOLID principles the less this will be the case.

The point isn't to get all worked up about it. The difference in this caseis rather minor. The concept extends well beyond simple cases like this though and the advice to watch out for RTTI is reasonable. RTTI based code often violates some important design principle and in so doing will often result in code that is difficult to change and prone to error. RTTI based solutions can usually be replaced by better solutions that neither use RTTI nor violate design principles and they're often better in other respects aswell. Like most things though this is not an absolute.
 
N

Noah Roberts

I mean complexity of a relation in general. Lets let the doors rest for asecond. If the relation is one_to_one then it is most simple to implement and most safe and efficient to use. One_to_maybe_one is slightly more complex relation. Unless it is clear compile time (if it is one or none) it addssome overhead to traverse it. One_to_any_number is more complex relation than previous. Anything traversing that relation has to support the whole complexity. Why all relations are not any_number_to_any_number? Because it isunneeded complexity.

Your version is not a one_to_one BTW. What you've done is implement one_to_maybe_one by requiring the client code to attempt a cast to lockable for the check.
 
Ö

Öö Tiib

Your version is not a one_to_one BTW. What you've done is implement one_to_maybe_one by requiring the client code to attempt a cast to lockable forthe check.

Usually such cross casting is done by base class, not client code. Something like getLockable() of Door does the dynamic_cast. Also it sometimes is turned one_to_one for robustness of usage by returning pointer to some staticdummy (like your null_lock) when dynamic_cast results with NULL pointer.

The outcome is roughly equal I see now. Some code is better than 1000 words.. One has cross-cast other has a pointer member. More or less a tie for me in that problem domain. cross-cast has inherent flexibility when such "Lockable" needs to provide on its order some "Openable" when it becomes required that attempts to lock it may not fail with excuse that the door was open.Your design can overcome it with more pointers perhaps.
 
N

Noah Roberts

In general yes one should prefer composition over inheritance but the

proper way to do it using inheritance is really quite simple:



struct door : lockable

{

bool open()

{

if (!unlock())

return false;

}

virtual bool unlock()

{

/* ordinary doors have no locks */

return true;

}

};

As has already been discussed, having the base class contain an unlock function just so that you can call the behavior in a base class is not good design. You'll start to see the problem, perhaps, when you consider how you'dimplement:

struct sliding_door : door { ... };

Once again, some will have locks and some won't. Going to have two classesfor this too?? Tiib's runs into the same difficulty. It gets even worse when you start having many different kinds of locks and what you end up getting is subclass explosion and often this comes with a lot of redundant copy-pasta.

The recommended pattern to combat this sort of problem is the bridge. That's basically what the door->lock relation creates.
 
N

Noah Roberts

The outcome is roughly equal I see now. Some code is better than 1000 words. One has cross-cast other has a pointer member. More or less a tie for me in that problem domain. cross-cast has inherent flexibility when such "Lockable" needs to provide on its order some "Openable" when it becomes required that attempts to lock it may not fail with excuse that the door was open. Your design can overcome it with more pointers perhaps.

Yeah, if you wanted to have the lock check the state of what it locks to make sure it can engage then you'd want a pointer back to the object it locks..

An alternative view would be that a lock can in fact engage with a door open, that door then just may not close. Some locks in fact can engage and allow you to shut the door. Car doors are like this. So you might have trouble implementing the former and it may depend entirely on the kind of lock (deadbolts keep the door from closing).

Funny story on this one...my cousin came to my house with her kid and he likes to lock doors. We left the house, closed the door, and talked as she was leaving. When I turned to go back into my home the door was locked and the keys were inside. I had to kick in my back door. When I bought the replacement I made sure to buy a knob that would not open from the inside while in the locked position.

This door->lock relationship could actually get quite complicated if we wanted to posit the various kinds of locks and doors. That's sort of the point I come from. Although you don't want to try to anticipate all of this crap when it's not needed, you also don't want to lock yourself from being able to implement it easily. A user could very well say, "Hey, that's neat....how about deadbolts or sliding doors?" This can be especially bad if theysuddenly decide this 6 months down the road and you've got all this code betting on the wrong kind of interface. If you've used SOLID principles andthus avoided violating LSP and OCP you'll likely have an easier chance of doing this. Doesn't always work that way, since you can't be open to EVERYTHING, but it at least usually takes care of most of the obvious paths and many of the less obvious.

Yet another way to look at it is from the single responsibility principle. Doors provide ways to open and close entryways. Locks lock things. Keeping them distinct follows this principle as well while a "lockable door" is actually responsible for two things: locking and opening.
 
Ö

Öö Tiib

Yeah, if you wanted to have the lock check the state of what it locks to make sure it can engage then you'd want a pointer back to the object it locks.

An alternative view would be that a lock can in fact engage with a door open, that door then just may not close. Some locks in fact can engage and allow you to shut the door. Car doors are like this. So you might have trouble implementing the former and it may depend entirely on the kind of lock (deadbolts keep the door from closing).

Yes the different abilities interfere with each other. So sometimes it is needed that the whole object orchestrates how the things really go. That is one reason why i hesitate when splitting ability as component.
Funny story on this one...my cousin came to my house with her kid and he likes to lock doors. We left the house, closed the door, and talked as shewas leaving. When I turned to go back into my home the door was locked and the keys were inside. I had to kick in my back door. When I bought the replacement I made sure to buy a knob that would not open from the inside while in the locked position.

This door->lock relationship could actually get quite complicated if we wanted to posit the various kinds of locks and doors. That's sort of the point I come from. Although you don't want to try to anticipate all of this crap when it's not needed, you also don't want to lock yourself from being able to implement it easily. A user could very well say, "Hey, that's neat....how about deadbolts or sliding doors?" This can be especially bad if they suddenly decide this 6 months down the road and you've got all this codebetting on the wrong kind of interface. If you've used SOLID principles and thus avoided violating LSP and OCP you'll likely have an easier chance of doing this. Doesn't always work that way, since you can't be open to EVERYTHING, but it at least usually takes care of most of the obvious paths and many of the less obvious.

Interface is basically same or takes 5 minutes to refactor into same. If the getLockable() returns the result of cross-cast or data member (or one or other depending on type when it is made virtual) makes no external diffrence.
Yet another way to look at it is from the single responsibility principle.. Doors provide ways to open and close entryways. Locks lock things. Keeping them distinct follows this principle as well while a "lockable door" is actually responsible for two things: locking and opening.

That is what i understand but the solution i am not buying. Imagine that i have 100 classes. I may have the abilities split out say there are 50 abilities in ability hierarchy. Each class combines 8 abilities in average. Wheni have rigid classes (a class inherits the abilities) then i have to test them 100 and that is it. When i have flexible (abilities are members) then .... doh i think it is something like 100 000 or more combinations that may make sense.

If i really only need the fixed 100 combos then making them flexible would just mean that my unit-tests run half a week pointlessly. That is second reason why i hesitate refactoring abilities into members without objective need.

Sure, in some domain i may need to go the flexible way and test all that pile of what is possible to make and filter maybe only utter nonsense regardless. For example when it is some framework i want to sell then 100 000 things is nice argument.
 
N

Noah Roberts

That is what i understand but the solution i am not buying. Imagine that i have 100 classes. I may have the abilities split out say there are 50 abilities in ability hierarchy. Each class combines 8 abilities in average. When i have rigid classes (a class inherits the abilities) then i have to test them 100 and that is it. When i have flexible (abilities are members) then ... doh i think it is something like 100 000 or more combinations that may make sense.

If i really only need the fixed 100 combos then making them flexible would just mean that  my unit-tests run half a week pointlessly. That is second reason why i hesitate refactoring abilities into members without objective need.

This isn't how to go about unit testing. Test the door and lock
separately. Use a mock/fake for the lock when you're testing the
door's functionality and if the lock needs to know about the door to
do its thing you do the same there. You don't need to run the full
combination of every possible assignment of lock and door. That's the
whole point of unit testing actually.

Let the next levels, such as integration and functional testing, pick
up anything that results from some specific combination. When and if
such a bug is found it will have something to do with one or both of
the units in question and it will point out something you missed in
your unit test...add it. It is quite normal for integration and
functional testing to take a while, which is another reason to keep
your unit testing small and fast...but complete for each unit.

I don't know why you're talking about refactoring here, I thought we
agreed the assumption was a new product design. I would not refactor
without some feature that can't be made the other way or was being
made more difficult, but I would not design it with inheritance from
the start for the reasons I've mentioned already...all of which ARE
objective reasons that I at least consider "needs" (such as future
maintainability).

Another issue with this conversation has been that you seem to be
taking a very simplistic, poorly thought out example to discuss
theoretical issues and used it specifically. While I've been
explaining ways in which to approach this example in terms that are
universal, you've been harping on this idea that you can make a
function to wrap up the cast. This is true here, but very often NOT
true with other, similar such constructs. Using inheritance as your
primary form of reuse is just plain wrong and leads to brittle,
unmaintainable code. Although this was how people thought OO would be
used when it first came out, it was realized fairly quickly that it
doesn't work that way. Inheritance should only be used for conceptual
organization--is-a relationships--and nothing else.

The problem with using a simplistic example to discuss such a far
reaching concept is that there can always be these, "but I don't need
it in this case," type responses. Unless we really dig into an actual
specification for this example problem we simply cannot know that.
Are we coding some game where doors are simple and they either lock or
don't? Maybe your solution is good enough (I wouldn't ever call it
the right one and would see it as massive overdesign for this
problem). Get much more complicated than that though and your
solution will begin to fall apart quite rapidly and you'll naturally
progress into a compositional relationship, which is simply preferable
to begin with because it follows well established design principles.

Anyway, someone somewhere asked how to do what they were doing without
RTTI and I've provided at least one good way now. Using RTTI isn't
necessarily bad, it's a smell that indicates something possibly bad
and/or that something could be done better. When you see RTTI being
used, look for violations of SOLID principles, which though are called
"OO Principles" extend quite readily into other paradigms like generic
programming.
 
Ö

Öö Tiib

This isn't how to go about unit testing. Test the door and lock
separately. Use a mock/fake for the lock when you're testing the
door's functionality and if the lock needs to know about the door to
do its thing you do the same there. You don't need to run the full
combination of every possible assignment of lock and door. That's the
whole point of unit testing actually.

Whole point of unit testing is to discover some of the defects earlier.
'Earlier' means before they get into repository and before they get into
build and before they start to cause more work to other people.
I am not sure why to split the tests at so low level? For example if design
say a template X<T> should not accept int as argument then why not to make
test that expects a static assert on X said:
Let the next levels, such as integration and functional testing, pick
up anything that results from some specific combination. When and if
such a bug is found it will have something to do with one or both of
the units in question and it will point out something you missed in
your unit test...add it.

Call it 'integration test', 'regression test' or whatever, like we saw
earlier the different abilities affect each other so depending on the
tested situation such test may still need actual or mocking or fake or
dummy code. Since it ensures that code matches with design I call it
'unit test' as well, for simplicity.

If to bring parallel with doors then test 'knock-ability', 'lock-ability',
'pass-ability' and 'open-ability' of whole doors too. In project with some
history such tests contain majority of test code.
It is quite normal for integration and
functional testing to take a while, which is another reason to keep
your unit testing small and fast...but complete for each unit.

Second level test for me after 'unit tests' is 'automatic tests', that
run on whole module and test it with real or generated scenarios.
Usually in environment of fake other modules. It is just to save the
human-testers time. May run constantly somewhere, i did not mean those.
I don't know why you're talking about refactoring here, I thought we
agreed the assumption was a new product design. I would not refactor
without some feature that can't be made the other way or was being
made more difficult, but I would not design it with inheritance from
the start for the reasons I've mentioned already...all of which ARE
objective reasons that I at least consider "needs" (such as future
maintainability).

I am talking about refactoring in the context of your words: "This can
be especially bad if they suddenly decide this 6 months down the road
and you've got all this code betting on the wrong kind of interface."

It happens with all projects that things turn 90 or even 180 degrees with
lot smaller intervals than 6 months. If something is difficult to refactor
then that is major flaw. I am not sure what you mean by wrong kind of
interface, so was just saying that the interface is pretty compatible.
Another issue with this conversation has been that you seem to be
taking a very simplistic, poorly thought out example to discuss
theoretical issues and used it specifically. While I've been
explaining ways in which to approach this example in terms that are
universal, you've been harping on this idea that you can make a
function to wrap up the cast. This is true here, but very often NOT
true with other, similar such constructs. Using inheritance as your
primary form of reuse is just plain wrong and leads to brittle,
unmaintainable code. Although this was how people thought OO would be
used when it first came out, it was realized fairly quickly that it
doesn't work that way. Inheritance should only be used for conceptual
organization--is-a relationships--and nothing else.

Is there a case of that "very often NOT true" where some "interface
getter" can not use cross-cast internally? It feels equal to forwarding a
reference to a member or other aggregate. Sure, lockable door is-a
lockable, what is wrong using inheritance there? Or ... when you see a
door what is wrong about asking "are you lockable?"? If it is wrong in
sense of some general principle of OOD then too bad for OOD. We maybe need
better example to discuss that side?

There are tons of ways how to reuse and combine functionality in C++
into real class. Some assortment of ways (far not all of them):

0) You do not need that functionality. Do not reuse it in any way. Lack
of any code is most simple.
1) Use template. Good template is hard thing to make and to test
sufficiently, but it is extremely efficient and reusable.
2) Forward functionality of non-public base class. If there are no
polymorphism then it is very efficient. Easier than (3) to use, bit
harder to understand.
3) Forward functionality to non-polymorphic data member. When the data
member is template then it is as efficient as (2) otherwise it carries
very little overhead. Easiest to understand.
4) Static polymorphism (CRTP). Very little overhead if any. Adds some
flexibility of (5), but is hardest to understand and to make since
it involves (1) with properties of (5).
5) Usual dynamic polymorphism. With its virtual functions it is bit less
efficient than others above. Somewhat overused, over-taught and
under-understood.
6) Forward functionality to polymorphic data member. That is both most
complex and flexible since polymorphic data member means that it may
change during object's life-time. More overhead than with (5).

Can not be that the SOLID OOD suggests to always use that (6) as very
first choice of functionality re-usage ... it seems least efficient and
not most simple.
The problem with using a simplistic example to discuss such a far
reaching concept is that there can always be these, "but I don't need
it in this case," type responses.

Ok, probably my fault. I did not want to base all considerations of
re-usage of functionality on one simplistic example.
Are we coding some game where doors are simple and they either lock or
don't?

No. I have explained else-thread. It was just used to avoid typical
class A, class B blah-blah too abstract example. I did not want to
introduce some problem with doors. I wanted to bring example of the usage
of cross cast.
As for doors ... yes let them rest ... Possibly the usage of vector of
polymorphic locks is the perfect choice for a door in some game-world.
It may also be that it is overkill and just a data member with fitting
key # is needed in some other game-world. That is why i have said that you
can let the doors to rest.
Anyway, someone somewhere asked how to do what they were doing without
RTTI and I've provided at least one good way now. Using RTTI isn't
necessarily bad, it's a smell that indicates something possibly bad
and/or that something could be done better. When you see RTTI being
used, look for violations of SOLID principles, which though are called
"OO Principles" extend quite readily into other paradigms like generic
programming.

Yes it was me. It was good discussion.
Your suggestion to go to next level and turn inheritance into component
can be correct choice. What i am still discussing is that you seem to
suggest to always design inheritance as component. I have seen it suggestedelsewhere and i do not see it at all as profitable on general case.

The way suggested by Scott and Leigh to combine all cases (to make all Doors
Lockable) was maybe most robust. It turns it into usual polymorphic usage.

The pre_opener of Goran (maybe also your, you mentioned visitor?) was to use
some double-dispatch to get rid of dynamic-cast. That raises complexity and
often feels like wheel-chair. It can work best with legacy code that should
not be modified.

Were there others? Sorry if i missed.

I myself still think that unless it burns (shows signals of under- or
over-engineering) somewhere else ... then that cross-cast there is fine. ;)
 
N

Noah Roberts

Whole point of unit testing is to discover some of the defects earlier.

'Earlier' means before they get into repository and before they get into

build and before they start to cause more work to other people.

I am not sure why to split the tests at so low level? For example if design

say a template X<T> should not accept int as argument then why not to make

test that expects a static assert on X<int>?

You can. You brought up a problem saying you'd get this massive explosion having to test every combination. I was explaining why this is not necessary. Sometimes it is in fact easier to just test with the real client objects than to build mocks.

We could argue about what's the point of unit tests forever I suppose. In my mind it is to make refactoring simpler and to be able to test individualcomponents by themselves, thus making it easier to work on them. Testing whether the program does what it's supposed to do without crashing is QA's job.
Call it 'integration test', 'regression test' or whatever, like we saw

earlier the different abilities affect each other so depending on the

tested situation such test may still need actual or mocking or fake or

dummy code. Since it ensures that code matches with design I call it

'unit test' as well, for simplicity.

Having a hard time figuring out what you mean by "ability". You can changewhat terms mean and call whatever you want "unit test", but then it's pretty damn tough to figure out what you're saying.
Second level test for me after 'unit tests' is 'automatic tests', that

run on whole module and test it with real or generated scenarios.

Usually in environment of fake other modules. It is just to save the

human-testers time. May run constantly somewhere, i did not mean those.

Unit tests should be automated, so I'm again having trouble figuring out what you mean.
I am talking about refactoring in the context of your words: "This can

be especially bad if they suddenly decide this 6 months down the road

and you've got all this code betting on the wrong kind of interface."

My point was to design it the right way to begin with.
It happens with all projects that things turn 90 or even 180 degrees with

lot smaller intervals than 6 months. If something is difficult to refactor

then that is major flaw. I am not sure what you mean by wrong kind of

interface, so was just saying that the interface is pretty compatible.

The wrong interface is one that violates basic principles of OO design for no good reason besides, "I like inheritance better than composition."

There are tons of ways how to reuse and combine functionality in C++

into real class. Some assortment of ways (far not all of them):



0) You do not need that functionality. Do not reuse it in any way. Lack

of any code is most simple.

1) Use template. Good template is hard thing to make and to test

sufficiently, but it is extremely efficient and reusable.

2) Forward functionality of non-public base class. If there are no

polymorphism then it is very efficient. Easier than (3) to use, bit

harder to understand.

3) Forward functionality to non-polymorphic data member. When the data

member is template then it is as efficient as (2) otherwise it carries

very little overhead. Easiest to understand.

4) Static polymorphism (CRTP). Very little overhead if any. Adds some

flexibility of (5), but is hardest to understand and to make since

it involves (1) with properties of (5).

5) Usual dynamic polymorphism. With its virtual functions it is bit less

efficient than others above. Somewhat overused, over-taught and

under-understood.

6) Forward functionality to polymorphic data member. That is both most

complex and flexible since polymorphic data member means that it may

change during object's life-time. More overhead than with (5).



Can not be that the SOLID OOD suggests to always use that (6) as very

first choice of functionality re-usage ... it seems least efficient and

not most simple.

I certainly said no such thing. It's pretty hard to have a conversation with someone that's going to straw man everything I say.

You can use templates for composition. You can use inheritance correctly. You can use external functions rather than members or friends. All of these things adhere to SOLID principles quite nicely.

The template solution doesn't appear to work in this case because what is desired is a RUNTIME check for the difference between a door with a lock andone that has none. Anything based on static information then is probably going to have similar flaws.
No. I have explained else-thread. It was just used to avoid typical

class A, class B blah-blah too abstract example. I did not want to

introduce some problem with doors. I wanted to bring example of the usage

of cross cast.

It's a decent example for that and shows exactly the kinds of flaws that come about from that kind of choice.

Yes it was me. It was good discussion.

Your suggestion to go to next level and turn inheritance into component

can be correct choice. What i am still discussing is that you seem to

suggest to always design inheritance as component.

I certainly never said any such thing.

The way suggested by Scott and Leigh to combine all cases (to make all Doors

Lockable) was maybe most robust. It turns it into usual polymorphic usage..

This is only correct if you're not adding functions to your base only for some of its concrete inheritors. If it makes sense to ask a door that has no lock to unlock then this is an appropriate solution. It makes no sense in the real world, but that does not mean it doesn't in code. Scott attempted to add the function just because one type of door had use for it and this is the wrong reason (reasons matter). Leigh on the other hand suggested that my solution was no different and he's probably right...at this point it begins to make more sense to simplify the algebra.
I myself still think that unless it burns (shows signals of under- or

over-engineering) somewhere else ... then that cross-cast there is fine. ;)

It already does show those signs. Cross casting is a smell and can violateboth LSP and OCP at least.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top