Accoppiamento del modello e del metodo

0

Dichiarazione di non responsabilità: scriverò in Python e nel contesto dello sviluppo web con Django, ma questa domanda non è linguistica né specifica di un framework.

Diciamo che ho una classe PizzaManager che funge da strumento di utilità per selezionare pizze in un menu Restaurant . Il gestore pizza utilizza i dati che restaurant deve eseguire le sue diverse azioni:

pizza_manager = PizzaManager(restaurant)

Il PizzaManager funge da filtro su un elenco specifico di pizze. Il gestore si occupa di diversi elenchi di pizze alla volta in diversi scenari. Quindi lo stesso gestore di pizze potrebbe funzionare con all_pizzas in un determinato livello e con pepperoni_pizzas in un altro stadio. Poiché la pizza sorgente è una variabile variabile, abbiamo deciso di passarla attraverso il metodo di concatenamento, piuttosto che usare un setter tradizionale:

pizza_manager.with_pizzas(all_pizzas)
pizza_manager.with_pizzas(pepperoni_pizzas)
pizza_manager.with_pizzas(pizzas_of_the_year)

Essenzialmente, il metodo with_pizzas funziona come segue:

def with_pizzas(self, pizzas):
    self.pizzas = pizzas
    return self

Come ho detto, il compito principale del manager è applicare i filtri alle pizze passate. Ad esempio, supponiamo di definire un metodo cheap_pizzas che seleziona le pizze economiche dall'elenco self.pizzas :

def cheap_pizzas():
    self.filtered_pizzas = [pizza for pizza in self.pizzas if pizza.price <= self.threshold_price]
    return self

all_pizzas = restaurant.pizza_set.all()
pizza_manager.with_pizzas(all_pizzas ).cheap_pizzas()

Possiamo continuare a filtrare concatenando un altro metodo:

pizza_manager.with_pizzas(all_pizzas).cheap_pizzas().top_pizzas()

Infine, per recuperare l'elenco delle pizze filtrate, eseguiamo solo il metodo get() :

def get(self):
    return self.pizzas

best_pizzas = pizza_manager.with_pizzas(restaurant.pizza_set.all()).cheap_pizzas().top_pizzas().get()

C'è comunque un avvertimento: non è possibile eseguire nessuno dei metodi di filtraggio a meno che non si sia passati prima nell'elenco pizza. Questo può sembrare ovvio, dal momento che il gestore non può operare su un elenco non esistente di pizze, ma questo può essere un errore quando si programma e si può commettere un errore in qualche modo facilmente. Non puoi farlo:

# we are going to get a TypeError here since self.pizzas is None at this point
PizzaManager(restaurant).cheap_pizzas()

Quindi sostanzialmente la mia domanda è come affrontare questa situazione. Come posso gestire correttamente l'accoppiamento dei metodi con i metodi concatenati allo stesso tempo? Come posso chiarire, da un punto di vista progettuale, che devi specificare l'elenco di pizze su cui il gestore sta per agire prima di eseguire uno qualsiasi dei metodi di filtraggio?

Si noti che il solo passaggio dell'elenco di pizze nel costruttore non è un'opzione valida, non voglio istanziare un singolo gestore ogni volta che voglio usare un elenco diverso di pizze.

    
posta dabadaba 29.05.2017 - 15:38
fonte

1 risposta

3

Un suggerimento comune è di non creare mai un oggetto con stato non valido. Passa l'elenco di pizze al costruttore di manager e il problema scompare.

Se i tuoi requisiti sono troppo complessi perché ciò sia possibile, allora il pattern Builder può aiutarti: usa un altro oggetto che contiene i metodi per aggiungere pizze, che poi ha un metodo di build che li copia sull'oggetto manager e lo restituisce, quindi il tuo codice cliente sarebbe simile a questo:

pizza_manager_builder()
     .with_pizzas(some_pizza_list)
     .with_pizzas (more)
     .build()
     .top_selling(5)
     .cheapest ()

L'implementazione sarebbe quindi qualcosa di simile a:

class pizza_manager_builder:
    pizza_list = []
    def with_pizzas (self, pizzas):
       self.pizza_list.extend(pizzas)
       return self
    def build (self):
       assert len(self.pizza_list) > 0, "must set up new pizza list before invoking build"
       result = pizza_manager(self.pizza_list)
       self.pizza_list = []
       return result

class pizza_manager:
    def __init__(self, pizza_list):
       self.pizza_list = pizza_list
    def top_selling (self, count):
       ...
    def cheapest (self):
       ...
    
risposta data 29.05.2017 - 15:58
fonte