www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Classes (does this make sense?)

reply "Saaa" <empty needmail.com> writes:
Best read after 'this needs a different approach' :)
Still trying to get some grip on those classes...
please comment on my try.

---------------------------
module main;
import std.stdio;
public import FruitClass

int main(string[] args) {

writefln("Fruits");
foreach( f; fruits) {
f.eat();
}

writefln("\nNew Fruits");
fruits[2] = PLUM;
foreach( f; fruits) {
f.eat();
}

return 0;
}

----------------------------
module FruitClass;
import Apple;

static this()
{
Fruit[] fruits=[APPLE,APPLE];
//with more fruits it would become something like
//Fruit[] fruits = [APPLE, PEAR, APPLE, APPLE, PEAR, PLUM];
}

abstract class Fruit
{
int color;

void eat();
//the actual class has more functions
}

----------------------------
module Apple;
import FruitClass;

class APPLE : Fruit
{
bool eaten=false;

void trowAway()
{
//how do you delete yourself?
}

void eat()
{
eaten=true;
}

this (uint color)
{
.color=color;
}
}
---------------------------- 
May 30 2008
next sibling parent reply "Saaa" <empty needmail.com> writes:
Let me ask some more specific questions ..

in the previous thread people suggested using the following:

static this()
{
     APPLE = new class() IFruit {
         void eat() {
             writefln("Eat APPLE");
         }
     };

     PEAR = new class() IFruit {
         void eat() {
             writefln("Eat PEAR");
         }
     };
}

Why the 'new class()' ? Couldn't you just create a class apple : IFruit ?
Those fruits are in reality quite big, with multiple functions/local 
variables.
I've tried placing the APPLE in another module:
-----------------------------
module apple;

import std.stdio;
import main;

class APPLE : IFruit {
   void eat() {
      writefln("Eat APPLE");
   }
}
------------------------------
module main;

import std.stdio;
import apple;

interface IFruit
{
     void eat();
}

IFruit APPLE;
IFruit PEAR;
IFruit PLUM;

static this()
{
     PEAR = new class() IFruit {
         void eat() {
             writefln("Eat PEAR");
         }
     };

     PLUM = new class() IFruit {
         void eat() {
             writefln("Eat PLUM");
         }
     };
}

int main(string[] args) {

IFruit[] fruits = [APPLE, PEAR, APPLE, APPLE, PEAR, PLUM];

writefln("Fruits");
foreach( f; fruits) {
f.eat();
}

return 0;
}
---------------------------------

Fruits
Error: Access Violation

:) 
May 31 2008
parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Saaa escribió:
 Let me ask some more specific questions ..
 
 in the previous thread people suggested using the following:
 
 static this()
 {
      APPLE = new class() IFruit {
          void eat() {
              writefln("Eat APPLE");
          }
      };
 
      PEAR = new class() IFruit {
          void eat() {
              writefln("Eat PEAR");
          }
      };
 }
 
 Why the 'new class()' ? Couldn't you just create a class apple : IFruit ?
 Those fruits are in reality quite big, with multiple functions/local 
 variables.

Yes. In fact, I would recommend to do that and not use the anonymous approach, specially if the classes are big. They probably showed it like that for brevity purposes.
 I've tried placing the APPLE in another module:
 -----------------------------
 module apple;
 
 import std.stdio;
 import main;
 
 class APPLE : IFruit {
    void eat() {
       writefln("Eat APPLE");
    }
 }
 ------------------------------
 module main;
 
 import std.stdio;
 import apple;
 
 interface IFruit
 {
      void eat();
 }
 
 IFruit APPLE;

Here you declare a variable of type IFruit called APPLE.
 IFruit PEAR;
 IFruit PLUM;
 
 static this()
 {
      PEAR = new class() IFruit {
          void eat() {
              writefln("Eat PEAR");
          }
      };
 
      PLUM = new class() IFruit {
          void eat() {
              writefln("Eat PLUM");
          }
      };

But you never initialize it, so it's null. That's why you get access violation error.
 }
 
 int main(string[] args) {
 
 IFruit[] fruits = [APPLE, PEAR, APPLE, APPLE, PEAR, PLUM];
 
 writefln("Fruits");
 foreach( f; fruits) {
 f.eat();
 }
 
 return 0;
 }
 ---------------------------------
 
 Fruits
 Error: Access Violation
 
 :) 
 
 

May 31 2008
parent "Saaa" <empty needmail.com> writes:
; ) thanks 
Jun 01 2008
prev sibling parent reply Lutger <lutger.blijdestin gmail.com> writes:
Saaa wrote:

 Best read after 'this needs a different approach' :)
 Still trying to get some grip on those classes...
 please comment on my try.

