What's the best way to define types of objects that gets often reused?

Hello, I’m kinda of stuck trying to decide what’s the best way to organize my code. I need to create several types of objects. I’ll use spells and effects as an example, but the same problem is true for all other kinds of game objects. For example, my character needs to cast many different types of spells, some of which can cast many different types of effects and projectiles.

Right now, I have it set up so that each spell has its own class with all its properties and methods. One of its variables is the “effect”, which will be one instance of a class that extends the Effect class. So this effect could be FireEffect, IceEffect etc. So when it’s time to use this spell, the game might need to many instances of that effect or projectile. So the game will clone the Effect object as many times as it needs to and render them all. For example, the “FrostBolt” spell has a variable that is an instance of the IceEffect object. If the spell needs to cast 2 projectiles, the renderer will clone the instance IceEffect object from the FrostBolt class and render them accordingly.

I can’t help to feel this seems kinda of disorganized and inefficient. But I’m not sure how could it be better? I thought of, instead of using a dummy object for the Effect, instead just using a reference to its class. So the Frostbolt object would have a variable pointing to IceEffect.class instead of an instance of that class, and the game would just create new instances of that class. But if I do that, I would lose the ability to use setters on that IceEffect to modify it for that specific case. What if I need IceEffects of 100 different sizes? I don’t wanna have to create 100 different classes.

How do you guys usually solve these kind of problems? I want to build something that is easy to read and easy to maintain. Specifically, I want an engine that is robust enough so that later on it’s easy for game designers to add new types of spells and effects without much hassle. What’s the best way to approach this problem?