1

I have an abstract class Dog and multiple subclasses (Beagle, Labrador, Bulldog...) extendig it. I have a DogHouse that can store a Dog.

My problem is that when I put for example a Beagle into a DogHouse like doghouse.addDog(beagle) and then take it out with doghouse.getDog(), this method gives me the beagle as a Dog, but I want to get my Beagle back.

DogHouse has many more methods, they all give me Dogs, but I work with specific dogs in my application. Does it mean that I always have to downcast?


Update:

I'm using TypeScript and can change any part of the code so my options are practically endless. What I don't understand:

Imagine you have two dog schools. Knowing that different breeds of dogs are capable of very different things and need different care and training, dog school 1 is specialized in Greyhounds, while the other is training let's say Bulldogs only.

There is a company selling DogHouses. They say that their product is compatible with any kind of Dog, so both dog schools decide to buy from them.

A trainer at dog school 1 tells a Greyhound to run very fast: greyhound.runVeryFast(); - something that only greyhounds can do. It works.

Then, she sends the greyhound into a DogHouse: doghouse.addDog(greyhound); - which works too.

After the dog could rest enough, she calls him and tells him to run very fast again: doghouse.getDog().runVeryFast(); - It does not work.

She turns to the company selling the DogHouses to complain what a stupid house they've built that a dog forgets in it to which breed he belongs. "Why do I have to tell the dog (cast him) that he is a Greyhound every time he comes out of his house?"

The company answers "Sorry madam, maybe we can build a new house using generics." But then the lady answers: "And will it then accept Cats" too? I only want a house for Dogs."

So my question is: How to build a proper DogHouse?

  • Do you control the DogHouse implementation, can you change it or pick other class for same purpose? – max630 Nov 17 '18 at 14:35
  • Yes, I can modify anything – tom Nov 17 '18 at 15:26
  • I'd encourage you to read the answer by Kilian Foth - it is important to understand what's said there. But for us to be able to help you in a more specific way, can you elaborate on what you are trying to do? Specifically, what are you using your DogHouse for? How many dog instances can be placed in there. Is it just a container, or does it have some behavior associated with it (some nontrivial mehtods)? What are you doing with each concrete dog class that cannot be expressed through the interface provided by the Dog base class? (Consider adding these points to your question.) – Filip Milovanović Nov 17 '18 at 15:58
  • Thanks, I added some more info to describe what confuses me. – tom Nov 17 '18 at 16:26
  • That's quite a picturesque clarification :) Anyway, from what you described, you can use generics with generic constraints, as described in Karel Coudijzer's answer - the code there seems to be C#, but TypeScript supports generic constraints as well; see the Generic Constraints section here. – Filip Milovanović Nov 17 '18 at 17:12
8

You could make use of generics and create a DogHouse of T.

class DogHouse<T>
    where T : Dog
{
    private T dog

    T getDog() { return this.dog; }

    void addDog(T dog) { this.dog = dog; }
}

var house = new DogHouse<Bulldog>();
var dog = new Bulldog();
house.addDog(dog);

Bulldog bulldog = house.getDog();
  • I like this approach but you're missing the using code that gets you back the dog as a bulldog without any casting. – candied_orange Nov 17 '18 at 13:48
  • 1
    +1, if OP needs different doghouses, but only one kind of dog per house, then this is the way to go. – Kilian Foth Nov 17 '18 at 14:44
  • 2
    I could be wrong, but I think that what candied_orange is getting at is that this is fine if the DogHouse itself has no behavior that depends on the concrete type of Dog, but if it does, then it won't work because within DogHouse you can only write code against the interface of Dog. Only external code can access the interface of Bulldog. – Filip Milovanović Nov 17 '18 at 16:02
  • @max630 that's not getting it back. That's putting it in. All I'm saying is the answer would be better if we saw getDog() being used without any casting. It's a bit hard to write a clear example while using var – candied_orange Nov 17 '18 at 16:35
  • 1
    @FilipMilovanović: If you want DogHouse to "have behavior that depends on the concrete type of Dog," then you need subclasses of DogHouse for each concrete type, which may mean we need to have a side discussion about covariance and contravariance. That naturally leads to questions such as "Why is it so complicated?" and "Why must every language do it differently?" It's probably easier to move that logic into the Dog interface and do dispatch via Dog's polymorphism instead. Then you can avoid that whole mess of toxic nonsense. – Kevin Nov 17 '18 at 18:28
11

Ask yourself: why do you want to downcast?

The declaration "DogHouse contains Dogs" says exactly that: you can be sure of dealing with a Dog, nothing more. If you want beagles and labadors to do different things in the same situation, then you should override those methods (e.g. bark()) in each subclass and simply call dog.bark().

If you can't arrange things that way in your language (for instance, if you need multiple dispatch to distinguish between appropriate actions), then subclassing is not the right way to structure your data model. If you must know the concrete type of an object in order to use it effectively, then there is no point in storing them in a generic container.

1

I have a similar case in a project where only on kind of “dog” is of special interest. Then I have factored all the common behavior into the parent class (sorting, name etc.) and built out only the interesting type with a lot more capabilities. This of course require check for is-of-type at strategic places and then branch out to analyzing the interesting “dog” with a downcast. This of course has limitation on future extendability but in my case I think I understand the problem domain well enough to say it is The right thing. Of course this goes against a lot of best practice but if you are very certain you can live with the limitations it may be simpler than injecting particular interfaces or making all dogs capable of responding to all methods.

The point is that if you can’t deal with most cases at the generic dog level then the inheritance idea has limited value. But then if you can and then need a few specials that require type check and downcast it may be ok.

0

A trainer at dog school 1 tells a Greyhound to run very fast: greyhound.runVeryFast(); - something that only greyhounds can do. It works.

This is a design smell and the base of the problems your running into. This is a violation of LSP: base and derived classes should have the same capabilities.

In your case, every dog (or none) should have a RunVeryFast method and only their implementations may vary.

By following the LSP, you can be sure that every dog you fetch from the doghouse has a RunVeryFast() method.

  • Thanks for mentioning this, because if it's correct what you're saying then I have made multiple mistakes in my design. For other dogs however, runVeryFast wouldn't make any sense, there could be no implementation there at all, so what should I do? Is this where interfaces come into the picture? – tom Nov 18 '18 at 20:33
  • There’s a principle called “composition over inheritance“ (en.m.wikipedia.org/wiki/Composition_over_inheritance) – Rik D Nov 18 '18 at 21:38
  • In short the principle says that your classes should hold references to other classes that contain the desired behavior. In your case Dog could have a IRunBehavior and when you create an instance of a Greyhound you pass in a RunVeryFast of type IRunBehavior. – Rik D Nov 18 '18 at 21:49
0

Objective-C allows a type "kindof <Dog>". If the getter method returns "kindof <Dog>" instead of "Dog", then the result can be assigned to a variable containing a reference to Dog or any subclass without cast. And any method calls are allowed that are implemented by Dog or any subclass. So you can write [dogHouse.dog runVeryFast] which is only implemented by Greyhound. The compiler accepts it. There will be a runtime error if the dog in the doghouse is not a Greyhound (or some other dog implementing runVeryFast).

This is not typesafe. But it is better than casting, which would allow casting to "SiameseCat". Neither assignment to a SiameseCat object nor [dogHouse.dog catchMouse] would compile.

Unlike the solution with generic types, DogHouse can contain any Dog.

(It is also possible to have a class DogHouse parameterised on some kind of Dog, which would be typesafe, but would have incompatible DogHouse objects for each kind of Dog).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.