www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - On inheritance and polymorphism in cats and dogs (and other beasts

reply "Derix" <derix dexample.com> writes:
So, I have this pet project where classes Cat and Dog inherit
from the more generic Beast class.

All beasts prosper and multiply and so do cats and dogs. The
breeding routine is fairly constant across species, with minor
variations. So I'd like to define the "breed" method in the Beast
class and overload it in the Cat and Dog classes.

Something like :

Cat rita = new Cat(female);
Cat ringo= new Cat(male);
Cat junior=rita.breed(ringo);

with

Class Cat:Beast{
...
	Cat breed(Cat sire){
   		// do what all beasts do

   		//then add cat-specific genetics
   	}
...
} 	

Now, what I can't seem to figure out is the "// do what all beast
do" part. "this" current object is obviously an instance of the
Cat class. How do I de-specialize it so it can behave as an
instance of the more generic Beast class ? Then, the offspring
will in turn be an instance of Beast : how to cast it as a Cat ?

So far, all I've been able to do is to dance around the issue by
writting ad-hoc constructors like

Beast b=new Beast( someCat )

or

Cat c=new Cat(someBeast)

but this seems awkward and inefficient.

There must be some more clever and straightforward way to do
this, surely ?

Also : D is not my first OO language, but polymorphism and
inheritance still are advanced concepts for me.
Dec 20 2014
next sibling parent "tcak" <tcak gmail.com> writes:
On Saturday, 20 December 2014 at 15:40:32 UTC, Derix wrote:
 So, I have this pet project where classes Cat and Dog inherit
 from the more generic Beast class.

 All beasts prosper and multiply and so do cats and dogs. The
 breeding routine is fairly constant across species, with minor
 variations. So I'd like to define the "breed" method in the 
 Beast
 class and overload it in the Cat and Dog classes.

 Something like :

 Cat rita = new Cat(female);
 Cat ringo= new Cat(male);
 Cat junior=rita.breed(ringo);

 with

 Class Cat:Beast{
 ...
 	Cat breed(Cat sire){
   		// do what all beasts do

   		//then add cat-specific genetics
   	}
 ...
 } 	

 Now, what I can't seem to figure out is the "// do what all 
 beast
 do" part. "this" current object is obviously an instance of the
 Cat class. How do I de-specialize it so it can behave as an
 instance of the more generic Beast class ? Then, the offspring
 will in turn be an instance of Beast : how to cast it as a Cat ?

 So far, all I've been able to do is to dance around the issue by
 writting ad-hoc constructors like

 Beast b=new Beast( someCat )

 or

 Cat c=new Cat(someBeast)

 but this seems awkward and inefficient.

 There must be some more clever and straightforward way to do
 this, surely ?

 Also : D is not my first OO language, but polymorphism and
 inheritance still are advanced concepts for me.
What you do is to implement the "breed" method in Beast class, then override it in both Cat and Dog, but then call "super.breed()" method which belongs to Beast.
Dec 20 2014
prev sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Saturday, 20 December 2014 at 15:40:32 UTC, Derix wrote:
   		// do what all beasts do
You'll want to call the function in the base class, which is done with the super keyword in D. I wouldn't make the super function return the new instance though, that can't be as easily customized. I'd separate it into two functions: breed and genetic mix class Beast { // call this on a new zygote // it is protected because it doesn't make sense to mix genetics // in public protected void mixGenetics(Beast mother, Beast father) { // modify this to be a combination of generic traits // for example this.furColor = mother.furColor + father.furColor; } } Now, make the specific breed functions on the subclasses. I didn't put that in the interface because you typically don't want to substitute parents with generic beasts - cats and dogs can't breed together, but if there was a breed(Beast) function in the base class, that would be permitted. (in OO theory, this is the liskov substitution principle) class Cat : Beast { Cat breed(Cat sire) { Cat offspring = new Cat(); offspring.mixGenetics(this, sire); return offspring; } protected override void mixGenetics(Cat mother, Cat father) { super(mother, father); // calls the generic Beast.mixGenetics // now we can do cat-specific stuff this.pickiness = mother.pickiness * father.pickiness; } } Dog would look similar to cat. However, since mixGenetics really only makes sense when making a new Cat, you might want to make it a constructor instead of a method, then follow the same pattern.
 Cat class. How do I de-specialize it so it can behave as an
 instance of the more generic Beast class ? Then, the offspring
 will in turn be an instance of Beast : how to cast it as a Cat ?
Generally speaking, de-specialization happens automatically. The super keyword calls the parent class' version which takes care of all that. You can cast Beasts back into Cats with cast(Cat) beast. Be sure to check for null - if beast is a dog, that cast will return null.
 So far, all I've been able to do is to dance around the issue by
 writting ad-hoc constructors like
that makes some sense, you'd just want to make sure the specific ones are done in specific child constructors, then call the parent with super()
Dec 20 2014
parent reply "Derix" <derix dexample.com> writes:
Thank you kindly for this detailed response. Your directions bore
frution, and I now have cats that breed cats (and not dogs).
However I can't get rid of the following warning on compilation :

|...
| void mixGenetics(Cat mother,Cat father) {...}
|...

 Warning: Beast.Beast.mixGenetics(Beast mother, Beast father) 
 hidden by Cat is deprecated. Use 'alias Beast.mixGenetics 
 mixGenetics;' to introduce base class overload set.
Well, I know every of those words, but I can't make sense out of the sentence. Hidden function ? Alias function ? I tried some variants, which yields errors : |... | override void mixGenetics(Cat mother,Cat father) { |...
 Error: function CatsAndDogs.Cat.mixGenetics does not override 
 any function, did you mean to override 
 'Beast.Beast.mixGenetics'?
Why, yes, this is precisely what I'm trying to do, stupid compiler. |...(in class Cat) |... | void mixGenetics(Beast mother,Beast father) { | super.mixGenetics(mother,father); | this.pickiness = (mother.pickiness + father.pickiness) / 2 ; | } |...
 Error: no property 'pickiness' for type 'Beast.Beast'
I'm not that surprised here, I tried this one just in case, but this leads me to a part of your previous answer I did not quite get :
You can cast Beasts back into Cats with cast(Cat) beast. Be sure 
to check for null - if beast is a dog, that cast will return 
null.
I do get the idea, but wasn't able to implement it, and I find the online litterature to be quite terse on the topic of casting user-defined types. http://dlang.org/operatoroverloading#cast Does that mean that I must define a cast operator in my classes ? Thanks again. ----------------------------------------------------------------------- For the record, I include below the code that works despite the warning ----------------------------------------------------------------------- module Beast; import std.conv; class Beast{ string name; int generation; this(){} this(string s) { name=s; generation=0; } //protected void mixGenetics(Beast mother, Beast father) { this.name=mother.name~father.name; this.generation=father.generation+1; } override string toString() { return "*"~this.classinfo.name~"* Name : "~this.name ~"; Gen. : "~ to!string(this.generation); } } class Cat:Beast { int pickiness; this(){}; this(string name, int pickiness){ super(name); this.pickiness=pickiness; } this(string name, string pickiness){ super(name); this.pickiness=to!int(pickiness); } protected void mixGenetics(Cat mother,Cat father) { super.mixGenetics(mother,father); this.pickiness = (mother.pickiness + father.pickiness) / 2 ; } override string toString() { return super.toString ~" ; Pick. : "~to!string(this.pickiness);} Cat breed(Cat father){ Cat kitten=new Cat(); kitten.mixGenetics(this, father); return kitten; } } class Dog:Beast { int fetchiness; this(){}; this(string name, int fetch) { super(name); fetchiness=fetch; } this(string name, string fetch){this(name, to!int(fetch));} override string toString() { return super.toString ~" ; Fetch. : "~to!string(this.fetchiness);} protected void mixGenetics(Dog mother,Dog father) { super.mixGenetics(mother,father); this.fetchiness = (mother.fetchiness + 2*father.fetchiness) / 3 ; } Dog Breed(Dog father){ Dog puppy=new Dog(); puppy.mixGenetics(this, father); return puppy; } } ---------------------- here be main ------ module Main; import Beast; import std.stdio; void main(string[] args){ string aName = (args.length>1)?args[1]:"default name"; string aArg2 = (args.length>2)?args[2]:"default arg2"; Cat rita = new Cat(aName,aArg2); writeln(rita.toString); Cat ringo = new Cat("Ringo", 8); writeln(ringo.toString); Cat Riton = rita.breed(ringo); writeln(Riton.toString); Dog dolly = new Dog("Dolly",20); Dog rocky=new Dog("Rocky",14); Dog snoopy=dolly.Breed(rocky); writeln(dolly.toString); writeln(rocky.toString); writeln(snoopy.toString); } ---------------------- and finally a satisfactory output sample : *Beast.Cat* Name : Rita; Gen. : 0 ; Pick. : 15 *Beast.Cat* Name : Ringo; Gen. : 0 ; Pick. : 8 *Beast.Cat* Name : RitaRingo; Gen. : 1 ; Pick. : 11 *Beast.Dog* Name : Dolly; Gen. : 0 ; Fetch. : 20 *Beast.Dog* Name : Rocky; Gen. : 0 ; Fetch. : 14 *Beast.Dog* Name : DollyRocky; Gen. : 1 ; Fetch. : 16
Dec 21 2014
parent reply "Mike Parker" <aldacron gmail.com> writes:
On Sunday, 21 December 2014 at 10:42:37 UTC, Derix wrote:



If you change the mother and father parameters from Beasts to 
Cats, then you aren't overriding anything -- you are 
/overloading/. That's where the first two errors were coming 
from. In order for a subclass method to override a base class 
method, the signature has to be the same (same parameter types).

The error about pickiness was because you were trying to access 
mother.pickiness and father.pickiness even though mother and 
father are declared as Beasts -- Beasts don't have pickiness. To 
get at their pickiness you have to downcast them to Cats. Think 
of it as a 'view' of each object.

The mother and father parameters are declared as Beasts, so you 
can only "see" what a Beast has. You can't see anything that a 
Cat has through a Beast, because the Beast might actually be a 
Dog instead. So you have to create a new "view" by casting to the 
appropriate type. If the underlying object is not an instance of 
the type you cast to, the cast will return null and you can 
immediately return from the function, throw an error, or whatever 
you think is appropriate in that case.

class Cat : Beast
{
    protected override void mixGenetics( Beast mother, Beast 
father )
    {
       // The superclass works on Beasts, so give it the Beasts
       super.mixGenetics( mother, father );

       // This method needs cats, so cast to Cats
       Cat catmom = cast( Cat )mother;
       Cat catdad = cast( Cat )father;

       // If the casts failed, then mother and father are not 
Cats. Since
       // they are not Cats, you can't do anything Catty with 
them. So
       // either return from the function or throw an Error or 
something.
       if( catmom is null || catdad is null )
          throw new Error( "Expected cats, got something else." );

       // Now do Catty things
       pickiness = (mother.pickiness + father.pickiness) / 2;
    }
}

You never need a cast operator to cast up and down a class 
hierarchy. A Cat *is* a Beast, so you can always "upcast" without 
fear of failure. A Beast *might be* a Cat, so you can attempt a 
"downcast" when you must and the cast will return null if the 
underlying instance isn't actually a Cat.
Dec 21 2014
next sibling parent "Mike Parker" <aldacron gmail.com> writes:
On Sunday, 21 December 2014 at 15:05:47 UTC, Mike Parker wrote:

       // Now do Catty things
       pickiness = (mother.pickiness + father.pickiness) / 2;
 
Sorry, forgot to change that line when I copy-pasted. Should be: pickiness = (catmom.pickiness + catdad.pickiness) / 2;
Dec 21 2014
prev sibling parent reply "Derix" <derix dexample.com> writes:
Thanks a lot, I begin to see the light !

you aren't overriding anything -- you are
/overloading/ Yep, I rekon the difference was not clear to me. It still isn't right now, but at least now I know that it exists and I have to look into it. As to the spell you cast to my cats in your rewriting of the class, I'm still a bit confused about the ways of the 'downcast'. I'll try and compile a working hack around your outline, but that will have to wait another day : late Sunday afternoon, lazy ... Thanks again, really.
Dec 21 2014
parent Mike Parker <aldacron gmail.com> writes:
On 12/22/2014 1:11 AM, Derix wrote:
 Yep, I rekon the difference was not clear to me. It still isn't
 right now, but at least now I know that it exists and I have to
 look into it.
Overriding - a subclass reimplements a method from a base class. The method must have the same number and type of parameters. The subclass version essentially replaces the superclass version. import std.stdio; class A { public void foo( int x ) { writeln( "I'm an A." ); } } class B : A { // Note that the parameter is an int, // just as in the base class public override void foo( int x ) { writeln( "I'm a B." ); } } void main() { // prints "I'm an A." A one = new A; one.foo(); // prints "I'm a B." A two = new B; two.foo(); // ditto B three = new B; three.foo(); } Overloading - two or more methods with the same name, but different types and/or number of parameters. They can be implemented in a single class, or in a subclass. Which method is called depends on the parameters passed at the call site. import std.stdio; class A { void bar( int x ) { writeln( "One int." ); } void bar( double x ) { writeln( "One double." ); } } class B { void bar( int x, double y ) { writeln( "An int and a double." ); } } void main() { A one = new A; // prints "One int." one.bar( 10 ); // prints "One double." one.bar( 10.0 ); // Error -- one is declared as an A. // A does not have the int,double version. one.bar( 10, 10.0 ); A two = new B; // prints "One int." two.bar( 10 ); // prints "One double." two.bar( 10.0 ); // Error -- the underlying instance of two // is a B, but two is declared as an A. A // does not have the int,double version two.bar( 10, 10.0 ); B three = new B; // prints "One int." three.bar( 10 ); // prints "One double." three.bar( 10.0 ); // prints "An int and a double." three.bar( 10, 10.0 ); }
 As to the spell you cast to my cats in your rewriting of the
 class, I'm still a bit confused about the ways of the 'downcast'.
 I'll try and compile a working hack around your outline, but that
 will have to wait another day : late Sunday afternoon, lazy ...
Using the A and B classes from above: class C : A {} void main() { A one = new A; // This cannot work. One is not a B, // and cannot be 'downcast' to be. The line // 'new A' means it is and always will be // an A. B aB = cast( B )one. assert( aB is null ); A two = new B; // This works. Since two was assigned a B, the // compiler knows that it can be 'downcast' so // it lets you do it. B anotherB = cast( B )two; assert( anotherB !is null ); A three = new C; // Again, this cannot work. This instance was created // as a C, not a B, so the cast will result in null. B yaB = cast( B )three; assert( yaB is null ); // It *can* be cast to a C C aC = cast( C )three; assert( aC !is null ); // And instances of B and C can be 'upcast' to A A four = cast( A )anotherB; A five = cast( A )aC; } downcasting - taking an instance of a class and casting it to a subclass. This can only work if the instance was originally created as a that specific subclass. upcasting - taking an instance of a class and casting it to its superclass. This will *always* work. Every class you create in D can be cast to Object, since all classes implicitly derive from Object.
Dec 21 2014