refactoring modello di strategia python per utilizzare la classe base astratta

Oct 07 2020

Mi sono imbattuto in questa implementazione del modello di strategia

https://github.com/jtortorelli/head-first-design-patterns-python/blob/master/src/python/chapter_1/adventure_game.py

class Character:
    def __init__(self):
        self.weapon_behavior = None

    def set_weapon(self, weapon_behavior):
        self.weapon_behavior = weapon_behavior

    def fight(self):
        self.weapon_behavior.use_weapon()


class Queen(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = KnifeBehavior()


class King(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = BowAndArrowBehavior()


class Troll(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = AxeBehavior()


class Knight(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = SwordBehavior()


class WeaponBehavior:
    def use_weapon(self):
        raise NotImplementedError


class KnifeBehavior(WeaponBehavior):
    def use_weapon(self):
        print("Stabby stab stab")


class BowAndArrowBehavior(WeaponBehavior):
    def use_weapon(self):
        print("Thwing!")


class AxeBehavior(WeaponBehavior):
    def use_weapon(self):
        print("Whack!")


class SwordBehavior(WeaponBehavior):
    def use_weapon(self):
        print("Thrust!")


knight = Knight()
king = King()
queen = Queen()
troll = Troll()
knight.fight()
king.fight()
queen.fight()
troll.fight()

Sarebbe corretto rifattorizzarlo nel modo seguente, usando un ABC?

from abc import ABC, abstractmethod


class Character:
    def __init__(self):
        self.weapon_behavior = None

    def set_weapon(self, weapon_behavior):
        self.weapon_behavior = weapon_behavior

    def fight(self):
        self.weapon_behavior.use_weapon()


class Queen(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = KnifeBehavior()


class King(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = BowAndArrowBehavior()


class Troll(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = AxeBehavior()


class Knight(Character):
    def __init__(self):
        super().__init__()
        self.weapon_behavior = SwordBehavior()


class WeaponBehavior(ABC):
    @abstractmethod
    def use_weapon(self, message):
        print(message)

class KnifeBehavior(WeaponBehavior):

    def use_weapon(self):
        super().use_weapon("Stabby stab stab")


class BowAndArrowBehavior(WeaponBehavior):
    def use_weapon(self):
        super().use_weapon("Thwing!")

class AxeBehavior(WeaponBehavior):
    def use_weapon(self):
        super().use_weapon("Whack!")


class SwordBehavior(WeaponBehavior):
    def use_weapon(self):
        super().use_weapon("Thrust!")


knight = Knight()
king = King()
queen = Queen()
troll = Troll()
knight.fight()
king.fight()
queen.fight()
troll.fight()

Risposte

3 slepic Oct 07 2020 at 23:31

Solo le classi Character e WeaponBehaviour sono effettivamente utili e nessuna delle due deve ereditare nulla. Il resto può essere solo funzioni di fabbrica, perché differiscono solo i costruttori. Se un costruttore di classi fa qualsiasi cosa tranne assegnare argomenti alle proprietà, probabilmente è sbagliato.

Il modello strategico si basa sulla composizione piuttosto che sull'ereditarietà.

class Character:
    def __init__(self, weapon):
        self.weapon = weapon

    def set_weapon(self, weapon):
        self.weapon = weapon

    def fight(self):
        self.weapon.use()


def Queen():
    return Character(Knife())

def King():
    return Character(Bow())

class Weapon:
    def __init__(self, message):
       self.message = message
    def use(self):
       print(self.message)


def Knife():
    return Weapon("Stabby stab stab")

def Bow():
    return Weapon("Thwing!")


king = King()
queen = Queen()
king.fight()
queen.fight()
queen.set_weapon(Bow())
queen.fight()

Si noti che ho rimosso la behaviorparte dei nomi in quanto sembrava un po 'inutile.

L'ho anche rinominato use_weapon()solo use()perché viene richiamato su una weaponvariabile, e quindi sembrava ridondante.

Notate anche che, a meno che non usassi il set_weapon()metodo su un'istanza del personaggio costruita (cioè per cambiare arma nel bel mezzo della battaglia), la classe del personaggio sarebbe stata inutile, perché tutto avrebbe potuto essere fatto con le sole armi. Ovviamente so che questo è solo un codice dimostrativo di pattern, ma volevo sottolineare comunque ..

Come bonus, ecco qualcosa (non solo) per il re :) Notate anche come ancora una volta la composizione sia preferita all'ereditarietà per fornire flessibilità.

class DoubleWeapon:
    def __init__(self, left, right):
        self.left = left
        self.right = right
    def use(self):
        self.left.use()
        self.right.use()

king.set_weapon(DoubleWeapon(Knife(), Sword()))
king.fight()
```