Incapsulamento di oggetti mutabili con una struttura speciale in Python

3

Sto scrivendo una libreria per lavorare con tipi speciali di alberi, chiamati alberi Foo . Un albero Foo ha una struttura molto speciale. Esistono alcune operazioni, chiamate bar e baz , che hanno senso solo sugli alberi Foo . Quindi sto progettando una classe FooTree per rappresentare gli alberi Foo .

Per costruire un'istanza FooTree , si potrebbe passare qualche oggetto che rappresenta un albero. Questo oggetto verrà controllato per la struttura speciale e memorizzato internamente:

class FooTree(object):

    def __init__(self, tree):
        ... # check that tree is a valid Foo tree
        self._tree = tree

    def bar(self):
        # operation that only makes sense on a foo tree

Il problema è: cosa succede se costruisco un FooTree da un oggetto mutabile? Ad esempio, supponiamo che Tree sia un tipo le cui istanze siano mutabili:

tree = Tree()
... # build a valid Foo tree
foo_tree = FooTree(tree)
tree.remove_node(0) # tree is no longer a Foo tree

Poiché tree non è più un albero Foo valido e foo_tree racchiude un riferimento a tree , foo_tree non è più valido.

Invece potrei copiare l'albero quando creo un FooTree :

class FooTree(object):

    def __init__(self, tree):
        ... # check that tree is a valid Foo tree
        self._tree = tree.copy()

    def bar(self):
        # operation that only makes sense on a foo tree

Questo risolve il problema, ma ne crea uno nuovo: gli alberi Foo sono in genere molto grandi, quindi copiarli è costoso.

Quali sono i modelli per la creazione di oggetti FooTree da oggetti Tree , senza copiare e senza il problema di mutevolezza?

Ad esempio, potrei avere FooTree accettare un callable che produce un Tree :

class FooTreeBuilder(object):
    def __init__(self, data):
        self.data = data

    def __call__(self):
        tree = Tree()
        ... # build tree from self.data
        return tree

class FooTree(object):

    def __init__(self, builder):
        self._tree = builder()

builder = FooTreeBuilder(data)
foo_tree = FooTree(builder)

Ci sono approcci migliori? Sono specificamente alla ricerca di approcci che ben si prestano alle implementazioni di Python.

    
posta jme 20.05.2015 - 17:48
fonte

1 risposta

2

alcune opzioni sono:

  • utilizzando alberi immutabili come struttura dati di base
  • utilizzando un builder o operazioni di mutazione per costruire un FooTree senza bisogno di un albero
  • copia l'albero di input (che è una buona pratica a meno che tu non abbia davvero grandi problemi di dati e di prestazioni)

Si noti che per costruttore intendo lo schema del builder come descritto di Joshua Bloch . (Articolo lungo, ma il problema di progettazione è rilevante per Python così come lo è per Java.) Il tuo builder Callable in realtà non mitiga il problema, perché se un utente di FooTree ha già un albero, chiamerà semplicemente FooTree (lambda: myTree ) e quindi avrai di nuovo il problema di mutevolezza.

Il pattern builder (o rendendo mutevole la creazione di FooTree), d'altra parte, consente ai tuoi utenti di creare FooTrees direttamente senza aver prima bisogno di un albero.

e la scelta migliore dipende da:

  • se Tree è una classe che puoi progettare (e rendere immutabile) invece di usare qualcosa che viene dato.
  • nel dominio dell'applicazione: gli alberi cambiano spesso o l'immutabilità è in realtà una buona astrazione?
  • quanta separazione vuoi tra i tuoi FooTrees e gli altri alberi. Forse copiare l'albero ha buoni vantaggi collaterali come l'utilizzo di una struttura dati specializzata per i propri alberi.
  • hai davvero bisogno degli alberi comunque? sono il vostro materiale di input dato? o sarebbe una soluzione senza la classe Tree avere senso?

Tutto ciò è indipendente dalla lingua che sta usando e facendolo diventare Pythonic è una questione di implementazione del design scelto.

    
risposta data 20.05.2015 - 19:15
fonte

Leggi altre domande sui tag