I'll try, comments are after each piece of code.
 ---------------------------
 module main;
 import std.stdio;
 public import FruitClass
 
 int main(string[] args) {
 
 writefln("Fruits");
 foreach( f; fruits) {
 f.eat();
 }

This has nothing to do with classes, but public import is not needed here since this is your main entry.
 writefln("\nNew Fruits");
 fruits[2] = PLUM;
 foreach( f; fruits) {
 f.eat();
 }
 
 return 0;
 }

A question of style but still important. PLUM is a type, a class, and that is usually written with only one uppercase letter: 'Plum". All caps are usually constants. It's a useful convention.
 ----------------------------
 module FruitClass;
 import Apple;
 
 static this()
 {
 Fruit[] fruits=[APPLE,APPLE];
 //with more fruits it would become something like
 //Fruit[] fruits = [APPLE, PEAR, APPLE, APPLE, PEAR, PLUM];
 }

I'd recommend against importing Apple here. The FruitClass module defines the more general type, what kind of a thing a fruit is. Intuitively, to do this, it should not depend on knowledge of specific kinds of fruits. The issue here is that the module FruitClass does two things: define what fruits are and store a bunch of specific fruits in an array. I think it's better to move the fruits to main where they are processed. This way, when you define a new kind of fruit, you don't have to modify the fruitclass module. More generally speaking, here you have a circular dependency. FruitClass depends on Apple, and Apple depends on FruitClass. The more such dependencies you have, the more different pieces of code you have to modify whenever you want to change something and the less flexible it becomes. ...
 ----------------------------
 module Apple;
 import FruitClass;
 
 class APPLE : Fruit
 {
 bool eaten=false;
 
 void trowAway()
 {
 //how do you delete yourself?
 }
 

Indeed, usually one doesn't delete oneself ;) Think about it this way: who is responsible for what? The fruit can very well remember when it's eaten or not, that's how you have implemented it. It's a property of a fruit. But it's not up to the fruit to get rid of itself, that should be the responsibility of the one who owns the fruit. Thus, throwAway() might not belong in this class.
Jun 02 2008
parent reply "Saaa" <empty needmail.com> writes:
Thanks, I changed it to this (I think I get it now :)
Any comments are of course still appreciated ;)

-----------------------
module main;

import std.stdio;
import apple;

abstract class IFruit
{
int number;
void eat();
}

int main(string[] args) {
IFruit[] fruits = [new Apple(12), new Apple];
writefln("Fruits");
foreach( f; fruits) {
f.eat();
}
return 0;
}
------------------
module apple;

import std.stdio;
import main;

class Apple : IFruit {
void eat() {
writefln("Eat Apple");
}

this ()
{
}

this (uint color)
{
this.color=color;
}
}
Jun 02 2008
parent reply torhu <no spam.invalid> writes:
Saaa wrote:
 Thanks, I changed it to this (I think I get it now :)
 Any comments are of course still appreciated ;)
 
 -----------------------
 module main;
 
 import std.stdio;
 import apple;
 
 abstract class IFruit
 {
 int number;
 void eat();
 }
 
 int main(string[] args) {
 IFruit[] fruits = [new Apple(12), new Apple];
 writefln("Fruits");
 foreach( f; fruits) {
 f.eat();
 }
 return 0;
 }
 ------------------
 module apple;
 
 import std.stdio;
 import main;
 
 class Apple : IFruit {
 void eat() {
 writefln("Eat Apple");
 }
 
 this ()
 {
 }
 
 this (uint color)
 {
 this.color=color;
 }
 }
 
 

I would probably put IFruit in its own module. Or if all the subclasses are defined in the same module, at the top of that module. It looks a bit backward when the main module imports apple, then defines IFruit. This is because Apple depends on IFruit, but not the other way around. Apple should import IFruit, then main should import Apple. Or main could define Apple, after importing IFruit. And IFruit probably shouldn't contain int number. Make it a property instead, this also allows IFruit to be an interface instead of an abstract class: int number(); // get number void number(int n); // set number Drop the last one if you only want number to be readable, not writable. Using properties instead of field allows the subclasses to calculate the number instead of storing it, doing something extra when you set the number, etc. If you put 'int number' in the superclass, you have fixed the implementation. If 'number' is the number of a kind a of fruit, it shouldn't be in a class called Apple, but a class called Apples. And IFruits. Or you could have a FruitBasket class...
Jun 02 2008
parent reply "Saaa" <empty needmail.com> writes:
 Thanks, I changed it to this (I think I get it now :)
 Any comments are of course still appreciated ;)

 -----------------------
 module main;

 import std.stdio;
 import apple;

 abstract class IFruit
 {
 int number;
 void eat();
 }

 int main(string[] args) {
 IFruit[] fruits = [new Apple(12), new Apple];
 writefln("Fruits");
 foreach( f; fruits) {
 f.eat();
 }
 return 0;
 }
 ------------------
 module apple;

 import std.stdio;
 import main;

 class Apple : IFruit {
 void eat() {
 writefln("Eat Apple");
 }

 this ()
 {
 }

 this (uint color)
 {
 this.color=color;
 }
 }

