Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Probably overcomplicating things using 3 classes of generics with circular reference

Discussion in 'Scripting' started by NeatWolf, Aug 11, 2019.

  1. NeatWolf

    NeatWolf

    Joined:
    Sep 27, 2013
    Posts:
    924
    Hi there,

    probably it's the heat, probably the lack of sleep, or maybe I'm just overcomplicating things.

    I'd need to create a collection of Puzzles, each one with, potentially, a custom puzzleData, and a custom puzzleLogic

    Code (CSharp):
    1.     public class Puzzle<TPuzzleData, TPuzzleLogic> : MonoBehaviour where TPuzzleData: PuzzleData where TPuzzleLogic: PuzzleLogic
    2.     {
    3.         public TPuzzleData data;
    4.         public TPuzzleLogic logic;
    5.  
    6.         public void PuzzleStart()
    7.         {
    8.             logic.PuzzleStart();
    9.         }
    10.  
    11.         public bool PuzzleSolved
    12.         {
    13.             get => data.puzzleSolved;
    14.  
    15.             set => data.puzzleSolved = value;
    16.         }
    17.      
    18.         public bool PuzzleRunning
    19.         {
    20.             get => data.puzzleRunning;
    21.  
    22.             set => data.puzzleRunning = value;
    23.         }
    All good so far (sort of, suggestion on how to improve the code style are welcome)

    But then I'd also love this class
    Code (CSharp):
    1.     [Serializable]
    2.     public class PuzzleData: RememberData // A class from AdventureCreator
    3.     {
    4.         public bool puzzleSolved;
    5.         public bool puzzleRunning;
    6.     }
    To also be able to reference back the Puzzle it's been made for, so I added a TPuzzle parameter to it
    (and later I removed it because I realized I actually just need this as a generic puzzleState data container)

    Also, I have the logic - handling class:
    Code (CSharp):
    1.     [Serializable]
    2.     public class PuzzleLogic: ScriptableObject
    3.     {
    4.         public virtual void PuzzleStart()
    5.         {
    6.         }
    7.  
    8.         public virtual bool CheckPuzzleSolved()
    9.         {
    10.             return false;
    11.         }
    12.      
    13.         public virtual void PuzzleEnd()
    14.         {
    15.         }
    16.     }
    ...but I also would like to reference the base type and the data it's handling, maybe passing the Data type it should be handling, and also the original Puzzle type.

    And of course the TPuzzle Type would be of type Puzzle<PuzzleData, PuzzleLogic<PuzzleData, Puzzle<PuzzleData, PuzzleLogic<PuzzleData, etc. etc.

    Probably I'm just overcomplicating things, but may there's a better way to implement all this?

    For each puzzle I would then
    - create a specific class, like Puzzle_clock : Puzzle<PuzzleClockData, PuzzleClockLogic>
    - create a subclass of the data type, eventually
    - create a subclass of the logic

    I'm also considering to collapse all this complexity to just inheriting from a simple PuzzleBase type and also implementing data and logic inside the same class, since decoupling it is taking away more time than available.

    What are your thought about this? I believe I'm not "keeping it simple", yet the idea of using mutual dependency Generics makes some sense... but probably I'm using the wrong approach.

    And yes, much of the above code should be <abstract>, I know :p
    And, side note, I'm not a fan of using Interfaces to solve this.
    I feel that I may be decoupling the tasks, but tightly coupling the implementation.
    So maybe this model is not suited for the problem.
    Or, I can keep things simple, since I'm already investing too much time into thinking and researching about it:
    https://www.theseus.fi/bitstream/handle/10024/125683/Parviainen_Niko.pdf?sequence=1&isAllowed=y
    https://itworksonmymachine.wordpres...injection-and-inversion-of-control-container/

    Any suggestion is welcome :)
     
    Last edited: Aug 11, 2019
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    Unfortunately this is a situation where tapping into polymorphism is the very thing to uncomplicate this. And interfaces or base abstract classes are a very handy way of getting that.

    I would be using a combination of polymorphism and a factory pattern.

    Here I'll use abstract classes instead of interfaces... though effectively they're doing the same thing... creating and enforcing a contract:
    Code (csharp):
    1.  
    2. public class Puzzle
    3. {
    4.    
    5.     public PuzzleData data;
    6.     public PuzzleLogic logic;
    7.    
    8.     public void PuzzleStart()
    9.     {
    10.         logic.PuzzleStart();
    11.     }
    12.    
    13.     public bool PuzzleSolved
    14.     {
    15.         get { return data.puzzleSolved; }
    16.         set { data.puzzleSolved = value; }
    17.     }
    18.    
    19.     public bool PuzzleRunning
    20.     {
    21.         get { return data.puzzleRunning; }
    22.         set { data.puzzleRunning = value; }
    23.     }
    24. }
    25.  
    26. [Serializable]
    27. public class PuzzleData //could be abstract, not sure your specific needs
    28. {
    29.    
    30.     public bool puzzleSolved;
    31.     public bool puzzleRunning;
    32.    
    33. }
    34.  
    35. public abstract class PuzzleLogic: ScriptableObject
    36. {
    37.     public abstract void PuzzleStart();
    38.  
    39.     public virtual bool CheckPuzzleSolved();
    40.  
    41.     public virtual void PuzzleEnd();
    42.    
    43. }
    44.  
    45. public class ClockPuzzleLogic : PuzzleLogic
    46. {
    47.     public override void PuzzleStart()
    48.     {
    49.         //start clock puzzle
    50.     }
    51.    
    52.     public override bool CheckPuzzleSolved()
    53.     {
    54.         //do check?
    55.         return false;
    56.     }
    57.    
    58.     public override void PuzzleEnd()
    59.     {
    60.         //end clock puzzle
    61.     }
    62. }
    63.  
    Then a factory (this is very loose pseudo-code since PuzzleLogic is a ScriptableObject and I don't know your actual construction case):
    Code (csharp):
    1.  
    2. public static class PuzzleFactory
    3. {
    4.    
    5.     public static Puzzle CreateClockPuzzle()
    6.     {
    7.         return new Puzzle() {
    8.             data = new PuzzleData(),
    9.             logic = GetLogic<ClockPuzzleLogic>() //since your PuzzleLogic is a ScriptableObject I don't know how you plan on loading them
    10.         };
    11.     }
    12.    
    13.     public static PuzzleLogic GetLogic<T>() where T : PuzzleLogic
    14.     {
    15.         return ...;//since your PuzzleLogic is a ScriptableObject I don't know how you plan on loading them
    16.     }
    17.    
    18. }
     
    NeatWolf likes this.
  3. NeatWolf

    NeatWolf

    Joined:
    Sep 27, 2013
    Posts:
    924
    Thanks @lordofduct !

    After quite some thinking and testing, I realised I was actually overcomplicating things, since I made very little use of those inter-linked classes. I realised that only after beginning to use the whole data structure.

    Nevertheless, I like the approach you suggested, and will use it when it's actually needed.
    It was one of those cases when the programmer really gets a bit stubborn on implementing something in a particular way, but I was losing track about another important resource: time spent - and of course the deadline ;)