Using interfaces as composeable plugins for a health system

I am trying to build up a health system that makes it easy to plug in standard damage behaviour like adding/removing health. I want to make it so that when the objects health reaches 0 or below then the generic health script calls custom Kill functionality. So for example, if we kill the player then the scene reloads but if we kill an enemy then I can start up an explosion particle effect. I am trying to achieve this by providing a pluggable IKillable interface to my generic health management system.
I know that I can’t serialize an interface and have it pluggable in the inspector so my question is what is the generally accepted way of composing objects in this way? Do I have to create a script for each killable component whose sole responsibility is to initialize the health system with custom kill functionality? Am I better off removing the kill functionality from the health system and have each killable component manage their own death trigger?


Health system for reference:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Health : MonoBehaviour
{
    [SerializeField] int maxHealth;
    [SerializeField] int currentHealth; 

    [SerializeReference] IKillable deathHandler;

    void Start(){
        currentHealth = maxHealth;
    }

    void DecreaseHealthBy(int amount){
        SetHealth(currentHealth - amount);
    }

    void IncreaseHealthBy(int amount){
        SetHealth(currentHealth + amount);
    }

    void SetHealth(int newCurrentHealth){
        currentHealth = newCurrentHealth;

        if(deathHandler != null && currentHealth <= 0){
            deathHandler.Kill();
        }
    }
}

I suggest playing to Unity strengths and staying away from it’s weaknesses. In other words: drop interface fields and say hi to gameObject.GetComponent<interface_name>() (yup, it works).

IHealth.cs

public interface IHealth
{
    int current { get; }
    int max { get; }
    void Set ( int value );
    void Modify ( int change );
}

PawnType1.cs

using UnityEngine;
public class PawnType1 : MonoBehaviour, IHealth
{
    [SerializeField] int _currentHealth = 100; 
    [SerializeField] int _maxHealth = 100;
    [SerializeField] GameObject _spawnOnDeath = null;
  
    #if UNITY_EDITOR
    void OnValidate () => _currentHealth = _maxHealth;
    #endif
  
    /// PawnType2 can implement this differently
    void OnDeath () => Instantiate( _spawnOnDeath , transform.position , transform.rotation );
  
    #region ICanHasHealth
    int IHealth.current => _currentHealth;
    int IHealth.max => _maxHealth;
    void IHealth.Set ( int value ) => _currentHealth = value;
    void IHealth.Modify ( int value )
    {
        _currentHealth += value;
        if( _currentHealth<=0 ) OnDeath();
    }
    #endregion
}

So then you can call these, for example, like this:

 public class Spikes : MonoBehaviour
 {
     void OnCollisionEnter ( Collision collision )
     {
         var health = collision.collider.GetComponentInParent<IHealth>();
         if( health!=null )
             health.Modify( -10 );
     }
 }