I would probably put IFruit in its own module. Or if all the subclasses are defined in the same module, at the top of that module. It looks a bit backward when the main module imports apple, then defines IFruit. This is because Apple depends on IFruit, but not the other way around. Apple should import IFruit, then main should import Apple. Or main could define Apple, after importing IFruit.

 And IFruit probably shouldn't contain int number.  Make it a property 
 instead, this also allows IFruit to be an interface instead of an abstract 
 class:

 int number();   // get number
 void number(int n); // set number

 Drop the last one if you only want number to be readable, not writable. 
 Using properties instead of field allows the subclasses to calculate the 
 number instead of storing it, doing something extra when you set the 
 number, etc.  If you put 'int number' in the superclass, you have fixed 
 the implementation.

Where should I store the number (it should have been called color, my bad )? Every fruit has the color variable.
 If 'number' is the number of a kind a of fruit, it shouldn't be in a class 
 called Apple, but a class called Apples.  And IFruits.  Or you could have 
 a FruitBasket class...

Thanks ;)
Jun 02 2008
parent reply Lutger <lutger.blijdestin gmail.com> writes:
Saaa wrote:
///
 Why is an interface better than a abstract class?
 Where should I store the number (it should have been called color, my bad
 )? Every fruit has the color variable.

Interfaces may have these benefits: - it is possible for a class to implement more than one interface - implementation is completely seperate from the interface. This can lead to more flexible code. Further, it means that the implementation is in one place, which is easier to maintain. When you make color a property, it's variable must be stored in the classes that implement IFruit: Apple, Plum, etc, and would be a private field. Here is an example of how that could have some advantage: class Apple : IFruit { int color() { return _color; } void rot() { _color = BROWN; } private int _color = GREEN; // GREEN is defined elsewhere } In this implementation, only inside the Apple class can the color be changed. This means that, when that code is correct color can be guaranteed to always be in a valid state. Alternatively a setter can be implemented that checks for valid input: void color(int clr) { if ( ! (clr == BROWN || clr == GREEN || clr == YELLOW) ) throw new Exception("invalid color for Apple"); _color = clr; } Yet another way to keep color always correct is to use an invariant: class Apple : IFruit { invariant() { assert(clr == BROWN || clr == GREEN || clr == YELLOW, "Apple's color is invalid"); } } When compiled with -unittest, the invariant is checked before and after every function in Apple is executed.
Jun 03 2008
parent reply "Saaa" <empty needmail.com> writes:
My idea was to put everything all the fruits share in the same (abstract) 
super class.
I would also like the assert code to be in the super class as it is the same 
for all fruits.
Same for the color setter/getter.


 ///
 Why is an interface better than a abstract class?
 Where should I store the number (it should have been called color, my bad
 )? Every fruit has the color variable.

Interfaces may have these benefits: - it is possible for a class to implement more than one interface - implementation is completely seperate from the interface. This can lead to more flexible code. Further, it means that the implementation is in one place, which is easier to maintain. When you make color a property, it's variable must be stored in the classes that implement IFruit: Apple, Plum, etc, and would be a private field. Here is an example of how that could have some advantage: class Apple : IFruit { int color() { return _color; } void rot() { _color = BROWN; } private int _color = GREEN; // GREEN is defined elsewhere } In this implementation, only inside the Apple class can the color be changed. This means that, when that code is correct color can be guaranteed to always be in a valid state. Alternatively a setter can be implemented that checks for valid input: void color(int clr) { if ( ! (clr == BROWN || clr == GREEN || clr == YELLOW) ) throw new Exception("invalid color for Apple"); _color = clr; } Yet another way to keep color always correct is to use an invariant: class Apple : IFruit { invariant() { assert(clr == BROWN || clr == GREEN || clr == YELLOW, "Apple's color is invalid"); } } When compiled with -unittest, the invariant is checked before and after every function in Apple is executed.

Jun 03 2008
parent reply Lutger <lutger.blijdestin gmail.com> writes:
Saaa wrote:

 My idea was to put everything all the fruits share in the same (abstract)
 super class.
 I would also like the assert code to be in the super class as it is the
 same for all fruits.
 Same for the color setter/getter.
 
 

Sure that's reasonable, then you can let the derived classes manipulate the color via the getter / setter interface and still make the color field private.
Jun 03 2008
parent "Saaa" <empty needmail.com> writes:
 Saaa wrote:

 My idea was to put everything all the fruits share in the same (abstract)
 super class.
 I would also like the assert code to be in the super class as it is the
 same for all fruits.
 Same for the color setter/getter.

Sure that's reasonable, then you can let the derived classes manipulate the color via the getter / setter interface and still make the color field private.

I think I'll do that then ;)
Jun 03 2008