YJack0000 / SimEvo

A Customizable Python Framework written in C++ for Simulating Dynamic Evolutionary Phenomena.
https://pypi.org/project/simevopy/
MIT License
10 stars 0 forks source link

Environment is highly coupled with childs of EnvironmentObject #16

Open MarkLai0317 opened 2 months ago

MarkLai0317 commented 2 months ago

potential recurrent problem: the Environment is coupled with Food and Organism, which will limit the scalability if there are new child of EnvironmentObject or Food, Organism . That is, we need to add new getFunction or new map in Environment.

solution: I suggest we can apply visitor pattern so that we can add different getter without hard coding FoodAllFoods, getAllOrganism . That is, we can define different getter and use it as parameter in the one and only getEnvironmentObject(getterFilter).

Expected result: Environment only depend on EnvironmentObject

YJack0000 commented 2 months ago

Actually, when I was working on this project, ChatGPT suggested using the Visitor pattern many times. However, I’m not really familiar with it.

Is that possible to use UML to show what the changes would look like? Especially about the decouple of Environment and Organism.

MarkLai0317 commented 2 months ago

Here is the uml to demonstrate the result of refactoring. the add and remove is changed to accept EnvironmentObject instead of Food and Organism.

The EnvironmentObjectGetter is what others call Visitor and getObject is what's called visit in visitor pattern.

Visitor pattern can do a lot more than just get. But I currently only use it on Getter. Other usage can be discussed in the future

For the deadOrganism. I suggest we can add variable to Organism to indicate dead. Or is there a reason we need to store them in a different vector ?

YJack0000 commented 2 months ago

This approach decouples Environment from different EnvironmentObject implementations, which is beneficial for adding new features.

However, two points should be considered:

  1. Exposing features to Python users: Directly exposing getObject and Getters might not be intuitive for scripting.
  2. Customizability: While refactoring increases customizability, it raises a concern related to feature #11. If users can customize different EnvironmentObjects, how will they manage their interactions?
YJack0000 commented 2 months ago

The deadOrganism vector is important for the simulation, and I included it for convenience. However, adding a status attribute to organisms might be a good alternative since iterating through all organisms to find the dead ones is not very intensive.

Maybe we could add another discussion or refactor issue to talk about this.

MarkLai0317 commented 1 month ago

Another approach is to only use a getObject method that accept a filter lambda function that get what the user want, such as id or class name. we can make the EnvironmentObject constructor set a attribute string that is equal to the class name.

This way, user can easily customize the filter without exposing too many complex things. They just need to implement a simple boolean function. And this can adapt to complex EnvironmentObject that has custom attribute defined by users.

YJack0000 commented 1 month ago

Can you provide some syntax for me to better understand what it will look like?

MarkLai0317 commented 1 month ago

I have redesign the getObject method that accept a custom filter which accept a EnvironmentObject pointer and return a vector that satisfy the custom filter.

This will not expose too much things to users. and it's quite common for python scripting like below.

my_list = [12, 65, 54, 39, 102, 339, 221, 50, 70] 
# use anonymous function to filter and comparing  
# if divisible or not 
result = list(filter(lambda x: (x % 13 == 0), my_list))