Apache81
Apache81

Reputation: 241

Create abstract classes that contain common methods to be used from their children

What I'm trying to do is to create abstract classes that contain common methods to be used from their children. Because I will need a static instance reference for the children, I'm making something like

public abstract class Environment<T> where T : Environment<T> {

  private static T s_instance;

  public static T Instance {
    get {
      return (T)s_instance
    }
  }  
}

In this way if I will call the Instance property on a child, I will get the child instance and not the abstract one declaring the child as:

public Desert : Environment<Desert> { }

So if I do

Desert.Instance

I'll get the Desert instance with all its implementations and new methods (as well as the one defined in the base).
Let's say I have 2 classes like this: Environment and Animal.
Let's say I have Desert and Jungle that are 2 Environment.
Let's say I have Scorpion and Lion that are 2 Animal.

What I would like to do is to access from Environment some properties of the Animal class not knowing anything about its implementations.

Very stupid example:

  1. I will instantiate a Desert (just one environment at a time will be present)
  2. the Desert will have a list of animals prefabs that can be instantiated to match the species allowed there
  3. as soon as it's instantiated, if no animals of that kind are already present, instantiate automatically 1 (e.g. Scorpion) and only one animal so that I can reference it with the static Instance property. No other animals will be instantiated until the one instantiated will be destroyed.

The first problem is that in the Environment I need to have a list (array, dictionary, generic list... whatever) that is defined as a list of Animal, then, it will be Desert duty to fill that list with Scorpion and others. Apparently I cannot define a structure with generic types:

protected List<Animal<T>> animalsList = new List<Animal<T>> ();


Another thing is: seeing that the action of instantiating a generic animal in an empty environment is common, I would like that my generic Environment could check if an animal has already been instantiated and, if not, instantiate one. The problem is that because Animal is generic, I cannot do

if (Animal.Instance == null) {
   Instantiate (animalList[Random(0, animalList.Length)]);
}

(please note that the previous is a pseudocode).

I hope I was not too confusing. Maybe it's the entire idea that is wrong and maybe I cannot have generics, but if you have any suggestion it's very welcome. Thanks to everyone :-)


EDIT: Why generics???
Because I was trying to make a base class with the common operations and because I need a static reference to the Instance of the children, I have defined the base classes as generic. Again, if I do this the Instance property needs to be declared just in the base class but if it when it will be called by the children, it will give back the children instance.
Examples:

public abstract class Environment<T> where T : Environment<T> {

  private static T s_instance;

  public static T Instance {
    get {
      return (T)s_instance
    }
  }

  public int CommonMethod () {
     return 1;
  }
}

and

public class Desert : Environment<Desert> {

   public int SandStorm () {
      return 12;
   }
}

and

public class Jungle : Environment<Desert> {

   public int Rain () {
      return 200;
   }
}

in my controller I can do

public class Controller () {

   public void CallCommonMethod () {
      Console.Write (Desert.Instance.CommonMethod ());
      Console.Write (Jungle.Instance.CommonMethod ());
   }

   public void CallSandStorm () {
      Console.Write (Desert.Instance.SandStorm ());
   }

   public void CallRain () {
      Console.Write (Jungle.Instance.Rain ());
   }
}

In this case it's NOT permitted to do

public class Controller () {

   public void CallCommonMethod () {
      Environment.Instance.CommonMethod ();
      // or
      Environment<T>.Instance.CommonMethod ();
      // or similar
   }
}

If instead I have

public abstract class Environment {

  public int CommonMethod () {
     return 1;
  }
}

and

public class Desert : Environment {

   public Desert Instance { get; private set; }

   public Desert () {
      Instance = this;
   }       

   public int SandStorm () {
      return 12;
   }
}

and

public class Jungle : Environment {

   public Jungle Instance { get; private set; }

   public Jungle () {
      Instance = this;
   }       

   public int Rain () {
      return 200;
   }
}

I need to define the Instance for each children. Probably I'm lazy but I thought that the generic way would be better.

Upvotes: 0

Views: 193

Answers (4)

Apache81
Apache81

Reputation: 241

For the sake of completeness, this is my final solution. Please note that some of the part are in pseudocode. This is just to give you an idea of what I had in mind.

public abstract class Environment<T> where T : Environment<T> {

   private List<Animal> animalList;
   private static T s_instance;

   public static T Instance {
      get {
         return (T)s_instance
      }
   }

   public void ToCallInConstructor () {
      PopulateTheList ();
      if (Animal.Instance == null) {
         Instantiate (animalList.Get (defaultAnimal)); // pseudocode
      }
   }

   public int CommonMethod () {
      return 1;
   }

   public abstract void PopulateTheList();
}

and

public abstract class Animal {

   public static Animal Instance { get; private set; }

   public Animal () {
      Instance = this;
   }

    public static T GetInstance<T>() where T : Animal {
       return (T)Instance;
    }
}

everything else will follow the example I made in the edited part of my question in which I explained why the generics.

Thank you very much to everyone's suggestion, especially Patrick and CoderDennis. :-)

Upvotes: 0

CoderDennis
CoderDennis

Reputation: 13837

You just need to add the code to create the instance within the abstract generic base class.

public abstract class Environment<T> where T : Environment<T>, new()
{
    private static T _instance = new T();
    public static T Instance
    {
        get { return _instance; } // no need to cast here. It's already of type T.
    }

    public int CommonMethod()
    {
        return 1;
    }
}

public class Desert : Environment<Desert>
{
    public int SandStorm()
    {
        return 12;
    }
}

Then usage is as you desired. Desert.Instance.SandStorm() and Desert.Instance.CommonMethod().

Suggestions in the comments to use a factory and/or get rid of the singleton requirement are probably valid, but you were already very close to what you were looking for.

Edit:

You can also access the static Instance property like this:

Environment<Desert>.Instance.CommonMethod();
Environment<Desert>.Instance.SandStorm();

But you can't get an Instance until you specify the generic type. So Environment.Instance isn't available.

Upvotes: 2

Patrick
Patrick

Reputation: 736

For your lazyness problem you could do something like this

public abstract class Environment
{
    private static Environment instance;

    public static T GetInstance<T>() where T : Environment
    {
        return (T)instance;
    }
}

public class Desert : Environment
{

}

public class class1
{
    public void SomeMethod()
    {
        Environment.GetInstance<Desert>()
    }
}

Upvotes: 1

Ian P
Ian P

Reputation: 12993

There are a lot of questions here, but start with understanding Generic type Covariance and Contravariance:

Return an inherited type from a method

It's a very common question. This is a link to one that I recently answered.

Upvotes: 0

Related Questions