где в иерархии классов должны быть написаны методы экземпляра? - PullRequest
4 голосов
/ 01 февраля 2011

Вот часть класса «иерархия», которую я использую для имитационной модели (мой код написан на Python, но я думаю, что мой вопрос не зависит от языка):

class World:
# highest-level class, which "knows" everything about the model
# most likely will have just one instance
# contains (e.g., in a dictionary) references to all the instances of class Agent

class Agent:
# each instance represents an agent
# an agent can, among other things, move around according to certain rules
# movement depends on the internal state of the agent,
# but also on the terrain and other information not stored in the Agent instance

Вопрос: куда мне поместить метод экземпляра move?

Я думал, что должен ограничить зависимость class Agent классами ниже в иерархии, чем он сам (т.е. классы, экземпляры которых содержатся в Agent экземплярах). Но это означает, что метод move не может быть в class Agent, поскольку он создает зависимость от (по крайней мере, интерфейса) классов, которые описывают ландшафт и т. Д., Поэтому я мог бы также добавить к Agent ссылку на (и, следовательно, зависимость от) World. Это нормально с точки зрения разработки программного обеспечения?

Альтернативой является помещение метода move в class World, где он не вызовет никаких дополнительных зависимостей. Тем не менее, class World будет тогда выполнять почти всю работу, и мне кажется, что это будет идти вразрез с основной идеей ООП (которая, как я понимаю, не сводит всю функциональность в одно место, а скорее содержит ее в соответствующие классы).

Вопросы производительности имеют второстепенное значение (и я не думаю, что производительность все равно будет отличаться между двумя подходами).

РЕДАКТИРОВАТЬ: я неправильно использовал слова "иерархия классов" выше. Я не имел в виду иерархию наследования, просто группу классов, экземпляры которых содержат друг друга.

Ответы [ 5 ]

3 голосов
/ 01 февраля 2011

Необходимо принять во внимание принцип единой ответственности . По сути, каждый класс должен нести ответственность за одну «вещь» и должен полностью инкапсулировать эту одну ответственность. И вы должны наследовать только там, где ответственность расширена. Вы всегда должны быть в состоянии сказать, что класс расширения составляет 100% от родительского и более (более в определенном смысле). У вас никогда не должно быть ситуации, когда ребенок является подмножеством родителя и «меньше». Таким образом, человек, расширяющий мир, не является хорошим дизайном, поскольку есть аспекты мира, которые не имеют отношения к человеку.

Итак, если мы посмотрим на пример, вы бы поместили методы экземпляра на уровень, который определяется ролью этого конкретного класса. Итак, давайте рассмотрим пример более определенно:

class Person:
    name: ""
    birthDate: ""

class PoliceOfficer extends Person:
    badgeNumber: ""

Очевидно, что это псевдокод, но он демонстрирует, что происходит.

Теперь, где бы вы добавили метод move()? Мы можем добавить его к PoliceOfficer, но тогда мы нарушим инкапсуляцию Person, так как человек также может двигаться.

class Person:
    def move(world):

Но куда бы мы добавили метод issueTicket()? Обобщенный Person не может выдать билет, поэтому если бы мы добавили его в класс Person, мы бы сняли с него ответственность. Поэтому вместо этого мы добавили бы его к PoliceOfficer, поскольку именно здесь это имеет смысл.

Что касается создания зависимости, вы должны всегда отдавать предпочтение композиции, а не наследованию . Таким образом, в этом смысле может быть столько зависимостей, сколько вы хотели бы, поскольку все они являются программными зависимостями (ну, вроде как). Поскольку move() принимает экземпляр world (или объект с мировым интерфейсом), зависимость выталкивается из класса в вызывающий код. Таким образом, код вашего класса остается довольно открытым и независимым, но при этом остается продуктивным.

Это обычно считается плохой практикой для жестких кодовых зависимостей. Но инъекция их (через инъекцию зависимостей или композицию) обычно считается хорошей вещью.

В итоге: Поместите методы экземпляра там, где логично их разместить.

