Java-Gaming.org Hi !
Featured games (90)
games approved by the League of Dukes
Games in Showcase (741)
Games in Android Showcase (225)
games submitted by our members
Games in WIP (823)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Using default methods in interfaces effectively  (Read 1432 times)
0 Members and 1 Guest are viewing this topic.
Offline cygnus
« Posted 2017-07-11 21:00:53 »

Hello. With Java 8 came a new and useful feature for interfaces - default methods. For those of you who don't know, this is what they look like:
1  
2  
3  
4  
5  
public interface Test {
     default void test() {
          System.out.println("Testing");
     }
}

Simply put, they allow for methods in interfaces that are not static (i.e. that are inheritable) to write code. I have been using this to make something akin to the Component pattern, meaning each game object is made up of multiple unrelated parts and they all come together in such a way that, with some code in the actual object, they form a complete entity.
Here is an example:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
public interface Collidable{

    public int getXPixel();
   
    public int getYPixel();

    public Hitbox getHitbox();

    default void renderHitbox() {
   renderHitbox(0xFF0C00);
    }
   
    default void renderHitbox(int color) {
   Hitbox hitbox = getHitbox();
   int xPixel = getXPixel();
   int yPixel = getYPixel();
   Game.getRenderEngine().renderSquare(color, xPixel + hitbox.getXStart(), yPixel + hitbox.getYStart(), hitbox.getWidthPixels(), hitbox.getHeightPixels());
    }

    /**
    * Called in the <tt>addToLevel</tt> method of the object
    */

    default void addCollidable() {
   Game.getLevel().addCollidable(this);
    }

    /**
    * Called in the <tt>removeFromLevel</tt> method of the object
    */

    default void removeCollidable() {
   Game.getLevel().removeCollidable(this);
    }
   
    /**
     * Call in <tt>MovingObject.onMove</tt>. Necessary for the proper storage of this object in certain data structures
     */

    default void updatePosition() {
   Game.getLevel().updateCollidable(this);
    }

}

This would then be implemented, along with other interfaces, in a class such as this:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
public abstract class MovingObject extends LevelObject implements Updateable, Collidable {

    public static final int AIR_DRAG = 4;
    public static final int MAX_SPEED = 20;
    private int xVel, yVel, dir;

    protected MovingObject(int xPixel, int yPixel) {
   super(xPixel, yPixel);
    }

    @Override
    public void update() {
   move();
    }

    @Override
    public void addToLevel() {
   addUpdateable();
   addCollidable();
    }

    @Override
    public void removeFromLevel() {
   removeUpdateable();
   removeCollidable();
    }

    protected void onMove(int pXPixel, int pYPixel) {
        updatePosition();
    }

    protected boolean getCollision(int moveX, int moveY) {
   return false;
    }

    protected void move() {
   //move...
    }

    public void addVel(int xVel, int yVel) {
   //add velocity...
    }
}

I have found this extremely helpful in my code. Tell me what you think of it and see if you can use it!
EDIT - however, funnily, I forgot to mention the largest and most obvious disadvantage about this - you cannot store instance variables. You can do what I did in Collidable and just force them to implement methods, but this is not always optimal.
Offline tariqbroadnax

Junior Devvie


Medals: 2
Exp: 3 years



« Reply #1 - Posted 2017-07-11 22:40:46 »

To summarize, you believe default methods are useful because they allow you to essentially extend the functionality of more than one class.

To extend the discussion...

For clarity, extending refers extending an abstract class while extension refers to extending or implementing functionality from default methods.

While extending multiple classes is mostly a technical restriction, it is not a bad one. Extension inherently creates complexity, making your code more difficult, even if only a little, to understand. Most people do not know about them and even if they did, a good programmer wants to make their code as simple as possible. Use default methods only if you must.

Regarding your code...

I seem like you want to be able to use the interface and not have to keep adding that line of code. This is a definitely a composition over inheritance problem. One of the issues with approach is that it does not scale well. You will end a bunch of classes or game entities, each only different by the interfaces. Unless you are making a very very small game, this will become very difficult to manage. I am sure there are more issues but I am no OOP guru.

