So for example:
I have MonoBehaviour Enemy class. Also I have MonoBehaviors: Health, Movement and KnockbackController. All this classes are the components on an enemy object. Enemy class handles communication between other components. For example Health class have OnDamaged event, and Enemy class have something like:
void OnEnable() {
if (health != null && knockbackController != null)
health.OnDamaged += knockbackController.Knockback();
}
The problem there - many null checks for components in Enemy class but actually I can just do components like Health and Movement - required.
Also there is EnemyData scriptable object which contain base data like max health and speed. And enemy class has reference of EnemyData. So, Enemy class filling out Health with max health from EnemyData and Movement with speed from EnemyData.
I don't like here that, for example, Health script will not be working without something that fill out max health through script. What I can do for this is creating IHealthData and implement it in EnemyData SO script. And in Health script make field of type IHealthData and fill it out with EnemyData through inspector window.
Also there is EnemyAI MonoBehavior class which is a state machine and, for example, will be using Movement component for executing movement behavior.
Is this good architecture? It sounds good for me but maybe there is problems which I didn't saw.