• Post Reply Bookmark Topic Watch Topic
  • New Topic

Why should these interfaces not inherit from each other?  RSS feed

 
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I posted a question on SO about interfaces and advice I read about in a book. I got the answer I wanted, and all was good, until this afternoon when I signed in to SO, and a comment was left saying

These interfaces should not inherit from each other.

I asked why, but still haven't received an answer. Do you see a problem with these interfaces? If so, how would you fix it, and still keep the same functionality provided in the answer?





Would it be the simplicity of the GameObject and the fact that the function it provides is unnecessary?
 
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Perhaps the question you should be asking yourself is whether or not you should be using interfaces to represent these ideas at all. Interfaces are nice and yes, you should program to them if they are defined for the objects you are using but they also add complexity. I prefer Joshua Kerievsky's approach of starting with something simple that works. Then, if you see an opportunity, refactor to a pattern. Don't start with a pattern already in mind and start implementing to it.

This goes back again to Andy Hunt's rule #1: Always consider context.

It seems like you read Bloch's advice to prefer programming to interfaces and thought, "Ok, let me make everything in this program an interface." The advice said to prefer, it didn't say always. The advice was meant to be applied in context. You seem to be using it like it's a context-free rule.

I haven't seen the rest of your program, so I have no context with which to base a yea or nay vote on interfaces here but I think you would make it easier on yourself to curb your enthusiasm and go with a simple, if naive, design first and learn some lessons from what the code really wants to be.

I told you before that your code should tell a story. You should also listen to your code when you write it. If it is too convoluted, it wants to be clarified. When it's too brittle, it wants to be teased apart and the couplings made looser, so it's more flexible. When it's too complex, it wants to be simplified. The abstraction that interfaces provide can be useful and even desirable but it also comes with an additional layer of indirection and therefore, more complexity. It seems to me your code is saying it's too complex.

Does that make sense?
 
Hank Emery
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

if naive, design first and learn some lessons from what the code really wants to be.



Let me explain why I chose to use interfaces.