The composition approach is to create one class, particularly one that implements Collidable and decide whether it is collides based on the data and not the its definition. For example, you could have some with a normal hitbox and some with an empty hitbox.

Actually it might be a Data Driven problem. IDK. q.q
Offline cygnus
« Reply #2 - Posted 2017-07-11 23:29:53 »

I would disagree with your thought that it brings unnecessary complexity in the form of multiple different classes that are variations of each other just with different interfaces. The reason I came up with this was to fix the following problem with my Block hierarchy. I wanted 3 types of blocks - Producer, Consumer, and Function (combination of producer and consumer), each of which is self-explanatory. However, I cannot have Function be a child of both classes, of course, so that would mean I would have to rewrite code from both of them which has the added problem of not being able to store the Function block as a Producer or a Consumer. However, with this method, I can do that:
1  
2  
ProducerBlock  p = new FunctionBlock(); // EDIT - I'd just like to point out that this is only an example and I will not have for some reason a concrete class called FunctionBlock :D
ConsumerBlock c = new FunctionBlock();

There are limitations, but provided that both Producer and Consumer have what is necessary and nothing more and I run what is necessary and nothing more on them, it works well.
In my codebase, this is a very handy thing for keeping storage of objects manageable and reasonable. Also, you said:
Quote
The composition approach is to create one class, particularly one that implements Collidable and decide whether it is collides based on the data and not the its definition. For example, you could have some with a normal hitbox and some with an empty hitbox.
I had this before - simply a Hitbox instance called Hitbox.NONE. However, this approach removes the need for checking this every time we want to do certain operations.

Basically, I find it more cumbersome to have one high level class with tons and tons of methods that are barely used by many children. There are of course other solutions to this, but this method allows for them to actually be an instance of some form of the functionality which is something that outweighs, for example, having some sort of MouseActionDetector with a bunch of anonymous inner class methods if I wanted to have a Block run code when you click on it.

I really do appreciate your thoughts though as of course I cannot predict what will result of this, but from the points you made, I still think it works well.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline tariqbroadnax

Junior Devvie


Medals: 2
Exp: 3 years



« Reply #3 - Posted 2017-07-12 01:20:53 »

I would disagree with your thought that it brings unnecessary complexity in the form of multiple different classes that are variations of each other just with different interfaces. The reason I came up with this was to fix the following problem with my Block hierarchy. I wanted 3 types of blocks - Producer, Consumer, and Function (combination of producer and consumer), each of which is self-explanatory. However, I cannot have Function be a child of both classes, of course, so that would mean I would have to rewrite code from both of them which has the added problem of not being able to store the Function block as a Producer or a Consumer. However, with this method, I can do that:
1  
2  
ProducerBlock  p = new FunctionBlock(); // EDIT - I'd just like to point out that this is only an example and I will not have for some reason a concrete class called FunctionBlock :D
ConsumerBlock c = new FunctionBlock();

There are limitations, but provided that both Producer and Consumer have what is necessary and nothing more and I run what is necessary and nothing more on them, it works well.

I guess this is an example of default methods being useful. But in practice, I cannot imagine there being many examples as obvious as this. But then again, I am only one person.

Basically, I find it more cumbersome to have one high level class with tons and tons of methods that are barely used by many children. There are of course other solutions to this, but this method allows for them to actually be an instance of some form of the functionality which is something that outweighs, for example, having some sort of MouseActionDetector with a bunch of anonymous inner class methods if I wanted to have a Block run code when you click on it.

If the class looks cluttered, then hide details in new fields.
If you are worried about a bunch code that is not being used, then STOP because optimizing your code early leads to complex-overengineered systems.
If things are still looking that bad and cluttered, then add another high level class. You are not forced to use only one class in composition.

What is most important is creating a system that simple and natural. If 30 classes that are different only through inheritance is more natural than 4 or 5 that are data driven, then feel free.
Offline h.pernpeintner

JGO Knight


Medals: 65



« Reply #4 - Posted 2017-07-12 11:07:47 »

That's what everyone feared people will use default methods for Smiley There are concepts like traits or mixins, which (I think) go in the same direction as what you are targeting.... you want to have multiple inheritance (of implementation). Why inheritance? Because that's what we OO people use to reuse implementations. Why? Becuase it is so damn easy to use, from a very shortsighted perspective. But multiple inheritance is something you should avoid, and the magic words have already been said: Composition over inheritance.

