Is it good practice to keep a reference to an GO to avoid having to instantiate it?

I have a base item class:

public abstract class ItemBase
{
#region Private Fields
private int id;
private ItemType itemType;
private Transform transform;
private GameObject item;
#endregion

#region Public Fields
public GameObject prefab;
#endregion

#region Public Properties
public GameObject Item
{
    get
    {
        if (this.item == null)
            InstantiateItem();
        return this.item;
    }
}
public Transform Transform
{
    get
    {
        return this.transform;
    }
    set
    {
        this.transform = value;
    }
}
public ItemType ItemType
{
    get
    {
        return this.itemType;
    }
    set
    {
        this.itemType = value;
    }
}
#endregion

#region Constructor
public void Initialize(int id, ItemType itemType, Transform transform = null)
{
    this.id = id;
    this.itemType = itemType;
    this.transform = transform;
}
#endregion

#region Private Methods
private void InstantiateItem()
{
    if (prefab == null)
    {
        throw new Exception("Item prefab not defined.");
    }

    if (this.transform != null)
    {
        this.item = GameObject.Instantiate(this.prefab, this.transform);
    }
    else
    {
        this.item = GameObject.Instantiate(this.prefab);
    }
}
#endregion

}

This structure allows me to create new instances of my items without having to instantiate GO’s.

Usually you would add an “Item” component to a GO (above example does the opposite).

I have managers which have references to these items which create, remove, etc.

I plan on using a database to read the item information and create references to them (only instantiating the item GO’s when needed).

My goal is to only instantiate GO’s when they physically need to be in the game.

Is this architecture/structure/implementation common, or am I going to be running into a lot of problems in the near future?

Well, a commonly held best practice for instantiation is to keep a pool of objects that are called on as needed. This has the benefit of objects typically already being loaded into memory, preventing slow-downs during instantiation and also allowing more direct management of memory and the object pool as a whole. Personally, I feel there are some downsides to this, but I can also see the benefits.

As to your implementation, just to make sure I understand it correctly, you’ll have a DB of items. When an item is needed, you’ll create a new ItemBase() and then Initialize() it with the appropriate data, then Instantiate() the item into existence? The only issue I can see is that this is a lot of work to create an item. The implementation itself doesn’t seem like it would cause any headaches, but if this is something that will happen a lot, I’d throw it through the Profiler to see how performant it actually is.

How does the code know what object to create? In other words, I’m imagining this as “I need object #42, create it” is called by some other script, so a new ItemBase() is generated, Init is called for object #42 (which ties in with additional information in the DB), then it’s instantiated at a location (or local to the script if no Transform is provided). Seems like a lot of overhead to call an object into existence.