www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Question on shapes

reply Alain De Vos <devosalain ymail.com> writes:
Let's say a shape is ,a circle with a radius ,or a square with a 
rectangular size.
I want to pass shapes to functions, eg to draw them on the screen,
draw(myshape) or myshape.draw();
But how do i implement best shapes ?
May 16 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/16/22 17:10, Alain De Vos wrote:
 Let's say a shape is ,a circle with a radius ,or a square with a 
 rectangular size.
 I want to pass shapes to functions, eg to draw them on the screen,
 draw(myshape) or myshape.draw();
 But how do i implement best shapes ?
There are many ways of achieving this but I think you are looking for the classic object-oriented (OOP) shape hierarchy. (Option 2 below.) 1) This option does not use OOP. It uses function overloading: import std.stdio; struct Circle { float radius; } void draw(Circle circle) { writeln("This circle's radius is ", circle.radius); } struct Rectangle { float width; float height; } void draw(Rectangle rectangle) { writefln!"This rectangle's dimensions are %sx%s."(rectangle.width, rectangle.height); } void main() { draw(Circle(1.5)); draw(Rectangle(2.5, 3.5)); } That's very simple but it does not allow putting different types of shapes e.g. in the same array. 2) If you want to have a shape hierarchy, then you can start by defining its interface and implement that interface by concrete shape types. Drawing is ordinarily handled by member functions: import std.stdio; // This defines what we can do with a Shape: interface Shape { void draw(); } // This type is now a 'class' that implements Shape. class Circle : Shape { float radius; // Classes require constructors; so here is one: this (float radius) { this.radius = radius; } // No parameter needed. This function always executes on // 'this' object. void draw() { writeln("This circle's radius is ", radius); } } // Similarly, a class class Rectangle : Shape { float width; float height; this(float width, float height) { this.width = width; this.height = height; } void draw() { writefln!"This rectangle's dimensions are %sx%s."(width, height); } } // Here is the promise of polymorphism: This function takes // a Shape but the special drawing of each shape type is // handled automatically. void use(Shape shape) { shape.draw(); } void main() { // Class hierarchies allow putting different types into // the same array: Shape[] shapes; // Let's populate with alternating circles and rectangles foreach (i; 1 .. 10) { if (i % 2) { shapes ~= new Circle(i); } else { shapes ~= new Rectangle(i, i * 2); } } // And finally let's use them foreach (shape; shapes) { use(shape); } } Ali
May 16 2022
next sibling parent reply matheus <matheu gmail.com> writes:
On Tuesday, 17 May 2022 at 04:37:58 UTC, Ali Çehreli wrote:
 ...
 2) If you want to have a shape hierarchy, then you can start by 
 defining its interface and implement that interface by concrete 
 shape types. Drawing is ordinarily handled by member functions:
 ...
Hi Ali, I'm not the author but I have a question, in your second example, let's say that sometimes it would be required to "draw" with some scale factor, so (As a newbie) I would do something like this: interface Shape { void draw(); void draw(float scale); } Then in Circle class: void draw(float scale) { writeln("This circle's radius is ", radius*scale); } void draw(){ draw(1); } Then in Rectangular class: void draw(float scale) { writefln!"This rectangle's dimensions are %sx%s."(width*scale,height*scale); } void draw(){ draw(1); } So calling shape.draw() would draw with the original scale, otherwise you could call as shape.draw(some_scale); The problem is these are just 2 shapes and it could be much more, so it would required to repeat all this. In D there would be a better way to do such thing? Thanks, Matheus.
May 16 2022
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/16/22 22:08, matheus wrote:

 interface Shape {
    void draw();
    void draw(float scale);
 }
Interfaces can have 'final' functions: interface Shape { void draw(float scale); final void draw() { draw(1); } } Obviously, for that to work, now Circle.draw() etc. required to respond to a 'scale' parameter.
 In D there would be a better way to do such thing?
Reminding that class hierarchies can be deeper as well: interface A { } class B : A { void foo() {} } class C : B { override void foo() {} } Ali
May 16 2022
prev sibling parent Mike Parker <aldacron gmail.com> writes:
On Tuesday, 17 May 2022 at 05:08:30 UTC, matheus wrote:

 In D there would be a better way to do such thing?