There's (usually) nothing wrong with using multiple (interface) inheritance... but you want (default) implementations, if many (interface) implementations share (almost) the same logic. Kotlin tackles this problem whith making composition as easy as (implementation) inheritance by adding delegation as a first-class citizen to the language. Example: https://kotlinlang.org/docs/reference/delegation.html . Scala is even a bit more crazy and allows for stateful traits - interfaces with implementations and fields and so on.

Funny, I experimented with default methods to have a more comfortable implementation of Transformations in my engine... I didn't want to put Transform ontop of my hierarchy, so I added an Interface Transformable. This is implemented by Transform and provides a method getTransform. All methods, like getPosition etc. are default implemented and call an abstract getTransform method... the concrete Implementation (Transform) implements Transformable and overrides the default methods... so every class can implement Transformable, implement the abstract getTransform method with returning a field and voila, you're done, no (implementation) inheritance and everything can easily be a Transform Smiley But I ditched it. Felt ugly somehow.
Offline 65K
« Reply #5 - Posted 2017-07-12 14:50:56 »

Interfaces are a useful concept, not least because it is simple. Simple is good. Which is no longer true for interface default methods.
The reason they were added was to be able to add the streaming stuff to collections classes. Alright, what we got is the ugly little brother of abstract classes.
There is another issue with your design: the probably unnecessary dependency from Collidable to the game and level classes. Minimizing dependencies is very important for developing maintainable software.

Lethal Running - a RPG about a deadly game show held in a futuristic dysoptian society.
Offline cygnus
« Reply #6 - Posted 2017-07-12 16:09:29 »

Well, I see your point. I suppose I'll change it, but the problem is I just can't think of good solutions... of course, that's part of game development XD so I'll come up with an answer eventually.
Offline tariqbroadnax

Junior Devvie


Medals: 2
Exp: 3 years



« Reply #7 - Posted 2017-07-12 17:06:15 »

There is another issue with your design: the probably unnecessary dependency from Collidable to the game and level classes. Minimizing dependencies is very important for developing maintainable software.

In layman terns, have your Level add Collidables and not Collidables add themselves to the level. Makes a lot of sense because Collidables or the things are collidable exist INSIDE your level.

I cannot stress how important it is to think SIMPLY and not use over-complicated solutions.
Offline cygnus
« Reply #8 - Posted 2017-07-12 18:08:48 »

Hmmm. Well I had that before, I had methods that the level used to add, for instance, DroppedItem objects to itself, and it would return if it was successful. However, I couldn't figure out a way that I felt was good for other objects.
Offline tariqbroadnax

Junior Devvie


Medals: 2
Exp: 3 years



« Reply #9 - Posted 2017-07-12 18:34:54 »

Hmmm. Well I had that before, I had methods that the level used to add, for instance, DroppedItem objects to itself, and it would return if it was successful. However, I couldn't figure out a way that I felt was good for other objects.

Just have one method for add the general entity. i.e. addEntity(Entity entity);

All your Level class really has to do is update entities. If each your entities each have all possible data (hitbox, graphics, etc) then you can handle things like collision and rendering without knowing the subclass.

Then have your DroppedItem extend Entity and put any DroppedItem specific code inside it.

Again, in the worse case scenario, your scene will know of few but multiple high level entities. i.e. addNPC(NPC npc), addCharacter(Character character), addMonster(Monster monster)
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Sickan
« Reply #10 - Posted 2017-07-12 20:23:15 »

Interfaces are a useful concept, not least because it is simple. Simple is good. Which is no longer true for interface default methods.

Sometimes, simplicity can be an issue as well, making it hard to express your intent in code - which causes you to end up with a simple language, but complex code. See this example:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
public interface Positioned {
    Vector2 getPosition();

    default float getX() {
        return getPosition().x;
    }
   
    default float getY() {
        return getPosition().y;
    }
}


Without default methods, you'd have a lot of calls like getPosition().x littering the codebase. In my opinion, this is a better solution.
Offline cygnus
« Reply #11 - Posted 2017-07-12 21:37:36 »

