Search Unity

Interfaces vs Inheritance Help

Discussion in 'Scripting' started by Nigey, Dec 11, 2015.

  1. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Hi Guys,

    I know it's an over talked subject sometimes, but I'm personally a little confused about my developing style. Basically I find many many instances where I'll be inheriting for abstraction, rather than using interfaces. I know everyone seems to love interfaces over inheritance. So could someone take a look at my theory please?

    Here's my current example that led me to post a thread:

    I have a class, which creates a random weather effect:

    Code (CSharp):
    1. public enum WeatherCondition { Clear, Rain, Fog, Snow, Count }
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using CDSFramework.CDSEnvironment;
    4.  
    5. [RequireComponent(typeof(WindZone))]
    6. public class EnvironmentManager : EnvironmentManagerFramework {
    7.  
    8.     public WeatherCondition weatherCondition;
    9.     [SerializeField] protected bool randomWeather;
    10.  
    11.     // Light Vars
    12.     [SerializeField] private Gradient nightDayClearColour, nightDayRainColour, nightDaySnowColour;
    13.     [SerializeField] private float minLightIntensity;
    14.     [SerializeField] private float maxLightIntensity;
    15.     [SerializeField] private float minAmbient;
    16.     [SerializeField] private float maxAmbient;
    17.  
    18.     // Fog Vars
    19.     [SerializeField] private Gradient nightDayFogColour;
    20.     [SerializeField] private AnimationCurve fogDensityCurve;
    21.     [SerializeField] private float fogScale;
    22.     [SerializeField] private Gradient snowingNightDayFogCurve;
    23.     [SerializeField] private float adjustFogWeather;
    24.  
    25.     // Atmos Vars
    26.     [SerializeField] private float minAtmosDensity;
    27.     [SerializeField] private float maxAtmosDensity;
    28.  
    29.     // Temperature Vars
    30.     [SerializeField] private AnimationCurve nightDayTemperatureCurve;
    31.     [SerializeField] private float tempCurveAmplitude;
    32.     [SerializeField] private float baselineTemperature;
    33.  
    34.     // Time vars
    35.     public float DayRotateSpeed { set { dayRotateSpeed = value; DebugPanel.DebugInstance.txtError.text = value.ToString() + "  " + dayRotateSpeed.ToString(); } }
    36.     public float NightRotateSpeed { set { nightRotateSpeed = value; } }
    37.     [SerializeField] [Range(0.1f, 100)] private float dayRotateSpeed;
    38.     [SerializeField] [Range(0.1f, 100)] private float nightRotateSpeed;
    39.     [SerializeField] private Vector3 skySpeed;
    40.     [SerializeField] private float renderTimeStep;
    41.     [SerializeField] private GameObject rainEffect, fogEffect, snowFlakeEffect;
    42.     [SerializeField] private Material cloudyBox;
    43.  
    44.     // Instance Vars
    45.     private float skyboxRotation = 0;
    46.     private bool rendering = false;
    47.     private Light directionalLight;
    48.     private Skybox sky;
    49.     private Material skyMat;
    50.     private WorldDataManager worldData;
    51.     private Gradient nightDayColour;
    52.  
    53.     protected override bool Initialise()
    54.     {
    55.         if ((directionalLight = GetComponent<Light>()) && (skyMat = RenderSettings.skybox) && (worldData = transform.parent.GetComponentInChildren<WorldDataManager>()) )
    56.         {
    57.             rendering = true;
    58.             RandomConditionRequest(randomWeather);
    59.             StartCoroutine(TickRenderEnvironment());
    60.  
    61.             return base.Initialise();
    62.         }
    63.         return false;
    64.     }
    65.  
    66.     public void RandomConditionRequest(bool _randomise)
    67.     {
    68.         if(bInitialised)
    69.         {
    70.             rainEffect.GetComponent<IWeatherEffect>().DeactivateWeather();
    71.             fogEffect.GetComponent<IWeatherEffect>().DeactivateWeather();
    72.             snowFlakeEffect.GetComponent<IWeatherEffect>().DeactivateWeather();
    73.         }
    74.  
    75.         if (_randomise)
    76.         {
    77.             int rand = Random.Range(0, (int)WeatherCondition.Count);
    78.             weatherCondition = (WeatherCondition)rand;
    79.  
    80.             //float fRand = Random.Range(0, 360);
    81.             //transform.Rotate(fRand * skySpeed);
    82.             //skyboxRotation += fRand;
    83.             //RenderSettings.skybox.SetFloat("_Rotation", skyboxRotation);
    84.         }
    85.  
    86.         SetConstantWeatherCondition();
    87.     }
    88.  
    89.     public void SetConstantWeatherCondition()
    90.     {
    91.         switch (weatherCondition)
    92.         {
    93.             case WeatherCondition.Clear:
    94.                 {
    95.                     RenderSettings.skybox = skyMat;
    96.                     nightDayColour = nightDayClearColour;
    97.                     break;
    98.                 }
    99.             case WeatherCondition.Fog:
    100.                 {
    101.                     nightDayColour = nightDayFogColour;
    102.                     adjustFogWeather = 0.00175f;
    103.                     RenderSettings.skybox = cloudyBox;
    104.                     fogEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    105.                     break;
    106.                 }
    107.             case WeatherCondition.Rain:
    108.                 {
    109.                     nightDayColour = nightDayRainColour;
    110.                     adjustFogWeather = 0.00125f;
    111.                     RenderSettings.skybox = cloudyBox;
    112.                     rainEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    113.                     break;
    114.                 }
    115.             case WeatherCondition.Snow:
    116.                 {
    117.                     nightDayColour = nightDaySnowColour;
    118.                     adjustFogWeather = 0.00150f;
    119.                     nightDayFogColour = snowingNightDayFogCurve;
    120.                     RenderSettings.skybox = cloudyBox;
    121.                     snowFlakeEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    122.                     break;
    123.                 }
    124.             default:
    125.                 break;
    126.         }
    127.     }
    128.  
    129.     // Update World Time, rotating 'Sun & Skies'
    130.     void Update()
    131.     {
    132.         // World Time
    133.         float dot = Mathf.Clamp01(Vector3.Dot(directionalLight.transform.forward, Vector3.down));
    134.  
    135.         Vector3 deltaRot = dot > 0 ? dayRotateSpeed * Time.deltaTime * skySpeed : nightRotateSpeed * Time.deltaTime * skySpeed;
    136.         transform.Rotate(deltaRot);
    137.  
    138.         skyboxRotation += deltaRot.magnitude;
    139.         RenderSettings.skybox.SetFloat("_Rotation", skyboxRotation);
    140.     }
    141.  
    142.  
    143.     // Ticks Rendering
    144.     private IEnumerator TickRenderEnvironment()
    145.     {
    146.         while(rendering)
    147.         {
    148.             float dot = Mathf.Clamp01(Vector3.Dot(directionalLight.transform.forward, Vector3.down));
    149.             float i = ((maxLightIntensity - minLightIntensity) * Mathf.Clamp01(dot)) + minLightIntensity;
    150.  
    151.             // Light Rendering
    152.             directionalLight.color = nightDayColour.Evaluate(dot);
    153.             RenderSettings.ambientLight = directionalLight.color;
    154.             RenderSettings.ambientIntensity = ((maxAmbient - minAmbient) * dot) + minAmbient;
    155.             directionalLight.intensity = i;
    156.  
    157.             // Fog Rendering
    158.             RenderSettings.fogColor = nightDayFogColour.Evaluate(dot);
    159.             RenderSettings.fogDensity = (fogDensityCurve.Evaluate(dot) * fogScale) + adjustFogWeather;
    160.  
    161.             // Temperature Adjusting
    162.             worldData.Temperature = baselineTemperature + (nightDayTemperatureCurve.Evaluate(dot) * tempCurveAmplitude);
    163.  
    164.             // Atmospheric Density Rendering
    165.             i = ((maxAtmosDensity - minAtmosDensity) * dot) + minAtmosDensity;
    166.             RenderSettings.skybox.SetFloat("_Exposure", i);
    167.  
    168.             // Performance Control
    169.             yield return new WaitForSeconds(renderTimeStep);
    170.         }
    171.     }
    172. }
    173.  
    Code (CSharp):
    1. public interface IWeatherEffect
    2. {
    3.     void ActivateWeather();
    4.     void DeactivateWeather();
    5. }
    6.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class RainModule : CDSFramework.CDSEnvironment.BaseEnvironmentFramework, IWeatherEffect
    5. {
    6.     [SerializeField] private ParticleSystem weatherEffect;
    7.  
    8.     public void ActivateWeather()
    9.     {
    10.         weatherEffect.gameObject.SetActive(true);
    11.     }
    12.  
    13.     public void DeactivateWeather()
    14.     {
    15.         // TODO::Add more effects! :D
    16.         weatherEffect.gameObject.SetActive(false);
    17.     }
    18. }
    19.  
    You see I use an interface to control weather effects. However It's a bit messy. My code here handles half of the changes of a weather effect, keeping a list of variables for each effect (like snowingNightDayFogCurve and nightDayRainColour for example), while the interface handles the player's particle effects. It seems to me it'd be much cleaner to put all the variables into a single WeatherCondition type, which the EnvironmentManager just takes as the current, and uses it's variables. So it knows to expect some sort of 'Gradient nightDayColour', but won't know the details until it's passed it from the current WeatherCondition. To me this screams inheritance, where I'd have a parent class called WeatherCondition, which'd hold the basics of activating and deactivating them, and each weatherConditions world variables like max/min temperature, all curves and gradients, and the EnvironmentManager would ask for a typeof WeatherCondition, and pull the child's data (like RainyWeatherCondition, SnowyWeatherCondition).

    However this is again me taking away an interface, and using inheritance instead. In this instance this might be okay, I'm not sure (please give me your input), but I notice I tend to use inheritance a lot more than interfaces, and I'm not sure if this is leaning toward a bad practice I'm not aware of.

    Can someone give me their perspective on it please?

    Thanks!
     
  2. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    interfaces trumps inheritance when you need to "mix" things.

    The classic example is "Items, Items>Armour, Items>Weapons".. so where does "shield bash" go? it's armour, but you can attack with it which is weapons, but it's an armour... weapon... erm... :confused:

    If you never need that "mixing" flexibility there shouldn't be a problem using just inheritance.
     
    oangel2133, Kiwasi and Nigey like this.
  3. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Lol I see. I get put off interfaces normally because you can only have empty methods.. So no variables, and each time you write a method for an implemented interface, you have to write it from scratch. So if you have two 'weapons', you'll have to literally copy/paste code, and won't be able to have a universally recognized damage float. It's the near copy/paste of it that gets me nervous. It's almost a trigger for me as a programmer that I'm doing something wrong lol. Is this just something you ignore when you implement interfaces?
     
  4. sfjohansson

    sfjohansson

    Joined:
    Mar 12, 2013
    Posts:
    369
    I thought of this a few times as I don't use interfaces a lot...

    One thought I had is to combine them...for instance... adding an interface for a weapon....that wraps the functionality of a weapon class...

    As that would give you the ability to inherit multiple interfaces instead of just a vertical class hierarchy... and apart from the wrapper code you can keep everything in one place.

    .....or am I crazy?
     
  5. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,741
    Also you can get around not having fields by using properties in a interface.
     
    villalbaweb likes this.
  6. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    That's a good idea. I might use that if I need it sometime. Taking the weapon example, I still see the gap of copy and pasting a lot of functionality. So having properties of damage, rate-of-fire, ect. You'd still need to re-write their relationship internally within the weapon's system. So the weapon would still need a firing system for each time it's fired, which would need to be re-written from scratch each time you make a new weapon. Unless of course you use @sfjohansson 's idea of creating interfaces for inherited members. So even though the Weapon inherits from item, and can be abstracted that way, the fire method is actually called via an interface. Weapon implements and satisfies said interface, as well as any other item, which may not inherit weapon, but has a unique 'fire type'. Does that make sense?
     
  7. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    In that case, you should consider putting that code into a separate behaviour that you attach to your game objects, rather than interface.

    I had a similar case where I used interfaces. All my relevant objects conformed to an, lets say, IEventActor interface to respond to events, etc. Let's say it looks like this:
    Code (CSharp):
    1. public interface IEventActor
    2. {
    3.      Vector2 GetTilePosition();
    4. }
    5.  
    6. //lets say units on map
    7. public BoardObjectBehaviour : MonoBehaviour, IEventActor
    8. {
    9.      public Vector2 tilePosition;
    10.  
    11.      public Vector2 IEventActor.GetTilePosition()
    12.      {
    13.           return tilePosition;
    14.      }
    15. }
    16.  
    17. //rect field on map
    18. public TileBehaviour : MonoBehaviour, IEventActor
    19. {
    20.      public Vector2 index;
    21.  
    22.      public Vector2 IEventActor.GetTilePosition()
    23.      {
    24.           return index;
    25.      }
    26. }
    27.  
    28. //player controlling RTS game
    29. public PlayerController : MonoBehaviour, IEventActor
    30. {
    31.      public Vector2 IEventActor.GetTilePosition()
    32.      {
    33.           return -Vector.one; //just empty, since we have to conform to all methods
    34.      }
    35. }
    They had a tile position Vector2 to represent themselves on a field. Notice how player, although relevant event receiver, does not have tilePosition variable since he is not present directly on the field.

    At this point I noticed, it would be better if I just created:
    Code (CSharp):
    1. public class FieldPositionBehaviour : MonoBehaviour
    2. {
    3.      public Vector2 position;
    4. }
    and just grabbed the component. It would prevent empty interface methods and code will be reused as much as possible. If the gameObject can be represented on field, it will have that component. But, to utilise that properly, you have to structure your code smart in order to prevent copy pasta. I learned in my experience that such knowledge comes along with making your game :)

    Also, whenever you think you should utilise inheritance, ask yourself if you can do it the Unity way. Inheritance can be good sometimes; if you have different gameModes, where each script should initialise your game differently. But to represent your gameObjects, always stick to behaviours/component based approach. It will save you many headaches in the long run.

    Best of luck! Hope this helps!
     
    MassimoFrancesco, Kiwasi and Nigey like this.
  8. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    You have put the words interface and inheritance in competition with each other, but that's not quite right. Inheritance comes in two flavours: class inheritance and interface inheritance. So what you're really struggling with is the difference between class and interface.

    I think you've envisaged the difference when you say:
    Class inheritance uses another object's implementation to describe a new object's implementation. This is a great tool for code sharing.
    Interface inheritance just describes when an object can be swapped out for another.

    Some Definitions...
    First, let's review what a class is: (from Wikpiedia) a concrete data structure and collection of subroutines.

    You might also remember that one of a classes primary tenants is to provide encapsulation: (also from Wikipedia) a language construct that facilitates the bundling of data with the methods(or other functions) operating on that data.

    Notice the words bundling of data with the methods. I think this is often missed. A class is supposed to group data with methods that operate on that data. First of all, that means classes that have nothing but data in them aren't really classes at all. This is a code smell. Second, and more relevant to this discussion, the data is supposed to be protected from external access so that the methods can make assumptions about what is in it.

    No Contest
    Technically, when you use pure class inheritance, there is no guarantee that the subclass will be capable of replacing an object from which it inherited, it simply wanted to "reuse" some of the definition. This is difficult for us to understand today because we are unfamiliar with procedural programming (though it often sneaks into our programs unintentionally) in which calling subroutines was the primary method for code reuse. One of the dangers of pure class inheritance is that it suggests we could start mixing components of different classes together. While, technically, this would be a powerful code reuse tool it can also lead to a pernicious side effect. When you gather code from different places, and put it together in a class, it can easily lead to code which shares data but makes different assumptions about that data. A very simple example is listed on Wikipedia as the "diamond problem". This is basically why inheriting from more than one class is not allowed in many popular languages.

    Pure interface inheritance is much easier to understand because it is the default action when implementing an interface in modern languages. Another term often thrown around is type. Actually, type is synonymous with interface, it defines the kinds of messages an object is able to receive and understand. The main purpose of interface inheritance, then, is to express the different types an instance of this class can "polymorph" into.

    So, theoretically, class inheritance and interface inheritance are not duals, they are orthogonal; you can have one, both, or neither.

    However, most modern languages now implicitly and automatically force interface inheritance when you try to take advantage of class inheritance. The reason, I think, is because, while it used to make sense to grab pieces of procedural code and gather them together, it no longer makes sense in terms of object oriented programming, in which all code should already be grouped together. When you "inherit" from another class, you are implicitly inheriting its interface.

    Still, this restriction doesn't change the orthogonal nature of the two types of inheritance.

    So it comes down to this:
    Class inheritance is a tool for sharing data and code.
    Interface inheritance is a tool for expressing where an object can be swapped in. That is, how it can polymorph.

    Rant
    That's all I have on inheritance but I thought it would be helpful to make a few more points..

    Class inheritance is not the only tool we have for sharing data and code. Something that often gets referred to as "the Unity way", though it's a technique that far predates Unity, is composition. Just like with class inheritance, you can reuse bits of code. However, unlike inheritance you can pick and choose different pieces of code from different classes and weave them together into a new class. The reason this isn't as dangerous as inheritance is because it doesn't break the encapsulation a class is supposed to provide: the code from different places do not touch each other's data. They have their own data protected by the shell that their encapsulating class provides.

    Anyways, composition comes in lots of different flavours. Most of the structural and behavioural design patterns use composition in one for or another.

    One of the, in my mind negative, side effects of the enforcement of interface inheritance with class inheritance is the notion of C#'s "sealed" classes. Because it's a class, it provides a default implementation; because C# enforces interface inheritance automatically, the type can be used as an abstraction; however, because it is sealed, third parties aren't allowed to implement it. This creates a very strange concept of an interface we know about but can't implement. This completely destroys our ability to reuse code and blatantly contradicts the Open Closed principle of SOLID OO design.

    Code Review
    Now that my rant is over.. Let's talk about your code.
    I think your interface is doing its job nicely: It's describing a type without enforcing how that type will behave. It allows your weather code to determine when an action will occur without dictating how it will occur. However, I have to question why you have designed an interface to control particle effects but no interface to control your nightDayColour, FogWeather, nightDayFogColour, nor skybox.

    Just look at this code:
    Code (csharp):
    1.         switch (weatherCondition)
    2.         {
    3.             case WeatherCondition.Clear:
    4.                 {
    5.                     RenderSettings.skybox = skyMat;
    6.                     nightDayColour = nightDayClearColour;
    7.                     break;
    8.                 }
    9.             case WeatherCondition.Fog:
    10.                 {
    11.                     nightDayColour = nightDayFogColour;
    12.                     adjustFogWeather = 0.00175f;
    13.                     RenderSettings.skybox = cloudyBox;
    14.                     fogEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    15.                     break;
    16.                 }
    17.             case WeatherCondition.Rain:
    18.                 {
    19.                     nightDayColour = nightDayRainColour;
    20.                     adjustFogWeather = 0.00125f;
    21.                     RenderSettings.skybox = cloudyBox;
    22.                     rainEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    23.                     break;
    24.                 }
    25.             case WeatherCondition.Snow:
    26.                 {
    27.                     nightDayColour = nightDaySnowColour;
    28.                     adjustFogWeather = 0.00150f;
    29.                     nightDayFogColour = snowingNightDayFogCurve;
    30.                     RenderSettings.skybox = cloudyBox;
    31.                     snowFlakeEffect.GetComponent<IWeatherEffect>().ActivateWeather();
    32.                     break;
    33.                 }
    34.             default:
    35.                 break;
    36.         }
    37.     }
    First of all, a switch statement is a code smell suggesting you could refactor with polymorphism. Secondly, see how similar each of the cases is? This is a smell that suggests you could refactor with a template method or substitute class. Here's one idea which might get you started.

    Code (csharp):
    1. interface IWeatherMode
    2. {
    3.   Gradient GetNightDayColour();
    4.   float GetFogWeather();
    5.   Gradient GetNightDayFogColour();
    6.   void RenderSkybox();
    7.   void ActivateParticleEffect();
    8. }
    9.  
    10. public class ClearWeather : IWeatherMode
    11. {
    12.   // No idea what values are appropriate, your code declares these values but doesn't seem to initialize them
    13.   private Gradient nightDayColour, fogColour;
    14.   private Material skyMat;
    15.   public Gradient GetNightDayColour()
    16.   {
    17.     return nightDayColour;
    18.   }
    19.   public float GetFogWeather()
    20.   {
    21.     return 0f;
    22.   }
    23.   public Gradient GetNightDayFogColour()
    24.   {
    25.     return fogColour;
    26.   }
    27.   public void RenderSkybox()
    28.   {
    29.     RenderSettings.skybox = skyMat;
    30.   }
    31.   public void ActivateParticleEffect()
    32.   {
    33.     // Either remove other weather related GameObjects, or move the logic into a particle effect manager and ask it to stop rendering from here
    34.   }
    35. }
    Now your switch statement can be reduced:
    Code (csharp):
    1. IWeatherMode weather = GetCurrentWeather(); // this could be a call to GetComponent, or just a factory method that creates a new IWeatherMode based on some criteria
    2. nightDayColour = weather.GetNightDayColour();
    3. adjustFogWeather = weather.GetFogWeather();
    4. nightDayFogColour = weather.GetNightDayFogColour();
    5. weather.RenderSkybox();
    6. weather.ActivateParticleEffect();
    As passerbycmc noted, you could just as easily change the Get methods to properties with a get keyword. However, I actually prefer creating types that do something rather than just return a float. Unfortunately, this code would require quite a bit of work in order for the IWeatherMode to replace the actual implementation of creating weather effects, so the Get methods are a pretty good first step.
     
    Last edited: Dec 15, 2015
    hippocoder, GenaSG, Fajlworks and 2 others like this.
  9. CloudKid

    CloudKid

    Joined:
    Dec 13, 2015
    Posts:
    207
    tl;dr You use interfaces when you need to call a function on a group of objects of which type you don't know or you don't want to force some incompatible inheriting.
    Just think of how Serialization works in c#. It's very easy, every class has to have 1 or 2 function (lets say .Serialize()), so the serialization is possible, but it will be madness if c# will request you that every class that is serializable inherits a Serialize class. So they have an interfaces ISerializable which makes sure that your object has .Serialize(), so the guys who did the serialization class can call .Serialize() on your object.

    I see it as a contract that you do with someone else. You say yeah, I am of type ISerializable so it's clearly I have your requested functions, but what my object is actually or what it dose not concerns you. So the other guy can call your functions without caring what is your type or what it does.

    If you understand inheritance and interfaces you will see that they have nothing in common.

    Also if you needs fields in an Interfaces you are doing something wrong... Just use getters and setters

    You usually don't need interfaces when you are coding by yourself, unless you are in a situation where you are checking an object for type to call a function that does the same thing regardless of type
     
    MassimoFrancesco and Nigey like this.
  10. CloudKid

    CloudKid

    Joined:
    Dec 13, 2015
    Posts:
    207
    Let's take an Unity examples.
    So we have a cool button, and when we press it, we want some of the animations to stop (not all of them so we cannot use GetComponent and stop straight to Animation class). Now, we could create a class named AnimationStop that has a function .Stop(), but that means that every object that we want to stop will be of type AnimationStop. Let's think a bit about that: why should MainPlayer be of type AnimationStop? He is a player not an animation. And why CoolButton22 is the same type as EndCredits? This makes no sense. But what makes sense is that all this objects use the IAnimationStop interface, which requires MainPlayer and Button33 and EndDragon to have a function .Stop(). So we can easily get all objects that have the IAnimationStop interface, save them in a list<IAnimationStop> and call .Stop(), while in the same time MainPlayer is till a player and Button99 is still a button. Think only how would you make MainPlayer which is of type Player and Button33 which is the type Button implement the .Stop() function. You will have to write a new "Object" class that sits at the base of all your objects (just like Monobehaviour) just for a frickin .stop())
     
    MassimoFrancesco and Nigey like this.
  11. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    @eisenpony thanks for that massive explanation. I think for me it's a matter of 'knowing what it is', but something just isn't quite clicking into my own internal workflow. Like I've mentioned before on this thread. My main concern is when I use an Interface, unless I also inherit from something, I need to re-write methods completely. Meaning a fair amount of copying and pasting. I mean there is the idea that I can inherit from a parent class if need be, that adds the functionality, but the actual calling of the method is done through an interface, which the parent class fulfills, and any other class that wants to be called can fill, but does not need to necessarily be inherited. What's your thoughts on both my 'problem', and my answer there?

    Also I've created an inheritance version of the fixes you suggested. I'm also going to take a hand at making an interface version. Can you give me your thoughts on it? Only if you have the time :) I'm making it anyways and I thought it might be a handy read for anyone else who comes across this with the same line of questioning.
    ---------------------------------------------------------------------------------------------------------------------------------------------------------
    So this is the inheritance model. It's gonna be a little cumbersome as I'm just steamrolling it out to give you an example of how I'd probably go about building it (with one or two tweaks):

    // Inheritance EnviroManager just pulls the sets the current weather effect and calls it's tick. Taking the temp as it does it.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using CDSFramework.CDSEnvironment;
    4.  
    5. [RequireComponent(typeof(WindZone))]
    6. public class InheritanceEnviroManager : EnvironmentManagerFramework
    7. {
    8.     public WeatherCondition weatherCondition;
    9.     [SerializeField] protected bool randomWeather;
    10.  
    11.     // Time vars
    12.     public float DayRotateSpeed { set { dayRotateSpeed = value; DebugPanel.DebugInstance.txtError.text = value.ToString() + "  " + dayRotateSpeed.ToString(); } }
    13.     public float NightRotateSpeed { set { nightRotateSpeed = value; } }
    14.     [SerializeField] [Range(0.1f, 100)] private float dayRotateSpeed;
    15.     [SerializeField] [Range(0.1f, 100)] private float nightRotateSpeed;
    16.     [SerializeField] private Vector3 skySpeed;
    17.     [SerializeField] private float renderTimeStep;
    18.     [SerializeField] private WeatherEffect[] weatherConditions;
    19.  
    20.     // Instance Vars
    21.     private float skyboxRotation = 0;
    22.     private bool rendering = false;
    23.     private Light directionalLight;
    24.     private Skybox sky;
    25.     private Material skyMat;
    26.     private WorldDataManager worldData;
    27.     private Gradient nightDayColour;
    28.     private WeatherEffect currentWeatherCondition;
    29.  
    30.     protected override bool Initialise()
    31.     {
    32.         if ((directionalLight = GetComponent<Light>()) && (skyMat = RenderSettings.skybox) && (worldData = transform.parent.GetComponentInChildren<WorldDataManager>()))
    33.         {
    34.             rendering = true;
    35.             RandomConditionRequest(randomWeather);
    36.             StartCoroutine(TickRenderEnvironment());
    37.  
    38.             return base.Initialise();
    39.         }
    40.         return false;
    41.     }
    42.  
    43.     public void RandomConditionRequest(bool _randomise)
    44.     {
    45.         if (_randomise)
    46.         {
    47.             int rand = Random.Range(0, (int)WeatherCondition.Count);
    48.             weatherCondition = (WeatherCondition)rand;
    49.         }
    50.  
    51.         SetConstantWeatherCondition();
    52.     }
    53.  
    54.     public void SetConstantWeatherCondition()
    55.     {
    56.         foreach(WeatherEffect weather in weatherConditions)
    57.         {
    58.             if (weather.weatherCondition == weatherCondition)
    59.             {
    60.                 weather.gameObject.SetActive(true);
    61.                 currentWeatherCondition = weather;
    62.             }
    63.             else
    64.                 weather.gameObject.SetActive(false);
    65.         }
    66.     }
    67.  
    68.     // Update World Time, rotating 'Sun & Skies'
    69.     void Update()
    70.     {
    71.         // World Time
    72.         float dot = Mathf.Clamp01(Vector3.Dot(directionalLight.transform.forward, Vector3.down));
    73.  
    74.         Vector3 deltaRot = dot > 0 ? dayRotateSpeed * Time.deltaTime * skySpeed : nightRotateSpeed * Time.deltaTime * skySpeed;
    75.         transform.Rotate(deltaRot);
    76.  
    77.         skyboxRotation += deltaRot.magnitude;
    78.         RenderSettings.skybox.SetFloat("_Rotation", skyboxRotation);
    79.     }
    80.  
    81.     // Ticks Rendering
    82.     private IEnumerator TickRenderEnvironment()
    83.     {
    84.         while (rendering)
    85.         {
    86.             if (currentWeatherCondition)
    87.             {
    88.                 float temp = 0; // As cannot use property in out parameter
    89.                 currentWeatherCondition.TickWeatherEffect(directionalLight, out temp);
    90.                 worldData.Temperature = temp;
    91.             }
    92.  
    93.             // Performance Control
    94.             yield return new WaitForSeconds(renderTimeStep);
    95.         }
    96.     }
    97. }

    This takes the actual WeatherEffect and holds the tick data on it, as well as all the weather effect's variables. The child of this class can choose how the effects are handled in the abstract methods at the bottom.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. namespace CDSFramework
    5. {
    6.     namespace CDSEnvironment
    7.     {
    8.         public abstract class WeatherEffect : BaseEnvironmentFramework
    9.         {
    10.             [SerializeField] protected WeatherCondition _weatherCondition;
    11.             public WeatherCondition weatherCondition { get { return _weatherCondition; } }
    12.  
    13.             [SerializeField] protected Gradient nightDayColor;
    14.             [SerializeField] protected Gradient nightDayFogColour;
    15.             [SerializeField] protected AnimationCurve fogDensityCurve;
    16.             [SerializeField] protected float fogScale;
    17.  
    18.             [SerializeField] protected float minLightIntensity;
    19.             [SerializeField] protected float maxLightIntensity;
    20.             [SerializeField] protected float minAmbient;
    21.             [SerializeField] protected float maxAmbient;
    22.  
    23.             [SerializeField] protected float minAtmosDensity;
    24.             [SerializeField] protected float maxAtmosDensity;
    25.  
    26.             [SerializeField] protected AnimationCurve nightDayTemperatureCurve;
    27.             [SerializeField] protected float tempCurveAmplitude;
    28.             [SerializeField] protected float baselineTemperature;
    29.  
    30.             protected override void OnEnable()
    31.             {
    32.                 base.OnEnable();
    33.                 ActivateWeatherEffect();
    34.             }
    35.  
    36.             protected override void OnDisable()
    37.             {
    38.                 DeactivateWeatherEffect();
    39.             }
    40.  
    41.             public virtual void TickWeatherEffect(Light light, out float outTemp)
    42.             {
    43.                 float dot = Mathf.Clamp01(Vector3.Dot(light.transform.forward, Vector3.down));
    44.                 float i = ((maxLightIntensity - minLightIntensity) * Mathf.Clamp01(dot)) + minLightIntensity;
    45.  
    46.                 // Light Rendering
    47.                 light.color = nightDayColor.Evaluate(dot);
    48.                 RenderSettings.ambientLight = light.color;
    49.                 RenderSettings.ambientIntensity = ((maxAmbient - minAmbient) * dot) + minAmbient;
    50.                 light.intensity = i;
    51.  
    52.                 // Fog Rendering
    53.                 RenderSettings.fogColor = nightDayFogColour.Evaluate(dot);
    54.                 RenderSettings.fogDensity = (fogDensityCurve.Evaluate(dot) * fogScale);
    55.  
    56.                 // Temperature Adjusting
    57.                 outTemp = baselineTemperature + (nightDayTemperatureCurve.Evaluate(dot) * tempCurveAmplitude);
    58.  
    59.                 // Atmospheric Density Rendering
    60.                 i = ((maxAtmosDensity - minAtmosDensity) * dot) + minAtmosDensity;
    61.                 RenderSettings.skybox.SetFloat("_Exposure", i);
    62.             }
    63.  
    64.             protected abstract void ActivateWeatherEffect();
    65.             protected abstract void DeactivateWeatherEffect();
    66.         }
    67.     }
    68. }
    69.  
    70.  
    Lastly rain just inherits WeatherEffect and overrides the effects. It also chooses the unique effects needed for rain.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Rain : CDSFramework.CDSEnvironment.WeatherEffect {
    5.  
    6.     [SerializeField] private ParticleSystem weatherEffect;
    7.  
    8.     protected override void ActivateWeatherEffect()
    9.     {
    10.         weatherEffect.gameObject.SetActive(true);
    11.     }
    12.  
    13.     protected override void DeactivateWeatherEffect()
    14.     {
    15.         weatherEffect.gameObject.SetActive(false);
    16.     }
    17. }
    18.  
    The alternatives for creating this through an interface could be done in two ways, like you said. One would be to just make property get / setters in the IWeatherCondition, and call those on the manager. The alternative to that would be to make a Tick method in the Interface itself, and call that on whichever is the current WeatherCondition. The only issue with that I see, is that you'd need to re-write the methods completely for each one. Is this something you'd suggest me doing? I think you mentioned 'the Unity way', being that you don't even need to necessarily worry about copy and pasting, just so long as encapsulation is upheld.
     
    Last edited: Dec 16, 2015
  12. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Maybe instead subclassing your weather class, you can use it to describe your weather as prefab.

    If all you are changing are variables and not really specific implementation, you could create different GameObjects, slap the Weather component, adjust the variables in the inspector panel, save as prefab and just place into scene the weather you need. As for custom implementation (lets say, thunderstorm weather with hurricane moving all game objects) you can create new components with those effects and add them to your prefabs.
     
    Nigey likes this.
  13. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Got KISS'd there I think. Yeah I think you're right, thanks.
     
  14. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    The sayings go -" code towards an interface and not an implementation" and "favour composition over inheritance"

    but this on its own is a best practice ideology but it does not declare inheritance as pure evil never to be used. If you have shared implementation then an abstract class is perfect for this. This abstraction could also adhere to an interface or not , depends on what you are interfacing.
     
  15. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    You're still missing something. Class inheritance and interface inheritance are not at odds with each other. I think what you mean is: "here is an inheritance version, how can I use composition instead?"

    Clarity (I hope)
    Okay, I think I might have over done my response. The good stuff is getting obscured..

    Essentially what I was getting at is this.

    Class inheritance provides these things:
    1. An abstraction (through the enforced interface inheritance)
    2. Shared implementation
    3. Shared data

    Unfortunately, class inheritance comes with some side effects. I tried to explain how mixing code and data from multiple places leads to obscure bugs which is why most popular languages don't even allow this. But I think LeftyRighty puts it much more clearly when he says
    Interface inheritance provides these things:
    1. An abstraction

    Composition provides these things:
    1. Shared implementation
    2. Shared data

    So you see, in combination with composition, interface inheritance can provide the same features of class inheritance but without the troublesome side effects.

    Inheritance vs Composition (Is a vs Has a)
    So why would we ever choose class inheritance? Well, in cases that truly do not have any chance of "mixing", class inheritance can reduce the amount of boiler plate code we need to type to share our implementation. Also, when you use class inheritance you get direct access to the share data which can simplify some of your logic.

    This isn't true. You are missing an opportunity to reuse code through composition. Consider something really simple: Unity's Vector3 struct. Do you inherit from this struct every time you need to use it's "features"? Of course not. You simply create a Vector3, use it and either store it or throw it away. This is composition in a nutshell.

    What a lot of people miss is that Vector3 actually provides an interface. It doesn't explicitly call it an interface in the language; there is no IVector3. But the collection of methods and properties on the struct is still an interface. We might call it a type to distinguish but there really is no difference.

    I definitely did not say that. Your instinct is totally correct on this one. In fact, most of our coding tools and patterns are designed specifically to promote code reuse rather than copy pasting. Copy paste is a code smell just as much, if not more so, as your switch statement but trading one smell for another isn't a good option.

    Fajlworks has suggested some great alternatives. In this case, I think he is proposing to add the functionality as another component into a game object so that you can access these components using GetComponent.
    And here, I think he is proposing creating your entire weather effect using a prefab and simply instantiating the kind of weather prefab you would like.
    Both of these ideas achieve code reuse without inheritance because they make use of composition. The truth is there are probably at least a dozen other ways you could implement the system using a variation on composition.

    Best Practice (if there is such a thing)
    To get back to your original post,
    In my experience attending conferences, watching videos, reading blogs and books it seems people with a lot of experience building systems agree that composition is more flexible and, in the end, a better solution than inheritance. I suspect the reason is tied to the explanation I gave at the top of this post but nobody has ever spelled it out perfectly to me. I would say that you are glossing over opportunities to reuse code via composition, so while I can't say inheritance is a "bad practice" I will suggest you are giving up opportunities to practice composition, which will probably serve you better in the long run.

    The truth is, abstracting your code and making it more reusable is hard. Typically, I start at a very low level and refactor it as I detect opportunities to reuse bits of code. Fajlworks hit the nail on the head in his first post
    Code Time
    Anyways, I think your last post of code is much better than your first; you have used composition:
    Code (CSharp):
    1. using UnityEngine;
    2. // ...
    3. public class InheritanceEnviroManager : EnvironmentManagerFramework
    4. {
    5. // ...
    6.     [SerializeField] private WeatherEffect[] weatherConditions; // <== Right here
    7. // ..
    8. }
    Still, I think you can clean things up even more. I don't think you need a list of possible weather conditions. This can be abstracted into a factory method or abstract factory. Just change your randomization and set constant methods:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using CDSFramework.CDSEnvironment;
    4.  
    5. [RequireComponent(typeof(WindZone))]
    6. public class InheritanceEnviroManager : EnvironmentManagerFramework
    7. {
    8. //...
    9.     // I'm removing the Boolean flag here because I prefer explicit methods over control flags
    10.     // if the _randomise value is false, simply don't call this method.
    11.     public void RandomConditionRequest()
    12.     {
    13.       var r = Random.value;
    14.       if (r < 0.2)
    15.         currentWeatherCondition = new Rain();
    16.       else if (r < 0.5)
    17.         currentWeatherCondition = new Snow();
    18.       else
    19.         currentWeatherCondition = new Clear();
    20.     }
    21. //...
    22. }
    Now you probably look at this and think "how is it better than a switch statement?" and you're totally right, it's nearly the same -- but you actually can't get away from code that looks like this living somewhere. The nice thing is that it's centralized in one spot and everywhere else I can just work with currentWeatherCondition. The instantiated object automatically causes different actions to be performed based on its actual class. This is also a super naïve solution for my factory method; I can do this because creating subclasses of WeatherEffect is quite simple.

    Composition
    If you took the composition approach one step further, for WeatherEffect, you might break each effect down into smaller components that can be injected such as the fog, light, atmosphere density, and temperature behaviours. I notice you already did that in the organization of your instance variables but you didn't move them into components on their own.

    For instance, rather than using inheritance let's say the constructor of a WeatherEffect accepts some parameters which control how it behaves:

    Code (csharp):
    1. public class WeatherEffect
    2. {
    3.   private readonly FogEffect fog;
    4.   private readonly LightEffect light;
    5.   private readonly AtmosphereEffect atmos;
    6.  
    7.   public WeatherEffect(FogEffect fog, LightEffect light, AtmosphereEffect atmos) // etc...
    8.   {
    9.     // initialize local variables here...
    10.   }
    11.   public virtual void TickWeatherEffect(Light light, out float outTemp) // not sure about these parameters but ignore them for now
    12.   {
    13.     fog.Tick();
    14.     light.Tick();
    15.     atmos.Tick();
    16.     // ...
    17.   }
    18. }
    If you did this, you may not even need the Rain subclass at all since Rain is just defined by a certain fog, light and atmosphere components configured with particular values.

    To prevent my randomize method from getting too complicated, I would create an abstract factory:

    Code (csharp):
    1. public interface IWeatherEffectFactory
    2. {
    3.   WeatherEffect CreateWeatherEffect();
    4. }
    5.  
    6. public class RainFactory : IWeatherEffectFactory
    7. {
    8.   public WeatherEffect CreateWeatherEffect(){
    9.     var fog = new FogEffect(1,2,3,4);
    10.     var light = new LightEffect(5,6,7,8);
    11.     var atmos = new AtmosphereEffect(9,0);
    12.     return new WeatherEffect(fog, light, atmos);
    13.   }
    14. }
    Then the Randomize function could stay simple
    Code (CSharp):
    1. public class InheritanceEnviroManager : EnvironmentManagerFramework
    2. {
    3. //...
    4.     public void RandomConditionRequest()
    5.     {
    6.       var r = Random.value;
    7.       IWeatherEffectFactory factory = null;
    8.       if (r < 0.2)
    9.         factory = new RainFactory();
    10.       else if (r < 0.5)
    11.         factory = new SnowFactory();
    12.       else
    13.         factory = new ClearFactory();
    14.  
    15.       currentWeatherCondition = factory.CreateWeatherEffect();
    16.     }
    17. //...
    18. }
    If the WeatherEffects are even more complicated, I will resort to more sophisticated ways of building them up, such as the builder pattern or prototype pattern.

    Speaking of the prototype pattern, this is essentially how Unity works with prefabs. I'm out of time for today so maybe we can save that for later..
     
    Last edited: Dec 16, 2015
    jister, Nigey and Fajlworks like this.
  16. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    @eisenpony you know, I genuinely didn't understand composition until reading this post thoroughly, and doing some further research. Things are finally clicking lol!

    I see what you mean about them not being at odds. From what it seems, you're saying they learn towards different methods, but they're not like yin and yang, or black and white. Just saying back to me what I heard from you. Basically an interface is a way to make components be represented as a type, which can be called in an abstract way. Like a 'has a'.. a wing, a gun, some legs, a Chihuahua. So there doesn't need to be direct coupling, or knowledge between the caller and the callee. Inheritance means it's all encompassing; you must be all of what the parent is, at minimum. Interfaces give you the freedom that it can be just referenced as an object that 'has it', in whatever way that's suitable, but that's doesn't mean that is everything it is. So one object can have multiple components / multiple types. Is that correct?

    This has actually changed my entire outlook on how I code, and how I approach Unity (thank Christ). So technically instead of a weather condition necessarily being a single type. It could be a collection of components I could add and remove? Like for example, rain, fog, atmosphere, lighting, snow.. Then I could simply 'TickUpdate' all that have interface of type IWeatherEffect, or something. I mean going super simple, and possibly the best I could use @Fajlworks idea where it's just a single script, that I attach to different object and assign it's unique variables. But just to help me understand Interfaces, I could quite happily do what I just suggested. Wow I see now! So actually interfaces are just a way to help abstract component based architecture!

    I might not be wording everything correctly, because the switch has just turned on in my head and I'm still processing. That all makes sense and has made such a difference though. Thank you! The lengthy process of teaching me has paid off lol (I believe).

    Edit:
    What I might do down the line. When I start building something next. Is to develop keeping composition over inheritance in mind, and see if I can make a more component based architecture using interfaces. See if I can get some C&C in these forums about it. Not enough C&C in here I think lol.
     
    Last edited: Dec 17, 2015
    eisenpony likes this.
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I'm really happy it was helpful for you. Actually just writing these things down helps to solidify my own understanding so I don't mind at all.

    Yes I think you are getting it. I do think my second attempt was a bit clearer though:
    stealing from shaderbytes,
    This means we should always try to have an abstraction; if we are not choosing class inheritance, we should choose interface inheritance. So since an abstraction is a "best practice" the only real choice is between class inheritance and composition.

    This is the Is a vs Has a relationship that I'm certain you can find loads of opinions for on the internet.


    The last bit of this sentence makes me a little bit nervous. I would say:
    The difference is subtle so let's take a concrete example. I'm building a car class. If I'm using a component design, I would probably include some things like: wheels, chassey, frame, windows. Now, I could have my car class implement IWheels and allow it to be swapped in anywhere something needs wheels, but that doesn't really make sense.. I would probably code to an interface called IVehicle which might provide a transporting feature. Later, I could build a rocket ship and have it implement IVehicle but it probably wouldn't have any wheels.

    Contrast that to the example you probably had in your mind, the IWeatherEffect. A weather effect could be any number of things but from a client's perspective I either want it on or off. So an interface which allows me to turn it on or off is sufficient. Under the covers, I might make a weather effect like Rain be a component of Fog, Light, Particles, etc... but how to implement that? I could do a number of things: keep them very separate like in my car example or make them very similar, since fog light and particles are basically weather effects on their own.
    Let's take a look at keeping the separate:
    Code (csharp):
    1. class FogMachine
    2. {
    3.   public FogMachine(float density, float hue, float brightness)
    4.   {
    5.     // initialize locals
    6.   }
    7.   public void MakeFog()
    8.   {
    9.     RenderSettings.fogColour = // ...
    10.     RenderSettings.fogDensity = // ...
    11.   }
    12.   public void StopMakingFog()
    13.   {
    14.     RenderSettings.fogColour = // Default or old value
    15.     RenderSettings.fogDensity = // Default or old value
    16.   }
    17. }
    18.  
    19. class LightController
    20. {
    21.   public LightController(float alpha, float hue, float intensity)
    22.   {
    23.     // initialize locals
    24.   }
    25.   public void SetLight()
    26.   {
    27.     RenderSettings.ambientLight = // ...
    28.     RenderSettings.ambientIntensity = // ...
    29.   }
    30.   public void RevertLight()
    31.   {
    32.     RenderSettings.ambientLight = // Default or old value
    33.     RenderSettings.ambientIntensity = // Default or old value
    34.   }
    35. }
    Then you will need a Rain class to implement our interface.
    Code (csharp):
    1. interface IWeatherEffect
    2. {
    3.   void Activate();
    4.   void Deactivate();
    5. }
    6.  
    7. class RainEffect : IWeatherEffect
    8. {
    9.   FogMachine fogger;
    10.   LightController lamp;
    11.  
    12.   public RainEffect()
    13.   {
    14.     fogger = new FogMachine(1,2,3);
    15.     lamp = new LightController(4,5,6);
    16.   }
    17.  
    18.   public void Activate()
    19.   {
    20.     fogger.MakeFog();
    21.     lamp.SetLight();
    22.   }
    23.  
    24.   public void Decativate()
    25.   {
    26.     fogger.StopMakingFog();
    27.     lamp.RevertLight();
    28.   }
    29. }
    That is super simple composition. Notice that my RainEffect class does not implement the same interfaces as FogMachine nor LightController -- I wouldn't want it to. My RainEffect class is not intended to swap in for one of these classes, it simply makes use of them.

    Getting back to my original point..
    I wouldn't make my classes inherit from interfaces of the types that they contain. I would only inherit from interfaces that express the things they can do. The fact that they contain other types is mostly so I can reuse their implementations.

    Now let's explore some of the other ways you might use composition. What if we make each component (effect) the same type? So if each of my effects is just an IWeatherEffect, I might do something like this:
    Code (csharp):
    1. class FogEffect : IWeatherEffect
    2. {
    3.   public FogEffect(float density, float hue, float brightness)
    4.   {
    5.     // initialize locals
    6.   }
    7.   public void Activate()
    8.   {
    9.     RenderSettings.fogColour = // ...
    10.     RenderSettings.fogDensity = // ...
    11.   }
    12.   public void Deactivate()
    13.   {
    14.     RenderSettings.fogColour = // Default or old value
    15.     RenderSettings.fogDensity = // Default or old value
    16.   }
    17. }
    18.  
    19. class LightEffect : IWeatherEffect
    20. {
    21.   public LightEffect(float alpha, float hue, float intensity)
    22.   {
    23.     // initialize locals
    24.   }
    25.   public void Activate()
    26.   {
    27.     RenderSettings.ambientLight = // ...
    28.     RenderSettings.ambientIntensity = // ...
    29.   }
    30.   public void Deactivate()
    31.   {
    32.     // you get the idea..
    33.   }
    34. }
    Now I could create another IWeatherEffect class to apply all of those effects at once
    Code (csharp):
    1. class FullWeatherEffect : IWeatherEffect
    2. {
    3.   IEnumerable<IWeatherEffect> others;
    4.   public FullWeatherEffect(IEnumerable<IWeatherEffect> others)
    5.   {
    6.     this.others = others;
    7.   }
    8.  
    9.   public void Activate()
    10.   {
    11.     foreach (var effect in others)
    12.       effect.Activate();
    13.   }
    14.  
    15.   public void Deactivate()
    16.   {
    17.     foreach (var effect in others)
    18.       effect.Deactivate();
    19.   }
    20. }
    You'll notice in this case my class actually does implement the same interface as the types it contains. That is okay because in this case my FogEffect, LightEffect and Rain classes are intended to stand in anywhere an IWeatherEffect is needed.
    This particular example is an implementation of the composite (not to be confused with composition in general) design pattern.

    I might then use a really simple factory to build standard effects conveniently
    Code (csharp):
    1. class WeatherFactory
    2. {
    3.   public static IWeatherEffect BuildRainEffect()
    4.   {
    5.     return new WeatherEffect(new FogEffect(1,2,3), new LightEffect(4,5,6), new AtmosphereEffect(7,8,9));
    6.   }
    7. }
    Alternatively, I might build my classes like this:
    Code (csharp):
    1. class FogEffect : IWeatherEffect
    2. {
    3.   IWeatherEffect baseEffect;
    4.   public FogEffect(float density, float hue, float brightness, IWeatherEffect baseEffect)
    5.   {
    6.     // initialize locals
    7.   }
    8.   public void Activate()
    9.   {
    10.     RenderSettings.fogColour = // ...
    11.     RenderSettings.fogDensity = // ...
    12.     baseEffect.Activate();
    13.   }
    14.   public void Deactivate()
    15.   {
    16.     RenderSettings.fogColour = // Default or old value
    17.     RenderSettings.fogDensity = // Default or old value
    18.     baseEffect.Deactivate();
    19.   }
    20. }
    21.  
    22. class LightEffect : IWeatherEffect
    23. {
    24.   IWeatherEffect baseEffect;
    25.   public LightEffect(float alpha, float hue, float intensity, IWeatherEffect baseEffect)
    26.   {
    27.     // initialize locals
    28.   }
    29.   public void Activate()
    30.   {
    31.     RenderSettings.ambientLight = // ...
    32.     RenderSettings.ambientIntensity = // ...
    33.     baseEffect.Activate();
    34.   }
    35.   public void Deactivate()
    36.   {
    37.     // you get the idea..
    38.     baseEffect.Deactivate();
    39.   }
    40. }
    Now I don't need a composite container because I can warp the effects up in a hierarchy. Here's how I might build a rain effect:
    Code (csharp):
    1. class WeatherFactory
    2. {
    3.   public static IWeatherEffect BuildRainEffect()
    4.   {
    5.     return new FogEffect(1,2,3, new LightEffect(4,5,6, new AtmosphereEffect(7,8,9)));
    6.   }
    7. }
    This is an example of the decorator design pattern.

    Here again, just a terminology thing. I would say "composition based architecture" though I'm not quite sure how component and composition are different.. I think we are on the same page anyways!
     
    Last edited: Dec 17, 2015
  18. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    I tend to find the same thing when I explain something to someone. It forces you to fully define it in your own head lol :).

    This is a very handy example for me. So following on from that. Can I confirm a few questions with you? I needed to do some studying on after that post (again lol), to understand the differences between composition, composite, aggregate, decorator, ect. So generally you'd use an Interface as a way to abstract, for when one object calls another with an interface implemented. So that object can be treated as a single entity which 'can do' something. Whether that means it's a single class with a few methods, or a composition of child classes, being activated by the parent, and being treated as a single entity. In this example, the car being treated as a vehicle, which can transport. The fact that it has wheels and everything is something unique to that entity, and the caller has no care about it. Like for example a transport function would be universal on all vehicles and would be treated as such. For going from one location to another, but the details of how that's done between a car and a aeroplane, would be completely different. The caller doesn't really care, so long as it's able to call that method.

    When it comes to composition, you wouldn't necessarily suggest against a direct coupling between a parent implementing an interface to 'do something', and it's children? That's as I understand composition at this point, as per this StackOverflow thread: http://stackoverflow.com/questions/9071067/design-patterns-composite-vs-composition. What I'm basically asking, is I think I get that interfaces are a great way for a composition to be abstracted, but when it comes to the ins and outs of an actual composition of objects, would you suggest the parent has a direct coupling with their children, or not? I guess that's another argument in itself. Is an interface regularly used to abstract an entity's component parts, from itself? So the object has no exact knowledge of it's own children, but it knows it has some, and it'll call 'something' on them. Is this just another level of abstraction which 'can' be used, when it's deemed something worthwhile doing. Like on big dynamic objects, which will switch out it's children a lot, and need to be tested?

    And to double check, the components are simply existing individually for re-usability. So a component can be used in different objects (e.g. wheels being used both on cars and trucks).

    Yeah I'm learning the correct terminology for everything here, and it's just taking time to fully grasp each one. With composite, composition, component, all being different things, but all being some comp lol. I think I do have a cleaner idea of it now.

    This has literally been a mass internal clean up for me thus far. I know I've said thanks, but this is brilliant, so thanks lol.
     
  19. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Yes.

    I think you're getting it but I'll say it anyways. "It depends"

    There is nothing fundamentally different between programming a complex object which inherits an interface and programming the components which it owns or uses to perform to the interface. How far you take the abstractions is up to you. There is no reason my Car class should have a direct reference to a Wheels class -- just as I am having my Car class inherit IVehicle, my Wheels could inherit IWheels; that way my Car would not have a direct dependency. Likewise, a particular Wheels implementation could depend on TireRims, or an interface IRims. As a programmer, you need to decide where it makes sense to introduce these abstractions and where it does not make sense. When there is only one implementation, it is generally simpler to just work with a direct dependency.

    In my experience, it's fairly trivial to introduce an interface once a second possible implementation becomes necessary unless someone beats you to the punch and creates the second implementation via a switch statement or a web of method calls.

    However, something to notice: if we allow a direct dependency, it is pretty easy to build our classes as we write them.
    Code (csharp):
    1. class Car
    2. {
    3.   Wheels wheels;
    4.   public Car()
    5.   {
    6.     wheels = new Bridgestone12InchWheels();
    7.   }
    8. }
    If we code to an interface we can't write code like that anymore:
    Code (csharp):
    1. class Car
    2. {
    3.   IWheels wheels;
    4.   public Car()
    5.   {
    6.     wheels = // ?? I can't 'new up' an IWheels so what class will actually perform the IWheels activities?
    7.   }
    8. }
    Instead, we need to use something called Dependency Inversion: the Car no longer depends on a concrete Wheels class. The client of Car, or some other component needs to depend on it instead and "inject" (Dependency Injection) the Wheel component into the car.
    Code (csharp):
    1. class Car
    2. {
    3.   IWheels wheels;
    4.   public Car(IWheels wheels)
    5.   {
    6.     this.wheels = wheels;
    7.   }
    8. }
    This makes my Car class simpler because I don't care what wheels are used, so long as they can perform to the IWheels interface. Unfortunately, it just pushes the complexity elsewhere. The obvious place is to the client of Car:
    Code (csharp):
    1. class Driver
    2. {
    3.   Car myCar;
    4.   public Driver()
    5.   {
    6.     myCar = new Car( // Crap! I need some wheels first....
    7.   }
    8. }
    An alternative is to push the complexity of building objects into another module. Sometimes this is called an Inversion of Control Container (IOC container). Basically, it's job is to know how to put a Car together based on a simple description when someone needs one.

    If you are working in a project large enough to justify an IOC container then it's typical to just push dependency injection all the way to the bottom of your object graphs.
     
    Last edited: Dec 18, 2015
    Fajlworks likes this.
  20. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Yes, Unity framework seems like it was built ground up to go with an approach like interfaces. The notorious SendMessage(), perhaps rightfully so, does the same thing as Interface. If your component implements method named TakeDamage(), it will execute the code. Even better, you don't need to explicitly write interface declaration; if the method name is equal to string provided, it will work. Regardless of project, which means with smart script coding you could reuse them much more.

    I could create ProjectileScript for my FPS game, which sends TakeDamage() when it hits a gameObject. I can later then take the same script and place it in my top-down space shooter and let my other scripts implement same method name, different implementation.

    That's the power I recognised from SendMessage(), because you could take the little script and place it in any project, let other scripts implement the method with that name and it works. Dependency none, code reuse deluxe I say.

    But as they say, with power comes great responsibility. You have no compiler auto-complete, you lose compile-time checking, refactoring, so tracking bugs can be a nightmare if executed poorly. I even heard performance can be bad with many gameObjects/scripts. I can see why many developers decide to avoid SendMessage() altogether. I personally don't use them for the reasons above, but would love to experiment with them somehow, make a small project just to see how it all works out with a complete game.

    Hope it helps!
     
    Last edited: Dec 19, 2015
  21. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    If you are running 4.6 or later you can use ExecuteEvents instead. It does the same thing as SendMessage, but it works with interfaces directly instead of string based programming.

    My latest project relies heavily on the event system. So it made sense to run with ExecuteEvents. And now just about everything is an interface. I'll see if I keep this structure as the project gets bigger. But so far its working nicely.
     
    Fajlworks likes this.
  22. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Kiwasi likes this.