我学DDD已经一年多了,但我还是对我的总体理解很不满意。我在python中准备了一个复杂的用例示例,其中出现了一些聚合问题。
用例:玩家可以命令自己的单位攻击其他单位,他选择用什么力量攻击攻击后,被攻击单位的所有者会被告知这一事实。
我的问题是,当单元攻击域逻辑中的另一个单元时,我只能访问那些单元聚合,但要计算攻击造成的损害,我需要访问这些单元上id引用的聚合。特别是武器和装甲聚集体(它们是ARS,因为它们可以在没有单元的情况下存在,我想跟踪它们的历史)。
我有两个选择:
在域服务中加载这些聚合并在函数调用中作为参数传递:
unit.attack_other_unit_with_power(unit_being_attacked, power, weapon, armor)
但看起来很糟糕。
从单位集合内部加载武器和装甲集合。
我已经准备好代码来展示这种方法。
应用程序服务。


"""
Game application services.
"""

from game.domain.model.attackpower import AttackPower
from game.domain.exception import PlayerNotOwnerOfUnit, UnitCannotMeleeAttack


class GameService(object):
    """
    Game application services.
    """
    def __init__(self, player_repository, unit_repository):
        """
        Init.

        :param PlayerRepository player_reposistory: Player repository.
        :param UnitRepository unit_reposistory: Unit repository.
        """
        self._player_repository = player_repository
        self._unit_repository = unit_repository

    def player_order_unit_to_melee_attack_another_unit_using_power(
        self, player_id, unit_id, unit_being_attacked_id, power
    ):
        """
        Player order his unit to melee attack other unit, using given power.

        :param int player_id: Player id.
        :param int unit_id: Player unit id.
        :param int unit_being_attacked_id: Id of unit that is being attacked.
        :param float power: Power percentage value .
        """
        player = self._player_repository.get_by_id(player_id)
        unit = self._unit_repository.get_by_id(unit_id)
        unit_being_attacked = self._unit_repository.get_by_id(unit_being_attacked_id)
        attack_power = AttackPower(power)

        if not self._is_player_owner_of_unit(player, unit):
            raise PlayerNotOwnerOfUnit(player, unit)
        if not unit.can_melee_attack():
            raise UnitCannotMeleeAttack(unit)
        unit.melee_attack_unit_using_power(unit_being_attacked, attack_power)

        self._unit_repository.store(unit)
        self._unit_repository.store(unit_being_attacked)

单位聚合。

from game.domain.model.health import Health
from game.domain.model.event.unitwasattacked import UnitWasAttacked
from game.domain.service.damage import calculate_damage


class Unit(object):
    """
    Unit aggregate.
    """
    def __init__(self, id, owner_id, player_repository, weapon_repository, armor_repository, event_dispatcher):
        """
        Init.

        :param int id: Id of this unit.
        :param int owner_id: Id of player that is owner of this unit.
        :param PlayerRepository player_repository: Player repository implementation.
        :param WeaponRepository weapon_repository: Weapon repository implementation.
        :param ArmorRepository armor_repository: Armor repository implementation.
        :param EventDispatcher event_dispatcher: Event dispatcher.
        """
        self._id = id
        self._owner_id = owner_id
        self._health = Health(100.0)
        self._weapon_id = None
        self._armor_id = None
        self._player_repository = player_repository
        self._weapon_repository = weapon_repository
        self._armor_repository = armor_repository
        self._event_dispatcher = event_dispatcher

    def id(self):
        """
        Get unit id.

        :return: int
        """
        return self._id

    def can_melee_attack(self):
        """
        Check if unit can melee attack.

        :return: bool
        """
        if self._is_fighting_bare_hands():
            return True
        weapon = self._weapon_repository.get_by_id(self._weapon_id)
        if weapon.is_melee():
            return True
        return False

    def _is_fighting_bare_hands(self):
        """
        Check if unit is fighting with bare hands (no weapon).

        :return: bool
        """
        return self.has_weapon()

    def has_weapon(self):
        """
        Check if unit has weapon equipped.

        :return: bool
        """
        if self._weapon_id is None:
            return False
        return True

    def melee_attack_unit_using_power(self, attacked_unit, attack_power):
        """
        Melee attack other unit using given attack power.

        :param Unit attacked_unit: Unit being attacked.
        :param AttackPower attack_power: Attack power.
        """
        weapon = self.weapon()
        armor = attacked_unit.armor()

        damage = calculate_damage(weapon, armor, attack_power)
        attacked_unit.deal_damage(damage)

        self._notify_unit_owner_of_attack(attacked_unit)

    def _notify_unit_owner_of_attack(self, unit):
        """
        Notify owner of given unit that his unit was attacked.

        :param Unit unit: Attacked unit.
        """
        unit_owner = unit.owner()
        unit_was_attacked = UnitWasAttacked(unit.id(), unit_owner.id())
        self._event_dispatcher.dispatch(unit_was_attacked)

    def owner(self):
        """
        Get owner aggregate.

        :return: Player
        """
        return self._player_repository.get_by_id(self._owner_id)

    def armor(self):
        """
        Get armor object.

        :return: Armor
        """
        if self._armor_id is None:
            return None
        return self._armor_repository.get_by_id(self._armor_id)

    def weapon(self):
        """
        Get weapon object.

        :return: Weapon
        """
        if self._weapon_id is None:
            return None
        return self._weapon_repository.get_by_id(self._weapon_id)

    def deal_damage(self, damage):
        """
        Deal given damage to self.

        :param Damage damage: Dealt damage.
        """
        self._health.take_damage(damage)

问题是这是否可以从聚合内部访问reposistory,仅用于读取(不存储)?
如果我想把单位的盔甲换一下然后储存起来呢。
armor = unit.armor() # loaded using repository internallyarmor.repair()armor_repository.store(armor)
是违反了什么规定,还是会造成问题?
如果您对本规范有任何其他意见,我将很高兴听到。
更新:我发现了另一个问题。如果每次攻击后我想降低武器质量怎么办?
我必须改变武器状态并存储它,但从insisde存储聚合是一个坏主意,因为我们无法控制它。

最佳答案

一般来说,从聚合中对存储库执行任何操作都是一个坏主意。尤其是从一个不相关的聚合中对另一个聚合的存储库执行操作。聚合的目的是维护该实体的不变量,并且只维护该实体的不变量。每当您在多个实体上执行操作时,您可能希望它进入域服务。
我想说你最好的选择,是第一个。如果您确实需要许多对象来计算损害,那么将它们打包到一个值对象中可能会更干净。您不一定需要这个新值对象中每个实体的所有属性,只需要应用于损坏计算的属性。您可以将这个新对象称为“DamageAttributes”,然后您的方法签名将如下所示:

unit.attack_other_unit_with_power(unit_being_attacked, damage_attributes)

最后一点,我试着做了一个类似的DDD游戏引擎前一阵子我的一个游戏。我遇到了很多摩擦,就像你一样,最终放弃了它,转而采用一种更具事务性的脚本方法。我的生活变得轻松多了,我一次也不后悔这个选择。并不是所有的项目都适合DDD,在我看来,这可能是其中之一。当规则不断变化时,DDD最受欢迎,但对于游戏引擎来说,规则的变化通常不会像数据(生命值、生命值、护甲)那么大。你必须扪心自问,你从滴滴涕中得到了什么。在我的情况下,我无法想出一个令人信服的答案。

10-06 02:17