Quote
Just have one method for add the general entity. i.e. addEntity(Entity entity);

All your Level class really has to do is update entities. If each your entities each have all possible data (hitbox, graphics, etc) then you can handle things like collision and rendering without knowing the subclass.

Then have your DroppedItem extend Entity and put any DroppedItem specific code inside it.

Again, in the worse case scenario, your scene will know of few but multiple high level entities. i.e. addNPC(NPC npc), addCharacter(Character character), addMonster(Monster monster)
What I had originally was the latter. Just a bunch of different methods for each object. Then I switched to a single one with a bunch of instanceof and if/else statements, but I just thought that that was so clumsy and there had to be some way to make use of Java's hierarchy. So I added a method in the top level class, addToLevel, which would go directly into the level objects and add it. I realize that it means it's not separate from the level, true, but it meant that I could override it in child classes. It seemed so much more natural and clean.

EDIT - and also, for this point
Quote
If each your entities each have all possible data (hitbox, graphics, etc) then you can handle things like collision and rendering without knowing the subclass.
the way I see it is I can go through every object, check if it needs rendering, collision, etc., or I can use the default methods for a Renderable or Collidable interface and be sure that the class actually needs what it's being checked for. I still don't need to know the subclass, all I need to know is given to me by the interface. I do see another disadvantage to that though - it means I have to store multiple lists, many of which may well contain the same object. However, as it's just a pointer, it shouldn't be too large of a negative effect.
Offline tariqbroadnax

Junior Devvie


Medals: 2
Exp: 3 years



« Reply #12 - Posted 2017-07-15 18:55:32 »

What I had originally was the latter. Just a bunch of different methods for each object. Then I switched to a single one with a bunch of instanceof and if/else statements, but I just thought that that was so clumsy and there had to be some way to make use of Java's hierarchy. So I added a method in the top level class, addToLevel, which would go directly into the level objects and add it. I realize that it means it's not separate from the level, true, but it meant that I could override it in child classes. It seemed so much more natural and clean.

Quote
the way I see it is I can go through every object, check if it needs rendering, collision, etc., or I can use the default methods for a Renderable or Collidable interface and be sure that the class actually needs what it's being checked for. I still don't need to know the subclass, all I need to know is given to me by the interface. I do see another disadvantage to that though - it means I have to store multiple lists, many of which may well contain the same object. However, as it's just a pointer, it shouldn't be too large of a negative effect.

I'll make it simple for you. Is that optimization worth the cost in code quality. No matter what anyone says, both methods, and many other, are possible.

I personally value code quality, particularly less classes, less static code, less lines of code, and more encapsulation. Would it not be amazing if your "core" engine was heavily data-driven and just 30-40 classes with the actual data being in arbitrary files. I am also willing to sacrifice speed because speed really does not matter most of the time.
Pages: [1]
  ignore  |  Print  
 
 

 
xxMrPHDxx (21 views)
2017-11-21 16:21:00

xxMrPHDxx (14 views)
2017-11-21 16:14:31

xxMrPHDxx (16 views)
2017-11-21 16:10:57

Ecumene (114 views)
2017-09-30 02:57:34

theagentd (150 views)
2017-09-26 18:23:31

cybrmynd (260 views)
2017-08-02 12:28:51

cybrmynd (250 views)
2017-08-02 12:19:43

cybrmynd (247 views)
2017-08-02 12:18:09

Sralse (260 views)
2017-07-25 17:13:48

Archive (880 views)
2017-04-27 17:45:51
List of Learning Resources
by elect
2017-03-13 14:05:44

List of Learning Resources
by elect
2017-03-13 14:04:45

SF/X Libraries
by philfrei
2017-03-02 08:45:19

SF/X Libraries
by philfrei
2017-03-02 08:44:05

SF/X Libraries
by SkyAphid
2017-03-02 06:38:56

SF/X Libraries
by SkyAphid
2017-03-02 06:38:32

SF/X Libraries
by SkyAphid
2017-03-02 06:38:05

SF/X Libraries
by SkyAphid
2017-03-02 06:37:51
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!