Você já deve ter notado que isso não é uma boa ideia. Resolvemos o problema da duplicação, mas criamos outro: herança só faz sentido quando se tem a necessidade de representar um tipo. Se a nossa classe não é do tipo da outra, herança não deve ser utilizada. Quando estendemos uma classe, não herdamos apenas a sua implementação e sua especificação, herdamos também o seu tipo. Uma classe herdada passa a ser do tipo da outra e, a partir dela, novos sub-tipos podem ser compostos.
Essa é uma opção que a grosso modo funciona, mas que nos leva a muitas inconsistências, mais que isso, pode nos levar a um ciclo vicioso de sempre criar uma classe base para ser estendida no intuito de evitar duplicação de código. Composição também tem um grande mecanismo de reuso e deve ser considerado preferencialmente em detrimento à herança (para grande parte dos casos).
O que precisamos nesse caso é uma forma de extrair essa funcionalidade e importar nas classes que precisam dela. É aí que entra um possível caso de uso de Trait:
Resolvemos de uma forma um pouco mais elegante, sem precisar definir um tipo genérico e fazê-lo ser herdado. Uma Trait é importada para uma classe usando a sintaxe use. Mais de uma Trait pode ser importada a partir da mesma declaração use separando os nomes por vírgulas ou através de mais de uma declaração use. Instanciando tais objetos você conseguirá acessar o método slug().
Mas nem tudo são flores. Quando uma classe passa a usar uma Trait, você precisa garantir que tudo o que for importado vai funcionar corretamente. Por exemplo, a nossa Trait precisa de um atributo nome para funcionar. Uma possível solução para evitar bugs ao utilizá-la em outras classes é validar se o atributo nome existe, caso contrário, lançar uma exceção:
Ou, claro, talvez você tenha bons testes para garantir que todas as classes que importem essa Trait funcionem corretamente.