Weapon is ambiguous, now, you could make it an abstract class, which is what I did before, I had a constructor that took the name and damage, and had an abstract method attack. Looked good, but wait! Let's say I had to subclass Gun, now depending on the complexity of my game, this would be fine, but let's think about this, Gun itself is a little ambiguous(let's make it abstract), is it a handgun or rifle? Both have a different set of behaviors before firing to make a successful shot, and both have different attachments(rifle has a scope, but both can have silencers, laser sight, etc). We add two more subclass to accommodate both types of Gun.

Now we have Weapon(abstract) > Gun(abstract) > Handgun,Rifle

This is getting messy and as many articles point out, a deep inheritance hierarchy should be avoided. Depending on my game environment, I might have a rifle different from the conventional type of rifle(think Unreal tournament), with it's own set of attributes/behaviors, distinct enough for another subclass.

Now, if I made Weapon an interface, and Reloadable an interface, I have a little more freedom. I create the final class HandGun and implement both interfaces without worrying about how deep the inheritance goes, same for rifle.

It seems to me your code is saying it's too complex.



My code was too complex. For me interfaces made it simpler. 

Now consider this, Gun probably shouldn't be named Gun, what if I also wanted a bow and arrow, it's reloadable every time you take shot, and again different behavior than a Gun.

Which means this Weapon > ReloadableWeapon > Guns > rifle/handgun
                                          ReloadableWeapon > Bow and Arrow 

Let's look at another problem,

Again, with Weapon(Abstract) > Sword(abstract) > Simple Sword, Fire Sword, and FireTransportSword.

Let's say sword split into two other swords, simple sword i can only do swing attack(nothing more, nothing less). Now let's say I have a fire sword, it can swing, the blade has a fire I can control, and can shoot fireballs. If Java allowed multiple inheritance, then having FireTransportSword extending Simple Sword and Fire Sword would create the diamond problem, both Simple Sword/Fire Sword override attack and implement it in different ways, how would FireTransportSword(A sword that has the same attributes and attack as simple and fire sword, but it also allows me to cut a hole in space/time to suck enemies into a black hole) implement attack?

I could make Sword and Transport and interface(also allowing other objects to implement transport in there own way), and using the weapon interface, create my swords without the diamond problem. I can even be more flexiable with my swing, I can swing and shoot fireballs at the same time.

To me interfaces made it simple, and made sense. What makes sense to you? How would you do it?
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I wouldn't go hog wild with all those different kinds of weapons until I got something working. Then I'd slowly introduce a different weapon that was only a little different from my other weapon. I'd see if that difference made my code and design messy. If it did, I'd refactor. I'd repeat this over and over, adding one small difference at a time, then refactoring each time it made the code/design messy. It sounds to me like you didn't do this but instead, went wild with a lot of imagined requirements, just one after another, without trying to put each one in first then refactoring. It seems like you were "refactoring" in your head and in abstractions instead of in the code.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Hank Emery wrote:To me interfaces made it simple, and made sense. What makes sense to you? How would you do it?


You're misunderstanding where interfaces can add complexity. Yes, they make your abstractions and high-level ideas simpler. However, the simplicity gained in the high-level abstract is paid for by added complexity in the underlying implementation details.

I was looking at your design in the other thread and your problem there was that every time you added a new GameObject, you were afraid that you were going to have to change your Character interface to accommodate it.

Everytime I have a new GameObject the player can interact with, I would have to update my Character interface with pickup/use methods. The problem with this is, a GameObject sometimes can be used in different ways.


That's a design smell and you seem to recognize it. Your problem is that you don't know what to do now. Here's a hint: when you find yourself saying things like "can be used in different ways" and you detect a smell, that means you need to find another abstraction so that you can treat different things similarly. And this is where you'll see yourself paying for the simplicity you had in a higher level of abstraction.

I'm actually working on a book with a core theme of thinking backwards. You are stuck because you want to find a way to solve your problem by looking at it from the perspective you have now. Most people will try to force that perspective and try to bend and twist their design to fit their perspective. Code and design has a way of fighting back and it's seldom the programmer who is the winner. However, I suspect that if you change your perspective and not think from the perspective of "all the different ways the character can use a weapon" but instead think of "how a game object can benefit the character" this will probably lead you to a more stable Character interface and abstraction.  After all, any weapon the character uses can be used to inflict damage on an opponent, no matter what kind of weapon it is. A weapon used by an opponent on your character will also inflict damage, but this time on your character.

When damage is inflicted, your character's health is affected. When a character uses a healthpack, again it's health is affected. So, when you think of it backwards and ask "How does this GameObject benefit the Character" you'll probably find that there are a limited number of ways that a Character can benefit from anything. Now you can provide a consistent, stable interface for your Character so that whatever thing he picks up, that thing can "know" how it benefits the Character.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I just explained this concept to my son and it blew me away when he immediately said, "So you need to think of how the GameObject interacts with the Character instead."  My son is a gamer and he explains it this way: "It's literally an option in every video game that you can look at an object and see how it affects your character's stats."  This is exactly what I meant by "thinking backwards" -- To borrow from JFK: Think not of how your Character can use the GameObjects, think how your GameObjects can benefit the Character.

EDIT: This was a great validation of the point I made earlier. I don't play video games any more because all these new video games give me motion sickness. The last video games I remember playing with my son were four or five game consoles ago, on a Nintendo 64 playing Donkey Kong and Star Wars. If my son says being able to view how an object affects a character's stats is a feature of literally every video game, I'll take his word for it. That just tells me that what you have is a common problem that has a known solution that is most likely very similar to what I described.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
With this fresh perspective in mind, look at the interfaces you defined again. Does a weapon really attack? Who does the attacking, the character or the weapon? Doesn't it make more sense to say that it's the Character who's attacking another Character with a weapon? How does that change of perspective change the way your objects interact with each other and how your code is written?

What about the Reloadable interface, why does it have a replenish() method instead of reload()? Doesn't the latter make more sense? I've never heard a shooting video game in the arcade prompt you to "Replenish!" when your magazine is spent; they always tells you to "RELOAD!"
 
Hank Emery
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:With this fresh perspective in mind, look at the interfaces you defined again. Does a weapon really attack? Who does the attacking, the character or the weapon? Doesn't it make more sense to say that it's the Character who's attacking another Character with a weapon? How does that change of perspective change the way your objects interact with each other and how your code is written?



The character does the attacking, this is why I rewrote my interface to accommodate this. The character uses a weapon to attack with.



Side Note: This game is a hobby project, in the beginning stages. It's constantly going to get rewritten, and tested, and finished when it's finished.

What about the Reloadable interface, why does it have a replenish() method instead of reload()? Doesn't the latter make more sense? I've never heard a shooting video game in the arcade prompt you to "Replenish!" when your magazine is spent; they always tells you to "RELOAD!"


While this is true, a container could also be reloaded, although you don't say reloaded, you say refilled, different words for the same behavior. The Reloadable interface could be applied to potion bottle, health bar, and weapons etc.   
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Then you might try going with the general concept of Container and refilling it to make the semantics fit better. A magazine is a container of bullets, a quiver is a container of arrows, a flask is a container of liquid, a box is a container of stuff, etc. If a container is empty, then you refill it.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Hank Emery wrote:


Reading your code out loud also helps you detect problems in the semantics of your design. How are you reading that method signature? To me, it reads "A character uses a weapon to attack with an enemy."  That's a very awkward sentence and it doesn't make much sense.  First, try to say your intent in plain English: "I want the Character to attack its enemy Character using a particular weapon." This leads you to write the code that you want to write:

Or something like that. I like to think of this as "listening to what code wants to be".
 
Hank Emery
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:

Hank Emery wrote:


This is still smelly. If you design it this way, your interface will not be stable until your imagination becomes stable and runs out of different things to add to your inventory. Again, when you hear yourself saying "I need to handle different things" and you smell instability in your interface, you need to find the commonality in those different things and abstract them away so that your interface only deals with what's common and your implementations handle the details of what's different in each of them.



How would you fix the character interface? This is what I have so far:


 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Hank Emery wrote:
How would you fix the character interface?


I already went over that in excruciating detail over the weekend. Maybe my long-winded response was too much to read? In short, I would change my perspective and try to make a stable interface for Character that is based on the common things that it can benefit from by interacting with different objects. Things like increasing health, agility, attack power, defenses, wealth, etc. Those are all inherent to the Character. I would move the responsibility of knowing how a Character's attributes are affected to the different things that have influence in changing, increasing, or decreasing one or more of these Character attributes.

Edit: On second thought, maybe the details were too excruciating or maybe they were just detailed in my mind and I didn't express myself in a way that you could see what the details were. I wouldn't blame you if your eyes glossed over reading what I wrote over the weekend. My apologies for being snippy there.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I just wrote this for my book this morning:

I wrote:Your backwards brain is what ... keeps you thinking more about the implementation details and less about the intent."


The names you chose for your parameters are signs that your backwards brain is hard at work. "enemyObj", "reloadObj", "reloadadd", "gameObj", "Reloadable vs ReloadableWeapon", all these names have one thing in common: They reveal your implementation-focused mindset.  Why tack on "Obj" to those names? Everyone reading your code will know those are objects, so why do you feel you need to constantly remind them? Or is this your way of making your code self-documenting? If it is, it's not a good practice. Well, self-documenting code is a good practice but tacking on a useless prefix or suffix wart like "Obj" isn't.  Charles Simonyi regrets how his Hungarian notation was misused over the years and turned into a meaningless convention because of mindless, rote, and uninformed use. Tacking on "Obj" to the name of something that is clearly an object is going down that same path to uselessness.  It's like me calling you HankMale or HankPerson.

Here's another smell: When your method name has the name of another entity. The name addItemToInventory not only reveals a mindset focused on implementation (you know that your Character has an inventory because you just announced it with your method name), it also hints at a misplaced responsibility.  Is the logic in this method going to show the details of adding an item to the Character's inventory of things? Why? Shouldn't the details of that operation be encapsulated in an Inventory object?  Why isn't that method just called pickUp(Item whatever)? This way, it will be more abstract. Who cares how the Character handles that item internally after he has picked it up? Nobody does, at least not at this level of abstraction.
 
Hank Emery
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
No worries! It's going to take time for me to come to that Aha! moment and realize the best solution. Let me add a little more detail and work from there.

In my game I have a variety of weapons with the following interface:



Not every weapon is reloadable, for example swords, so they must be applied accordingly.



I have the following class:


I now store it in it's own collection like so:



I also have close quarters weapons (swords, knives, etc..)



Problems:
1. I could store all my weapons in a List<Weapon>, problem is I wouldn't have access to the replenish() method if I do.
2. Creating a List collection for every Weapon type
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You might want to read this thread: https://coderanch.com/t/679568/java/feature-exist-returning-downcasted-object

I think the question will give you a different perspective as well, or at least widen your current perspective. It's relevant to your inventory of things and how you might be able to create a flexible design that takes advantage of Java's type system, or work around its limitations.
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Hank Emery wrote:
In my game I have a variety of weapons with the following interface:



Not every weapon is reloadable, for example swords, so they must be applied accordingly.
...


This is what I was referring to as going hog wild. I think you should definitely curb your enthusiasm, put your imagination on a leash, and just implement one weapon first. It's far more difficult to juggle several different, untested, and nebulous ideas in your mind than it is to have just one bouncing around in there. When you have your idea written down as code and a testable design, then you can learn from it. Working software is the best way to learn about what your code and design wants to be, even if your software isn't fully working or designed the best way yet. You still learn from it. If not what it should, at least you'll learn what it shouldn't be.  It's like my wife going shopping. She'll often just go to the store not knowing what she wants. She'll look at different things and try them on. As she does that and moves on the next thing, she slowly gets a better idea of what she's actually looking for. Of course, what she's looking for is always the last thing she sees, right?
 
Hank Emery
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:You might want to read this thread: https://coderanch.com/t/679568/java/feature-exist-returning-downcasted-object

I think the question will give you a different perspective as well, or at least widen your current perspective. It's relevant to your inventory of things and how you might be able to create a flexible design that takes advantage of Java's type system, or work around its limitations.



Casting is fine, but, this means, I will have to use the instanceof operator, outside of the equals method, isn't it bad practice? 
 
Junilu Lacar
Sheriff
Posts: 11598
187
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
No, I'm not telling you to cast things. Not just yet. I'm suggesting that you consider that idea of having different things in a list and being able to filter them according to a certain criteria.

My idea last night was that a GameObject might respond to a query, specifically, as part of a call in a Predicate, that determines whether or not it has some capability. I think Bloch actually writes about better alternatives to instanceof in his "Effective Java" book.

If a GameObject had this method:


You might be able to use one of the methods of Class: https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html like isInstance() or isAssignableFrom() to filter it from a list using a Predicate.

Not using instanceof is another one of those "rules" that you have to take in context. In most cases where polymorphism is not properly used, instanceof is a smell. In other cases, like in a framework that needs some extra flexibility that polymorphism might not be able to provide, then it may be acceptable and even appropriate to use. Again, the trick is knowing the difference.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!