1 голос
/ 01 февраля 2011

Я бы положил move в класс Agent. Если в этом нет необходимости, Agent не должен знать весь мир, а должен располагать только той информацией, которая ему нужна. Однако если он знает весь мир, это тоже неплохо. Вот несколько причин:

  1. Как бы вы переместили одного агента, если бы поместили метод move в класс World? Вы хотите передать экземпляр Agent для перемещения в этот метод? Это выглядит довольно некрасиво.
  2. Если вы хотите, чтобы один агент что-то сделал, лучше сделать это в методе экземпляра с точки зрения ООП.
  3. Вы также можете вызвать метод перемещения из экземпляра другого класса, который не знает мир, но конкретный агент.

Однако, если все ваши агенты перемещаются одновременно и вы не хотите, чтобы перемещались отдельные агенты, вы можете просто поместить один метод moveAllAgents в мировой класс, а затем выполнить итерацию по списку агентов и переместить всех этих агентов. Тогда вам не нужен метод move в классе Agent.

1 голос
/ 01 февраля 2011

Поместите метод перемещения туда, где он имеет смысл, мир не может двигаться, агент может.

Если вы хотите получить доступ к функциям мирового уровня, задайте свой метод конструктора параметри передать экземпляр мира в.

world = World()
agent = Agent(world)

Это дает явный доступ к миру от вашего агента, а не предполагает какую-то иерархию.

Вы можете сделать этот шаг дальше и потребоватьчто все игровые объекты в мире принимают мир в качестве параметра.Вы можете применить это, создав базовый класс GameObject, который наследует ваш Агент и другие игровые объекты.

class GameObject:
    def __init__(self, world):
        self.world = world

class Agent(GameObject):
    def __init__(self, world, startX, startY):
        # don't forget to call the super and pass the world to it
        super(Agent, self).__init__(world)
        self.startX = startX
        self.startY = startY

    def move(self):
        print 'I can see the world'
        print self.world

РЕДАКТИРОВАТЬ: Чтобы расширить мои пояснения, если у вас был Enemy у класса и противника тоже был метод move(), велики шансы, что вы захотите, чтобы враг двинулся к агенту.Однако вы не хотите, чтобы враг спрашивал мир о позиции агента, вместо этого вы можете просто сохранить ссылку на агента внутри врага в качестве «цели» и проверять его позицию в любое время.

class Enemy(GameObject):
    def __init__(self, world, target):
        super(Agent, self).__init__(world)
        self.target = target

    def move(self):
        if self.target.x > self.x:
            self.x += 5
0 голосов
/ 01 февраля 2011

Не слишком много зная о вашем приложении, вы также можете позволить агенту «знать» об определенной части вашего мира (возможно, определенной как максимальная область, в которой может быть выполнено движение, ограниченная некоторыми вашими правилами о том, как далекоАгент может переместиться в любой вызов .Move).Таким образом, агент может содержать ссылку на «область обрезки» большого мира (эта концепция украдена из «прямоугольника обрезки», используемого в .net GDI + Graphics Object).с остальными: имеет смысл определить метод Move для класса агента, и для класса агента является приемлемым знание о его окружении.

Хотя ООП стремится минимизировать ненужные зависимости, когда это имеет смысл, это имеет смысл.Человек в реальном мире знает свое окружение и может инициировать действие, необходимое для перемещения в этом окружении из одного места в другое.

0 голосов
/ 01 февраля 2011

Но это означает, что метод перемещения не может быть в классе агента, так как он создает зависимость от (по крайней мере, интерфейс из) классов, описывающих ландшафт, и т. д.

Агент должен знать о своем окружении. Это означает, что Агент должен использовать интерфейсы, которые описывают местность, да. Я бы не назвал это «зависимостью». Нет ничего плохого в том, что класс, реализующий ITerrain, на самом деле следует интерфейсу ITerrain. : -)

Итак, вы положили .move () на агента. Затем метод move () будет проверять окружение и пытаться перемещаться по ним в соответствии с правилами передвижения.

...