Can I implement generics to create objects that each work with a different class.
Hello all! Thank you for taking the time to check out my question. While I understand the basics of generics, I struggle to apply them into my own code. I'm curious if I could imply them in this scenario since it would greatly increase my alignment with the DRY principle.
I am working on an AI system where NPC's "Workers" can request a job from a controller and then will carry out the assigned task. Each "Job" gets created from a certain "JobType." Each JobType works with a different class inheriting from Monobehaviour to carry out it's work.
I initially had my code set up like this:
JobType
public abstract class JobType: ScriptableObject
{
public abstract void DoJob();
public abstract void FindJobSites();
public abstract void PickRandomJobSite();
}
Shopping
Public class SightSeeing : JobType
{
PointOfInterest[] pointsOfInterest;
public override void DoJob()
{
throw new System.NotImplementedException();
}
public override void FindJobSite()
{
pointsOfInterest = FindObjectsOfType<PointOfInterest>();
}
public override Vector2 PickRandomJobSite()
{
return pointsOfInterest[UnityEngine.Random.Range(0, pointsOfInterest.Length)].Vec2Position;
}
}
SightSeeing
public class SightSeeing : JobType
{
PointOfInterest[] pointsOfInterest;
public override void DoJob()
{
throw new System.NotImplementedException();
}
public override void FindJobSite()
{
pointsOfInterest = FindObjectsOfType<PointOfInterest>();
}
public override Vector2 PickRandomJobSite()
{
return pointsOfInterest[UnityEngine.Random.Range(0, pointsOfInterest.Length)].Vec2Position;
}
}
As you can see in the two classes that inherit from JobType (Shopping and SightSeeing), they each work with a different JobSite (Shopping stores finds a stores an array of Shops and SightSeeing finds and stores an array of PointsOfInterest.
I started thinking that when I want to add a new JobType, I'm going to have to go in and adjust each one to work with it's specific JobSite. I started to wonder if this was a situation I could apply generics in.
Just to get some practice with generics, I attempted to apply it. My attempt to do so looked like this:
New Generic Version of JobType
public abstract class JobType<T> : ScriptableObject where T : JobSite
{
T[] jobSites;
public void FindJobSite()
{
jobSites = FindObjectsOfType<T>();
}
public Vector2 PickRandomJobSite()
{
if (jobSites.Length > 0)
{
return jobSites[UnityEngine.Random.Range(0, jobSites.Length)].Vec2Position;
}
else
{
Debug.LogError("No Job Sites were found");
return Vector2.negativeInfinity;
}
}
}
With JobType setup like this, I thought all I would need to do from then on, is inherit from JobType like before but just specify the JobSite to replace the generic like the two examples below.
New Generic Version of Shopping
[CreateAssetMenuAttribute(menuName = "VillageBuilder/JobType/Shopping")]
public class Shopping : JobType<Shop>
{
}
New Generic Version of SightSeeing
[CreateAssetMenu(menuName = "VillageBuilder/JobType/SightSeeing")]
public class SightSeeing : JobType<PointOfInterest>
{
public override void DoJob()
{
throw new System.NotImplementedException();
}
}
I realized the issue with my attempt when I tried to update my JobController which controlled all of this:
public class JobController : MonoBehaviour
{
JobModel model;
private void Start()
{
model = FindObjectOfType<JobModel>();
}
public void AssignJob(Action<Job> JobRequestCallBack)
{
Right here is where I realized the issue
JobType<JobSite> jobType = model.GetRandomJobType();
jobType.FindJobSite();
Vector2 jobLocation = jobType.PickRandomJobSite();
if (jobLocation == Vector2.negativeInfinity)
{
Debug.LogError("No Vector2 for job location was returned");
return;
}
Job newJob = new Job(jobLocation);
JobRequestCallBack(newJob);
}
}
At the part where I attempt to get a RandomJobType, I was thinking I could just store it has a JobType, relying on polymorphism to carry out the specific task based on what type of JobSite it actually was. But the error popped up where, because JobType is a generic, it wanted me to define what type of JobSite it was working with i.e... JobType'<'Shop'>' or JobType'<'PointOfInterest'>'. This was something that I hadn't thought through and made me reconsider if this was possible / generics should be used here.
So I'm curious of:
Is this situation a place where I can implement a generic class or function to align with the DRY principle, or do I just have to tell each JobType what JobSite it's working with?
If so, what can I switch to correctly implement Generics here?
If not, any thoughts on how I could implement this in a cleaner way?
Am I going about this all the wrong way / have some core principle mixed up.
Thank you so much for your time (:
Let me know if I can include any more information or code or clear up any confusions!