Modifica __class__ in una fabbrica?

2

Sto analizzando un insieme di file XML di tipi diversi (questi tipi sono noti in anticipo).

Questi sono i miei requisiti:

  • Voglio che un oggetto rappresenti ogni documento XML (mappatura object-xml)
  • Preferirei avere una classe diversa per ogni tipo, così che identificare il tipo di file XML equivale a considerare la classe dell'istanza
  • Non conosco il tipo in anticipo
  • Voglio un comportamento specifico per ogni oggetto

Ma per semplicità, consideriamo, ho solo bisogno di aprire numeri diversi, di due tipi noti:

  • numero pari
  • numero dispari

In termini di pattern di progettazione, utilizzo una variazione del pattern di progettazione di fabbrica , dove l'afctory si trova nella __init__ della classe di livello superiore e imposto l'implementazione corretta cambiando __class__ in modo dinamico

class Number():
    def __init__(self, val):
        # common implementation (in case of XML, I do the recursion of xml imports)
        self.value = val

        # This is also the point where I discover the type of my object
        # Hence, the constuctor is also a factory of the appropriate type
        if val % 2 == 0:
            self.__class__ = EvenNumber
        elif val % 2 == 1:
            self.__class__ = OddNumber
        else:
            # keep the default Number implementation
            pass


class EvenNumber(Number):
    def half(self):
        ''' 
        specific behaviour for even numbers
        '''
        return self.value / 2


class OddNumber(Number):
    def next_int(self):
        return self.value

if __name__ == '__main__':
    for x in (Number(2), Number(3)):
        '''
        In the main algorithm, the processing depends on the type of the object
        '''
        if isinstance(x, EvenNumber):
            print(x.half())
        elif isinstance(x, OddNumber):
            print(x.next_int())

Cosa ne pensi di questo approccio?

Sapevo che __class__ poteva essere letto, ed ero molto sorpreso che potesse anche essere scritto. Cosa ne pensi di cambiare __class__ in modo dinamico?

Si tratta di un modello di progettazione ben noto (non è il modello di fabbrica né il modello di stato né il modello di strategia , anche se sembra collegato a tutti loro)

Modifica Il mio vero caso d'uso

Voglio aprire una tassonomia XBRL, che è composta da linkbase XML. Tranne il generico XML Linkbase, voglio gestire più precisamente alcuni di loro:

  • XML Label linkbase
  • Definizione XML linkbase
  • ecc.

In sostanza, ecco cosa ho fatto:

class Linkbase(etree.xml.ElementTree):
    '''
    Generic linkbase
    '''
    def __init__(self, filename):
        # parse XML
        if self.type == 'definition':
            self.__class__ = DefinitionLinkbase
        elif self.type == 'label':
            self.__class__ = LabelLinkbase

     @property
     def type(self):
        return self.find('//xpath/expression').tag

class LabelLinkbase(Linkbase):
   @property
   def labels(self):
     # find all appropriate elements and returns a list of Labels

class DefintionLinkbase(Linkbase):
   @property
   def definitions(self):
      # returns all definitions

In alternativa, avrei potuto usare una fabbrica. Posso pensare a qualcosa del genere, ma non sembra elegante come il primo approccio.

class Linkbase(etree.xml.ElementTree):
    '''
    Generic linkbase
    '''
    def __init__(self, tree):
        self.tree = tree

     @property
     def type(self):
        return get_type(self.etree)

class LabelLinkbase(Linkbase):
   @property
   def labels(self):
     # find all appropriate elements and returns a list of Labels

class DefintionLinkbase(Linkbase):
   @property
   def definitions(self):
      # returns all definitions

def build_Linkbase(file):
    tree = etree.parse(file)
    if get_type(tree)== "defintion"
       return DefintionLinkbase(tree)
    elif get_type(tree) == "label":
       return LabelLinkbase(tree)
    else:
       return Linkbase(tree)
def get_type(tree):
    return tree.find('//xpath/expression').tag
    
posta rds 01.02.2013 - 11:32
fonte

2 risposte

1

Dato il tuo esempio, consiglierei sicuramente di definire le proprietà pari / dispari. Ora, per rispondere alla domanda, farei quanto segue:

class Number():
    def __init__(self, val):
        # common implementation (in case of XML, I do the recursion of xml imports)
        self.value = val

class EvenNumber(Number):
    def half(self):
        ''' 
        specific behaviour for even numbers
        '''
        return self.value / 2


class OddNumber(Number):
    def next_int(self):
        return self.value

# this is your factory
function buildNumber(val):
    if val % 2 == 0:
        return EvenNumber(val)
    else:
        return OddNumber(val)

Questo è più vicino al modello di progettazione di fabbrica. Se si desidera rendere BuildNumber un membro di una classe NumberFactory configurabile, è possibile.

Modifica

La bellezza e l'eleganza sono negli occhi di chi guarda, ma trovo il tuo secondo approccio migliore. Questo è un giudizio, ma penso che il primo non riesca a seguire il principio della meno sorpresa perché il costruttore restituisce un oggetto di un tipo diverso (che è inaspettato nella maggior parte delle lingue, incluso python).

Il secondo, tuttavia, mostra una struttura sana:

  • una gerarchia di classi che espone funzionalità
  • una funzione utilizzata per generare gli oggetti appropriati da un'origine

Ciò porta a una migliore separazione delle responsabilità e a una maggiore flessibilità per quanto riguarda la fonte dei dati. Ciò a sua volta porta a una manutenzione più semplice: se devi aggiungere un altro tipo, questo non influirà sul codice esistente. Questo è noto come principio di apertura / chiusura ed è una buona cosa.

Il primo approccio rende la classe base consapevole dei suoi implemetri, che è una cosa molto (molto) cattiva, perché gli oggetti astratti non dovrebbero dipendere dai dettagli di implementazione.

    
risposta data 01.02.2013 - 11:49
fonte
0

Puoi fare ciò:

class MetaNumber(type):
    subclasses = {}

    def __init__(cls, name, bases, dict):
        super(MetaNumber, cls).__init__(name, bases, dict)

        # if the subclass has an attribute named type
        # store it
        try:
            type = cls.type
        except AttributeError:
            pass
        else:
            MetaNumber.subclasses[cls.type] = cls

    def __call__(cls, value):
        # lookup the appropriate type
        subclass = MetaNumber.subclasses[value % 2]
        # substitute subclass
        return super(MetaNumber, subclass).__call__(value)

class Number(object):
    __metaclass__ = MetaNumber

    def __init__(self, val):
        self.value = val


class EvenNumber(Number):
    type = 0

    def half(self):
        ''' 
        specific behaviour for even numbers
        '''
        return self.value / 2


class OddNumber(Number):
    type = 1

    def next_int(self):
        return self.value

In questo modo:

  1. Non devi modificare __class__ di oggetti già costruiti
  2. Non c'è alcuna duplicazione nel recupero del tipo, perché solo il metodo __call__ lo controlla
  3. La semplice definizione della sottoclasse con un attributo di classe di tipo appropriato è sufficiente per supportarlo.
risposta data 01.02.2013 - 20:49
fonte

Leggi altre domande sui tag