Thread overview
On inheritance and polymorphism in cats and dogs (and other beasts too)
Dec 20, 2014
Derix
Dec 20, 2014
tcak
Dec 20, 2014
Adam D. Ruppe
Dec 21, 2014
Derix
Dec 21, 2014
Mike Parker
Dec 21, 2014
Mike Parker
Dec 21, 2014
Derix
Dec 22, 2014
Mike Parker
December 20, 2014
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.
December 20, 2014
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.
December 20, 2014
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()
December 21, 2014
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

December 21, 2014
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.
December 21, 2014
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;
December 21, 2014
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.
December 22, 2014
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.