Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

The Interface Component Pattern

Discussion in 'General Discussion' started by jeango, May 17, 2019.

  1. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
  2. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,416
    The problem with your first example that uses an interface are the calls in OnCollisionEnter to GetComponent. You should really avoid calling GetComponent as much as possible, especially the generic version, which tends to be nearly twice as slow as the non-generic counterpart. It should be cached on Awake() into a variable, and referenced only through that.
     
  3. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Oh I didn't know there was a performance difference with the non-generic version. Duely noted

    however caching on Awake makes no sense in this case because you're not always colliding with the same object.

    The only alternative would be to use SendMessage instead of getting the component, but that's a completely different subject
     
  4. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,416
    Yes obviously caching on awake in this case wouldn’t work, I’m just saying that’s how GetComponent should be used.

    You have two alternatives, create an object cache, each time there is a collision, check the cache, if the colliding doesn’t exist, add it to the cache.

    Or use Sendmessage as you said to broadcast the collision to all subscribers.
     
  5. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Thanks for the suggestion. I guess I now have a subject for my next blog post. I have a generic object pool pattern, guess I could use the pool as object cache.
     
  6. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,416
    Also if you want a strongly typed event system, much better than SendMessage, check this out, I love it.
    http://www.willrmiller.com/?p=87
     
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    558
    But if it's about interfaces, there's no point complicating the example with caching. I mean, there are a million other possibly useful things you could cram in. And anyway, it's at best a tiny speed-up - you're only colliding a few times a second and it's not like you have 400 components. The extra memory it uses may slow you down.

    What do you mean about non-generic getComponent? The one with a string is slower: GetComponent"Collider"), but that was for back in unityscript. The other two typeOf(Collider) (which is also not generic), or angle-brackets-collider, seem about the same.
     
    Kiwasi, elmar1028 and angrypenguin like this.
  8. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Yeah I think GetComponent is only a performance issue if you’re doing it on update. A raycast is apparently more expensive from what I read. And like you said, caching is beside the point of my blog post. Still it’s interesting to know that there are alternatives.

    Regarding <Component> vs typeOf(Component) I read an article dating back from 2014. Not sure if it’s still relevant. Would have to make some profiling to know for sure. I don’t see why it would be different though, unless it did some hidden stuff under the hood
     
    halley likes this.
  9. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Oh don’t get me wrong, I love the observer pattern :) just beyond the scope of my tutorial. I’m doing Event driven architecture any time I can :)
     
  10. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    To get around the problem I sometimes add the component to a dictionary were transform is key and component instance is value. Then I can use that at a o(1) expense from oncollide.

    Problem is you can never know how often OnCollide will trigger also some problems require OnCollisionStay. For example we have networked physics and if you touch one item that touchs another item that touches another item you should take ownership of all these items recursively. Otherwise physics will be calculated on the remote client and look a bit jerky.

    Here is an example when I take ownership of a bunch of remote owned items
     
  11. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Yup that’s exactly what @Meltdown means by object cache
     
  12. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    He means a cache to a single object, not a lookup dictionary. But yeah, they are both caches
     
  13. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    Btw, I agree withyou, examples and tutorials should focus on the problem they try to explain. Not go out of scope. Though I would probably have changed GetComponent<T> to a custom method. That way the reader just knows that you fetch the reference, not how. That way they are not led to believe GetComponent is the silver bullet to all problems.
     
  14. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Honestly the getcomponent is only there to illustrate a use case. I use this interface pattern everywhere I can. I actually use it for observer (Listener interface), object pooling (Poolable interface), and just about every time I need a list of objects that implement similar functionality
     
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    11,760
    Don't sweat it. There is nothing at all wrong with GetComponent as it is used in your example.
     
  16. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Actually, I’m not too sure what to think, is the use of getComponent the only noteworthy thing about my blog post? I had expected to get some feedback about the actually pattern, or the presentation, the tone, grammar, structure or just about anything else than the example use case used to Illustrate my point :shrug:
     
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    11,760
    The same thought came to me, and for that reason I nearly didn't comment at all. Honestly, the GetComponent part is perfectly normal and did not stand out to me whatsoever. I only posted in case the previous posts here were making you think it was a problem. Alas, I only skimmed the rest, so can't yet give informed/useful feedback on it, and am just heading off for the evening so I'm unable to do so at the moment.
     
    Pagi likes this.
  18. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    I only commented on the others, can read it and get back with feedback
     
  19. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    1,635
    So, using an interface is now a pattern.

    What a progressive world we're living in.
     
  20. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    The pattern is not called "Interface Pattern" but rather "Interface Component Pattern". There's a difference.
     
  21. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    1,635
    Yeah, right. I N N O V A T I O N
     
  22. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    I’m not claiming it’s innovative or anything, but it’s definitely not something I’ve seen done elsewhere. You don’t like it, fine, no need to be agressive about it
     
  23. sinzer0

    sinzer0

    Joined:
    Aug 29, 2013
    Posts:
    114
    Why do you still need the interface in your final example?

    Code (CSharp):
    1. public abstract class Damageable : MonoBehaviour, IDamageable
    2. {
    3.     public abstract void TakeDamage(Damage damage);
    4. }
     
    angrypenguin likes this.
  24. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Because the OnCollisionDamage script still works with IDamageable
     
  25. sinzer0

    sinzer0

    Joined:
    Aug 29, 2013
    Posts:
    114
    But cant you just do GetComponet<Damagable>() on the abstract base class and then it returns the correct concrete damagable component attached to that game object. I guess I don't understand why the abstract base class and interface are needed in this example. I'm probably missing something though.
     
    angrypenguin likes this.
  26. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    You could, and it would work, but you would have a dependency with the Damageable class.

    Depending on an interface allows for much more scalable code.

    The role of the Damageable abstract class is to be able to take advantage of unity’s serialisation in the inspector. It wraps the interface in a serializable component
     
    sinzer0 likes this.
  27. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    558
    My comments:

    o Those first bullet-points look like quotes. It's normal to want to aggregate all the other stuff you found useful, but the internet is searchable. I'd use your own words, or at least mention that it's the standard advice. I feel like people are very sensitive to the many paste-up blog posts. If something looks copied, they're done reading.

    o The SOLID stuff is probably more confusing than helpful.

    o Who's the audience? It seems to be a starting OOP programmer who hasn't used Unity much. I mean, anyone who uses Unity already wants to put things as components instead of inheriting multiple interfaces. That's the Unity way. And most Unity programmers don't really know inheritance, which you sort of assume right away that they do. But anyone who's gotten to inheritance is probably going to read about pure interfaces next, anyway, in whatever book taught them inheritance.

    Put another way, I see the Damage class with a single int and understand the point - it will eventually have the elemental type, possible negative status effect... . But won't your target audience just be confused by why ints need to be in a class?

    o The example really needs 2 Damage classes (and two Health classes). The point of a pure interface is to unite two+ health systems that work in completely different ways. I'm also not sure about splitting takeDamage and Health. After all, the only point of health is to be damageable.

    o But I can also see that the main thing is that hack to put an array of C# interfaces in the Inspector. That's sort of buried. You might cut a lot of the rest, and try to start there. Focus on the "jamming interfaces into Unity" part.

    But the things is, 90% of Unity Answers/Forums is replies to try to work it out for themselves - trying to explain something helps you understand it better. If writing that helped you, then it was worth it.
     
    RecursiveFrog and jeango like this.
  28. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Thanks for the time and the advice

    As far as copy-pasting, I’ll take it as a compliment, as there’s absolutely 0 copy-paste done, it’s all my own words. I teach programming, so I guess that’s why it sounds academic. But point taken, I’ll try to be more mundane.

    My target audience is OO programmers who want to apply OO principles in Unity. That’s my background, and it’s been my Center if interest with Unity since I started using it in 2012.

    But I’m also trying to appeal to a larger audience, that might know a little bit of OO but didn’t dig deeper.

    You’re probably right about the whole point being buried into the mass. Probably my academic approach. Perhaps a video where you can skip to where you want is the way to go.

    Oh and you’re right about learning from explaining. Rubber duck FTW :)
     
  29. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    About TakeDamage being separate from health, it’s actually a biased assumption to say that taking damage only has to do with health.

    You could have objects with no health that are destroyed when they take damage, or that change state. The damage could also go through a lot of processes before the health is actually impacted

    The Single Responsibility Principle encourages you to have only one purpose for a class. Health should only have one purpose: manage health (max, min, current) perhaps an event when it drops to 0, and that’s it.

    That’s not how they do in online tutorials, because they are not trying to teach good programming, they are trying to teach the basics. That’s how you end up with people not knowing about inheritance (though I hope you’re wrong when saying most programmers don’t know about it)
     
    RecursiveFrog likes this.
  30. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    1,161
    By "unite" you mean that in a way that interfaces can be used in place of multiple inheritance? Yes, It would be cool if the example were something that could only be done with interfaces. The example shown in the article could just as easily be done with a base-class monobehaviour called Damageable and some derived classes.
     
  31. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Challenge accepted

    Édit: however it’s important to understand that the point of interface is to allow your code to be resilient to CHANGE. I could show you 1000 examples, and you could always find a way to do it without an interface, however if I showed you an example where I changed the specifications of a problem 1000 times, if you coded with interfaces, you would hardly ever have to modify existing code, you only need to write a new class that will plug itself into the existing. That’s something you can’t easily do with pure inheritance
     
    Last edited: May 20, 2019
  32. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    Also this is why composition over inheritance is a thing. I often turn to composition instead of inheritance and I seldom (if ever) regret the decision.

    Open close principle being a huge part of its beauty
     
  33. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    558
    I'm thinking of just single inheritance. One common inheritance set-up is a fully functional base class, with subclasses used to tweak it (it's even a Design Pattern - "Template" - from 20 years ago). Technically, that big base class is also the interface, but it's sort of de-facto. All you did was write a class, then sloptastically added the subclasses later.

    The other extreme is where the subclasses have nothing in common, there's no common data. That's when you have to specifically think about the interface they'll be sharing, specifically for the purpose of uniting them into a common type.
     
    RecursiveFrog likes this.
  34. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    558
    Ah - so you're really committed to the SOLID thing. Whereas I find this an example of why it's no good. You're stuck pondering the philosophical question whether taking damage counts as part of the Responsibility of "managing health". It seems as if it takes you away from thinking about the actual program you're making, and the practical ways to organize it.
     
    RecursiveFrog and xVergilx like this.
  35. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    Well, here I’m thinking in reverse: I’m thinking of an example I can use to illustrate the use of the interface component. Honestly, damage IS a complex thing, the thing my blog is missing is probably an example that illustrates the problem of not having an interface.

    Here's an example:
    let's say we went with the classic route of the OnCollisionDamage doing a GetComponent<Health>.TakeDamage(damage)

    everything works fine, but then the lead designer comes in and says: "hey guys, our community wants fire elementals, so we're going to add them. Tony, can you make it so that when a fire elemental takes fire damage he's healed instead? You're awesome"

    Ok so now with that architecture, the intuitive way to work would be to just change the code of Health to add a bitmask of DamageType and then modify TakeDamage to heal when the damage type is part of the bitmask. Suddenly you're adding more responsibility to the Health class.

    And as the community keeps asking for more features, and as the lead designer keeps coming into your office, suddenly your simple health component has become this huge monster of dependencies and you're looking at it like a big Jenga tower wondering when it's going to collapse.

    With my proposed design, we keep Health untouched, all we have to do is write a new Damageable variant and the job is done.
     
  36. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    558
    I don't know - "Suddenly you're adding more responsibility to the Health class." seems like another example of how the word Responsibility doesn't add anything. Likewise I'm not so sure about "has become this huge monster of dependencies". Dependency is another of those words which is often too vague to be helpful.

    You're describing the basic situation where everything is a 1-off: water elementals are immune to swords, vampires take double damage on Sunday, and so on. That's the problem of modifying a general-purpose class for a specific case: adding a check for vampires and elementals directly into Health's takeDamage(). Easy to show how it causes problems, and a known fix is plug-ins (which Unity fakes with an adjacent Component, like you're describing). In fact, that's what the old MUDs did: if you wanted a special weapon, you wrote a little procedure onto that weapon - a "weapon proc".

    In other words, my main advice is to get rid of all that SOLID talk (and DRY), but you clearly find it useful.
     
    RecursiveFrog likes this.
  37. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    5,402
    I'm still trying to piece together what the actual benefit of any of this is.
     
    RecursiveFrog likes this.
  38. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    13,866
    I most likely would have wondered that myself if I hadn't been too busy trying to figure out what benefit the article would have when the target audience (stated to be OO programmers) should already know how to do this.
     
    RecursiveFrog likes this.
  39. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    1,161
    This all goes back to the design specifications. For some games Damage is synonymous with losing health. Other games, maybe not. In the end, it's all just a matter of semantics.
     
  40. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    I think my main bias is that I go by the assumption that most people who make games with unity were first OO programmers and then got interested in making games.

    I also have a hard time imagining that professional game devs are not going by SOLID principles when making games

    I’m probably overly obsessed by this, and perhaps I’m making a wrong assumption that most people are as well.

    To get back to my article, it actually took me 4 years to come up with that, after iterating on many different solutions I ended up shelving in the « OO heresy » locker.

    But maybe I’m the heretic in all of this and I should do like Elsa and... well you know the song
     
  41. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    You will not find any understanding in these forums, most are noobs and hobbyists who do not understand what maintainable code is.

    edit: Given the level of QA in AAA studio games I actually think they do not think about SOLID and other maintainability patterns.
     
  42. Pagi

    Pagi

    Joined:
    Dec 18, 2015
    Posts:
    132
    I find articles like these useful, it's interesting to know how different people structure their code.

    I personally put common behaviour in one class when doing more complicated projects. In my last project, the player has about 10 components and some of them are networked. If I wanted to make a class for each piece of functionality, there would be 30 components. And that's just basic character control, animation, stats etc.
    I think it would be good to look for solutions that don't need components, but they are just too damn convenient.
     
    jeango likes this.
  43. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    I just love composition. Its never a bad thing splitting stuff into components. For example in our game. The difference between a sniper rifle and a assualt rifle is just which slide (action) component it uses





    edit: Though there is some inheritance at work here. All 3 components inherit a interactible component. If i redeisgned this today, I would let the ineractible compoennt be one component and the slide/action component be one seperate component.
     
    jeango likes this.
  44. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    11,760
    I think people could be getting a bit over critical, here?

    The pattern being described is the bit at the end of the article. The talk about interfaces is explaining the context, it's not the pattern itself. The pattern itself is a workaround for the fact that you can't expose a list of an interface type in Unity's Inspector.

    No, "dependency" has a pretty clear meaning. Being aware of and managing dependencies can certainly help to design and write better code.
     
    Ryiah and jeango like this.
  45. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    I don’t see it as being overly critical, in my eyes it illustrates the shortcomings of my article as it seems to be confusing people.

    I may be wrong, but I think most people don’t understand the way I structure my reasoning (story of my life).

    Anyhow, seems you got it spot on :) I really enjoy reading your quality comments on this forum.
     
    angrypenguin likes this.
  46. jeango

    jeango

    Joined:
    Dec 19, 2012
    Posts:
    72
    When a functionality is complex, and would require a lot of components, I usually make those 30 classes as C# classes, or as scriptable objects, and create a monobehaviour that strictly serves to aggregate everything in a single component.

    I like to tell my students: if your class is over 100 lines long, you’re probably doing something wrong.
     
  47. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    1,635
    But what about 300 lines that I was thought before?

    At the same time 1 line is enough for everything anyway.

    herewegoagain.jpg

    You know what's worse than awful tutorials? Awful lessons.
    I've been on a few while I was studying in my university.

    People there have no bloody clue how actual applications work. Reinventing same code all over again. Fun times.
    Teaching obsolete things about obsolete things. Might be my own country's fault though.

    _______________________

    You can't just slap an interface to a component and call it a pattern.
    Why not just call it interface class pattern then?
    Or, interface code pattern?
    Or, maybe interface pattern?

    What you're describing in your article is basically what "component" "pattern" does.
    Thats what components are for.

    If people are miss using them by making class more complex without any inheritance - that may lead to code needed to be refactored in the end. But its not an end of the world.

    If you think you'll reuse pseudo-generic game logic code - you (probably) won't.
    Unless you're making 1 to 1 reskin of a game.

    This doesn't apply to the frameworks that are implemented though.

    Thing is with games - they're different. Each and every one.
    As your knowledge accumulates you will find better and better solutions to the problems that are presented.

    And that code will automatically become obsolete.
     
    Last edited: May 22, 2019
  48. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    Most big names in the software industry do advocate composition over inheritance though. Though if you write clean good code its always easy to refactor.

    As an example we had a handgrenade base class and a fraggrenade subclass. Later we added a Bomb for search and destroy game mode. It explodes just like a fraggrenade but has none of the handgrenade characteristics. It was a pretty small task refactor the explosion into a component and reuse it for both grenades and bombs.
     
    Last edited: May 22, 2019
    xVergilx likes this.
  49. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    1,635
    I've meant interface inheritance. It is almost always a good thing when combined with composition.
    Its always easy to introduce an interface when its needed.

    Class inheritance is a bit evil though. In terms of performance and maintainability. Depends on the implementation.
    If it's too "deep" rather than "wide" its a spaghetti code in the end. Which is just hidden.

    I agree on the rest though.
     
  50. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    4,288
    Ah it's not really inheritance, it's more that you tell that a class must implement all interfaces in the chain. But yeah. Chaining interface like that is not that dangerous as long as they are clear subsets of each other.
     
    RecursiveFrog and xVergilx like this.