質問

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?

役に立ちましたか?

解決

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();

他のヒント

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.

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.

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.

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).

ライセンス: CC-BY-SA帰属
所属していません softwareengineering.stackexchange
scroll top