Чтобы делать как ты хочешь - тебе придется знать в каждом конкретном методе какой у тебя будет объект и все это придется как-то менеджить. Поэтому все обычно и пихают в базовый класс который отвечает за функционал ( аля Creature / Character ), чтобы не ебаться с резолвингом кастов, и где какой объект должен был вернуться.
Но если тебя раздражает 10к строк - можешь вынести функциональный код в сервисы и компоненты. И тогда мувинг у тебя будет не в Creautre, а в MoveController (можно сделать его интерфейсом или абстрактным классом, если базовый мув контроллер один). И вот тут уже фабрика тебе поможет все дженерализировать (через дженерики или вайлдкард):
Описываешь интерфейс фабрики со всеми функциями-генераторами и фабрикой классов, которые будут наследовать / переопределять эти генераторы:
Java:
public interface ObjectFactory<T extends Creature> {
static ObjectFactory<Creature> playerFactory() {
return (ObjectFactory<Creature>) Factories.PLAYER_FACTORY;
}
static ObjectFactory<Creature> boatFactory() {
return (ObjectFactory<Creature>) Factories.BOAT_FACTORY;
}
MoveController newMoveController(T creature);
}
Имплементируешь генераторы под нужные объекты(родителей-держателей этих сервисов), которые будут использовать разный функциональный код.
Java:
class PlayerFactory implements ObjectFactory<Player> {
static final ObjectFactory<? extends Creature> FACTORY = new PlayerFactory();
private PlayerFactory() {}
@Override
public MoveController newMoveController(Player creature) {
return new PlayerMoveController(owner);
}
}
Где то у тебя в базовом объекте все это инициализируется через фабрику один раз. Там же создается геттер под нужный сервис.
Java:
public abstract class Creature extends GameObject {
private final MoveController moveController;
protected Creature(int objectId) {
moveController = getFactory().newMoveController(this);
}
protected abstract ObjectFactory<Creature> getFactory();
public final MoveController getMove() {
return moveController;
}
}
В объекте наследнике фабрика вернет соответствующий этому объекту генератор. И потом когда к обращаешься за нужным сервисом, переопределяешь возвращаемый объект и получаешь нужный класс:
Java:
public class Player extends Creature {
@Override
protected ObjectFactory<Creature> getFactory() {
return ObjectFactory.playerFactory();
}
@Override
public final PlayerMoveController getMove() {
return (PlayerMoveController) super.getMove();
}
}
Что это все дает:
Можно разделить функционально код, очень гранулярно разделить его на дерево сервисов и контроллеров, которые будут наследовать и переопределять код друг друга (например, CreatureService с общей логикой, наследованные от него PlayableService и NpcService с собственной) и уже эти компоненты определить нужному родителю (PlayableService - для Player и всех его наследников типа Summon, NpcService - для Npc)
Ну а дальше просто исходя из уровня фантазии кусками любого размера код можно выносить в компоненты.
Насчет производительности - естественно тут будут накладные расходы, тк компилятор не сможет сам определять когда какой объект будет возвращен и все это будет происходить в рантайме. Поэтому все пихать как угарелый в отдельные компоненты не надо, стоит быть рациональным и делать компоненту, которая будет часто переиспользоваться. Тогда вместо постоянного обращения к переменным и функциям через Creature, вся работа будет внутри компоненты PlayerMoveController, который мы получим всего один раз за действие. Это должно быть недорого относительно, остальной нагрузки.