Nothing really specific to D, but for one or two properties, you might just add them as function parameters with default values: ```d void draw(float scale = 1.0f); ``` If you have a number of them (scale, color, blend state, etc.), then you might add them as members of the `Shape` class. You could then expand on that with a single draw function in the `Shape` class that handles the actual drawing, and the subclasses would then call that internally after, e.g., setting up any vertex buffers or whatever specific to the shapes. ```d class Shape { private: float scale; RGBA color; DrawBuffer buffer; // some API-specific vertex buffer or whatever protected: void drawImpl() { // get the shape on screen } public: abstract void draw(); } class Circle { override void draw() { // set up buffer ... drawImpl(); } ``` Or you could have a `DrawProperties` struct independent of the `Shape` hierarchy that you can fill out and pass to every draw call. Or set global properties in the renderer and draw objects that have the same properties all at once. There are several ways to go about it.
May 16 2022
prev sibling parent reply forkit <forkit gmail.com> writes:
On Tuesday, 17 May 2022 at 04:37:58 UTC, Ali Çehreli wrote:

In you OOP example, I am curious why you chose Shape to be an 
interface, rather than a base class.
May 17 2022
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 5/17/22 02:30, forkit wrote:
 On Tuesday, 17 May 2022 at 04:37:58 UTC, Ali Çehreli wrote:

 In you OOP example, I am curious why you chose Shape to be an interface,
 rather than a base class.
I always have the same question. :) interface feels lighterweight, so it is an arbitrary decision for me. I haven't hit any situation where a class would be needed. One difference is, class requires the 'override' keyword in the subclasses. I really don't know any technical difference. Ali
May 17 2022
prev sibling parent Dom Disc <dominikus scherkl.de> writes:
On Tuesday, 17 May 2022 at 09:30:12 UTC, forkit wrote:
 On Tuesday, 17 May 2022 at 04:37:58 UTC, Ali Çehreli wrote:

 In you OOP example, I am curious why you chose Shape to be an 
 interface, rather than a base class.
You can inherit from multiple interfaces, but only from one base class. So if you need multiple inheritance, better use interfaces. Especially at the first level of objects I would almost always use only interfaces, no classes. I consider this better design.
May 17 2022
prev sibling next sibling parent bauss <jj_1337 live.dk> writes:
On Tuesday, 17 May 2022 at 00:10:55 UTC, Alain De Vos wrote:
 Let's say a shape is ,a circle with a radius ,or a square with 
 a rectangular size.
 I want to pass shapes to functions, eg to draw them on the 
 screen,
 draw(myshape) or myshape.draw();
 But how do i implement best shapes ?
In addition to all the answers, just remember that if you want to make a square class then it should inherit from the shape class and not the rectangle class. It might seem like the obvious rule is for square to inherit rectangle, since a square is a rectangle, but it's only true on the surface, not in functionality. An example is below: ```d void changeRectangle(Rectangle rectangle, int amount) { rectangle.length = rectangle.length + amount; } ... // While this works, then it's functional wrong as you must change both the length/height of a square, since they cannot differ. changeRectangle(new Square(100, 100), 50); ``` You might think that it's easy to just not call changeRectangle with a square, but what if you store the rectangles in a list, map, get it from an external data source etc. then it starts being more and more complex and for no reason at all! I know your post really isn't about such things, but I think it's a good thing to learn already.
May 16 2022
prev sibling parent JG <someone somewhere.com> writes:
On Tuesday, 17 May 2022 at 00:10:55 UTC, Alain De Vos wrote:
 Let's say a shape is ,a circle with a radius ,or a square with 
 a rectangular size.
 I want to pass shapes to functions, eg to draw them on the 
 screen,
 draw(myshape) or myshape.draw();
 But how do i implement best shapes ?
You could also do something like: ```d import std; struct Circle { double radius; void draw() { writeln(format!"Draw a circle of radius %s"(radius)); } } struct Rectangle { double width; double height; void draw() { writeln(format!"Draw a rectangle of width %s and height %s."(width,height)); } } alias Shape = SumType!(Circle,Rectangle); void main() { Shape[] shapes = [Shape(Rectangle(2.0,3.)),Shape(Circle(3.0))]; foreach(shape; shapes) { shape.match!(x=>x.draw); } } ``` or ```d import std; struct Circle { double radius; } struct Rectangle { double width; double height; } alias Shape = SumType!(Circle,Rectangle); struct Drawer { int drawerState; void drawShape(Shape shape) { shape.match!(x=>drawShape(x)); } void drawShape(Circle circle) { writeln(format!"Draw a circle of radius %s"(circle.radius)); } void drawShape(Rectangle rectangle) { writeln(format!"Draw a rectangle of width %s and height %s."(rectangle.width,rectangle.height)); } } void main() { Shape[] shapes = [Shape(Rectangle(2.0,3.)),Shape(Circle(3.0))]; Drawer d; foreach(shape; shapes) { d.drawShape(shape); } } ```
May 17 2022