Я уже некоторое время размышляю над этим вопросом об объектно-ориентированном дизайне и не могу найти удовлетворительного решения, поэтому подумал, что брошу его здесь толпам, чтобы высказать свое мнение.
У меня есть класс Игра, представляющий пошаговую настольную игру, мы можем предположить, что для целей этого вопроса он похож на «Монополию». В моем дизайне у меня есть класс Игрок, содержащий метод Принять поворот.
Игра проходит через все Игрок и вызывает метод TakeTurn, чтобы сделать все необходимое для завершения поворота. Я хочу иметь возможность иметь n игроков и иметь возможность назначить произвольное их количество компьютерными игроками. Итак, я подумал о том, чтобы иметь класс HumanPlayer и класс КомпьютерПлеер, оба из которых являются производными от Player.
Игра знает только класс Игрок и просто вызывает метод Принять поворот для каждого Игрок по очереди. Моя проблема заключается в том, что объекты КомпьютерПлеер могут быть полностью автоматизированы, то есть в соответствии с примером «Монополия» могут принять решение о покупке собственности, используя некоторую логику. Теперь, с объектом HumanPlayer, он должен получить ввод от фактического пользователя, чтобы иметь возможность, например, купить собственность, что, похоже, подразумевает другой интерфейс и потенциально означает, что они не должны получать
Мне не удалось найти хорошее решение проблемы, если класс Игра не знает фактических реализаций различных классов Игрок. Я всегда мог сделать предположение в классе Игра, что всегда будут только люди и компьютерные игроки, и эффективно закрыть его для расширения, но это не похоже на хорошее объектно-ориентированное программирование.
Приветствуются любые мнения по этому поводу.





Я думаю, вы не должны позволять классу Game обрабатывать ввод-вывод. таким образом (блокирующий) метод TakeTurn скроет с игрового поля средства реализации. он может использовать другие объекты для связи с пользователем.
Все, о чем должен заботиться класс Game, - это состояние доски и ход. все игроки должны реализовать интерфейс для одного игрока и скрыть всю реализацию от игры.
+1 за стремление дать Игре хорошую сплоченность (надлежащие обязанности).
Вместо того, чтобы сообщать игровому классу, что существует только один человек, почему бы не позволить ему получить этот ввод во время меню / инициализации игры? Если игроков больше, это можно решить с помощью некоторой формы ввода (выберите игроков в меню) до инициализации игрового класса.
Я думаю, что класс Game не должен беспокоиться о каких-либо реализациях классов Player, а также игнорировать пользовательский интерфейс.
Любой ввод пользователя должен обрабатываться классом HumanPlayer.
Интерфейс, который Игрок представляет для Игра, ортогонален поведению производных классов Игрок.
Тот факт, что реализация Принять поворот варьируется в зависимости от конкретного типа объекта Игрок, не должен вызывать беспокойства.
Я не уверен, что ты хочешь этого
public abstract class Player
{
int position;
DecisionMaker decisionDependency;
...
public void TakeTurn()
{
position += RollDice();
GameOption option GetOptions(position);
MakeDescion(option);
}
protected int RollDice()
{
//do something to get the movement
}
protected abstract void MakeDecision(GameOption option);
}
Public class ComputerPlayer : Player
{
public ComputerPlayer()
{
decisionDependency = new AIDecisionMaker();
}
protected override void void MakeDecision(GameOption option)
{
decisionDependency.MakeDecision(option);
//do stuff, probably delgate toan AI based dependency
}
}
Public class HumanPlayer : Player
{
public HumanPlayer()
{
decisionDependency = new UIDecisionMaker();
}
protected override void void MakeDecision(GameOption option)
{
decisionDependency.MakeDecision(option);
//do stuff, probably interacting with the a UI or delgate to a dependency
}
}
Я бы сказал, что класс Игра не должен заботиться о том, является ли он компьютерным игроком или человеком. Он всегда должен вызывать Принять поворот для следующего класса игрока. Если это игрок-человек, то ответственность за взаимодействие с пользователем и запрос у пользователя, что ему делать, возлагается на класс Игрок. Это означает, что он будет блокироваться, пока пользователь не примет решение. Поскольку обычно взаимодействие с пользовательским интерфейсом происходит в основном потоке приложения, важно только, чтобы блокирующий Принять поворот не блокировал приложение в целом, иначе пользовательский ввод не может быть обработан, пока Игра ожидает Принять поворот.
Если Game управляет игровым состоянием и, выполняя ввод-вывод, Game делает слишком много.
Вы хотите, чтобы игра была сосредоточена только на правилах, поворотах и изменениях состояния. Игра не знает, кто такой игрок; он знает только, что у него есть игроки.
Вы хотите, чтобы игроки проверяли состояние игры и выполняли юридические действия во время своих ходов.
Люди-игроки и игра в целом обе используют общий пакет ввода-вывода, который показывает состояние игры и предлагает людям вводить данные.
Вы можете эффективно использовать Java Observable, сделав пакет ввода-вывода Observer игры. Таким образом, изменения состояния игры передаются в систему ввода-вывода для отображения или регистрации, или и того, и другого.
Вместо того, чтобы класс Игра вызывать Принять поворот для всех игроков, игроки должны вызывать Принять поворот в классе Игра, а класс Игра должен проверять, идет ли свой ход правильный игрок.
Это должно помочь решить проблему проигрывателя Пользователь и Компьютер.
У меня, вероятно, было бы не два класса HumanPlayer и ComputerPlayer, а один класс Player, который настраивается во время создания с правильной стратегией ввода.
Способ получения игроком информации для принятия решения о своем ходе в следующем ходу игры - это Только, который отличается (по крайней мере, от исходного описания проблемы), поэтому просто инкапсулируйте это в отдельной абстракции.
Какой бы высокоуровневый класс ни создавал игру, он должен также создать два набора игроков (один человек, другой - смоделированный компьютером) с правильной стратегией ввода для каждого, а затем просто передать эти объекты игроков игровому объекту. Тогда класс Game будет вызывать метод TakeTurn только для заданного списка игроков для каждого нового хода.
Я не уверен, что здесь применяется принцип открыт-закрыт.