I agree with Irusan, and I'd like to go into a little more detail about his suggestion. It's one of my favorite topics in game dev, the entity component system (ECS) pattern - an incredibly simple yet powerful approach to solve exactly the sort of problem as you have it in a very elegant way.
The basic idea is: Instead of modeling things in your world as a collection of separate explicit classes (perhaps related to each other through inheritance), you make everything a generic "entity" that has "components" attached to it. In the purest form, an entity does really nothing other than storing a list of components, while components are objects of different classes that can store different data and define different behaviors. The simulation of the world (in your case mostly the interaction of the player with items) is implemented by so-called "systems" with process entities on the component level and depending on which types of components they have.
Let's say you have things in the world that can be picked up by the player, and things that can't. In order to allow the player to be able to pick up an item and store it in her inventory, you assign a "PickUpComponent" to it, and the whole pick up logic is implemented in a "PickUpSystem" which will simply ignore all items/entities that don't have a PickUpComponent. This pattern can be applied to almost the entire game logic and leads to very clean, flexible and elegant code. Discovering and understanding it was probably the biggest game dev epiphany ever for me, that's why I'm sort of an "evangelist" for it :D. The implementation is crazily simple and short, especially in relation to the great benefits it brings with it. The difficulty lies in the application. It takes a while to really understand how to use the concept properly and how to design your components and systems and how everything interacts. I'm using it since a bit less than two years now, and I still discover new fascinating tricks frequently (I'm just a small on-off hobby game dev though).
There are many different implementations of ECS for different languages, game engines and frameworks. I have written my own very very basic one, which still provides a huge advantage over how i did things before. The largest part of the power of ECS really lies in its very basic concept/architecture, which can be implemented with just a few lines of code. There are several different opinions/philosophies about how it should be done the "right" way, some more "extremist" than others.
After two years of experimenting and fixing bugs and flaws while still keeping the core code *very* simple all the time, I arrived at something with the following core characteristics:
- An entity is an object/class that contains an array/list of component objects. Entities are self-sufficient and do not require some kind of "entity manager" to be usable and "alive". Other than the list of components, entities hold no other data.
- Components are objects/classes that implement a Component interface with is only used to store them together in an entity's components list. Other than that, they can be anything. Some purists say that components should contain only data and no logic. My approach is less strict, but I try to follow the rule that logic in a component class should only handle the component and it's own data and not know about other component types, systems or any other parts of the game. This definitely helps to keep things loosely coupled and reusable. Some ultra-purists even say that components should be immutable and that whenever component data changes, an entire new component object should be created. I ignore that.
- Components are stores in an entity in a hash map with the Component's class type as the key. So, an entity can only have one component of each type. Also, inheritance is not used for defining component classes. I have experimented with that, but only ran into more problems than benefits.
- As a very useful helper tool to manage entities, I have an EntityList class which wraps a list of entities and provides a method to filter entities by their components ownership, i.e. "entityList.getEntititiesWith(ComponentClass1, ComponentClass2, etc.)". This method returns another EntityList, providing the same interface and allowing further filtering. EntityLists are used everywhere. Usually, a game has one "primary" EntityList that basically holds the entire game world (unless splitting it up into multiple lists is advisable for performance reasons). Other places where EntityLists are used are things like player inventory, and for filtering in "systems" that only operate on entities that have specific components.
- While "systems" are a core concept in my solution, they are *not* part of the library implementation at all. A system is basically just an object or function that operates on entities that have specific components (like a "GravitySystem" on entities that have a "PhysicsComponent" with properties like mass, velocity etc.). How exactly systems are implemented can vary and depends on the particular game.
You can take a look at my tiny ECS (actually just "EC" without the "S", I guess) library here: https://github.com/sebastian-bechtold/manfred-ecs-kotlin/blob/master/src/ManfredEcs.kt
As I said, it is really super small. It's written in Kotlin, but you could easily port it to any language you use.