Jump to page: 1 2
Thread overview
Classes (does this make sense?)
May 31, 2008
Saaa
May 31, 2008
Saaa
May 31, 2008
Ary Borenszweig
Jun 01, 2008
Saaa
Jun 02, 2008
Lutger
Jun 03, 2008
Saaa
Jun 03, 2008
torhu
Jun 03, 2008
Saaa
Jun 03, 2008
Lutger
Jun 03, 2008
Saaa
Jun 03, 2008
Lutger
Jun 03, 2008
Saaa
May 31, 2008
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 31, 2008
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
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
> 
> :) 
> 
> 
June 01, 2008
; ) thanks


June 02, 2008
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.



June 03, 2008
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;
}
}


June 03, 2008
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...
June 03, 2008
>> 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.
O.k.
>
> 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.
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.

>
> 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...
Yeah, sorry. It's the color.

Thanks ;)


June 03, 2008
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.
June 03, 2008
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.


« First   ‹ Prev
1 2