Schema della strategia e funzionalità ereditaria della piastra

1

Ho letto che quando si utilizza il modello di strategia, è necessario attenersi alla composizione piuttosto che all'ereditarietà. Ma questo significa che qualsiasi eredità è una cattiva pratica? Se c'è una funzionalità da condividere tra strategie simili è anacronistico implementare una classe base per incapsulare quella funzionalità? Di seguito è riportato un esempio che potrebbe essere migliore, ma spero che illustri la mia domanda.

from abc import ABC, abstractmethod


class AdditionStrategyInterface(ABC):    
    @abstractmethod
    def add(self, num1,  num2):
        raise NotImplementedError


class AdditionStrategy(AdditionStrategyInterface):
    def add(self, num1, num2):
        return num1 + num2


class AdditionPlusFourStrategyBase(AdditionStrategyInterface, ABC):
    def add_four(self, num):
        return num + 4

    @abstractmethod
    def add(self, num1, num2):
        raise NotImplementedError


class AdditionPlusFourStrategy(AdditionPlusFourStrategyBase):
    def add(self, num1, num2):
        return self.add_four(num1 + num2)


class AdditionPlusEightStrategy(AdditionPlusFourStrategyBase):
    def add(self, num1, num2):
        return self.add_four(self.add_four(num1 + num2))
    
posta Allie Fitter 09.03.2018 - 18:36
fonte

3 risposte

2

Non esiste una regola d'oro per questo - e difficilmente una regola empirica per rispondere a questa domanda in modo accurato. Come sempre, dipende completamente dal problema che intendi risolvere e dalla durata della soluzione.

Il motivo alla base dell'ereditarietà è di condividere comportamenti comuni tra tipi di oggetti comuni . Se hai cose in cui hai piccole differenze nel comportamento overal, non c'è ragione di non usare inheritance . Questa è stata la ragione per cui è stata inventata l'eredità.

Ma l'ereditarietà ha un costo:

Come già detto, ha senso se si ha un insieme di oggetti, che sono essenzialmente uguali; ma se costruisci catene di eredità complesse, esiste la possibilità che tu erediti il comportamento, che è in basso nella catena non desiderato. O più spesso finisci per eseguire il debug della catena di ereditarietà, perché non hai capito perché il comportamento è così com'è: in modo inaspettato.

Questo rende l'ereditarietà per alcuni scenari non così favorevoli.

D'altro canto strategy è sempre interessante quando si hanno oggetti che non sono essenzialmente uguali, ma condividono un tipo di comportamento comune.

Se lo fai, modelli di ereditarietà nouns e strategia verbs .

Quando si esaminano linguaggi come Java, si implementano strategie con interfaces che spiega, quale "tipo" di comportamento è previsto: cos'è il contratto . Non c'è nulla di simile in Python però.

»Ma come si inserisce composizione nell'immagine?« potresti chiedere.

Per scambiare facilmente il comportamento e aderire al principio di apertura chiusa , si compongono comportamenti. La tua classe è aperta a ricevere nuovi tipi di comportamento, ma è chiusa a se stessa.

Se implementi il modello di strategia, non hai gerarchie e nessun problema di comportamento indesiderato, perché inserisci ciò di cui hai bisogno e non viene spinto con un comportamento.

Detto questo, ci possono essere casi in cui ci sono un sacco di oggetti che sono essenzialmente uguali ma hanno aspetti, che sono meglio modellati come strategie. Lì una miscela di entrambi sarebbe perfetta.

La cosa difficile della programmazione è che ci sono pochissimi dos e dont's ma il più delle volte si hanno cose, che sono piuttosto buone in qualche contesto.

    
risposta data 09.03.2018 - 21:33
fonte
2

Il consiglio popolare non è "attaccare alla composizione" è "preferisci la composizione all'eredità". Questo non è qualcosa che puoi seguire senza pensarci. Devi davvero capire perché lo stai facendo prima di farlo.

L'ereditarietà presenta vantaggi ma comporta molti problemi. Il grande vantaggio è che non richiede la delega per funzionare come fa la composizione. Ciò significa meno codice standard. Ciò significa meno battitura sulla tastiera.

Ma significa anche che sei a rischio del yo-yo problema . L'ereditarietà tende a generare un'eredità così presto si ha una lunga catena che costringe i lettori del codice a rimbalzare su e giù cercando di seguire il comportamento del programma.

Inoltre, l'ereditarietà si insinua nella backdoor. Consente ai programmatori di presumere di avere una conoscenza approfondita del genitore da cui ereditano. Ciò incoraggia l'accoppiamento oltre a ciò che la delega farebbe in quanto si limiterebbe a utilizzare l'interfaccia normale.

La composizione utilizzata con la delega fornisce il polimorfismo come l'ereditarietà ma incoraggia il codice più flessibile. Ecco perché a volte è preferibile.

Ora non è che mi rifiuto di usare mai l'ereditarietà. Uno dei miei usi preferiti è dare nuovi nomi di eccezioni:

class MyError(Exception):
    pass
    
risposta data 09.03.2018 - 20:26
fonte
1

Il modello di strategia è in realtà un'applicazione di composizione sull'ereditarietà: context usa la composizione:

  • questo permette di cambiare dinamicamente la strategia a cui si riferisce.
  • questo permette anche di usare diverse strategie non correlate, e gestirle separatamente, rafforzando così separazione delle preoccupazioni

Il principio di composizione sull'ereditarietà è una raccomandazione generale, perché si è tentati di riutilizzare il codice della classe X in classe Y usando l'ereditarietà, che può essere una pessima idea se una Y non sia realmente una X.

Il particolare rischio legato all'ereditarietà eccessiva nel caso del modello strategico consiste nel combinare con l'ereditarietà diverse strategie che in realtà dovrebbero rimanere indipendenti. Ciò rende il codice più rigido e l'evoluzione del codice più difficile (esplosione combinatoria di classi, codice ridondante, ecc ...).

La composizione sull'eredità è tuttavia una raccomandazione, e non un comando divino. Quindi, se vedi che un algoritmo incapsulato in una strategia richiede una leggera variazione, dovresti chiederti se non è in realtà il sintomo che dovrebbe appartenere a una strategia indipendente. Ma se dopo un'attenta analisi concludi che non lo è, e che l'eredità sarebbe davvero giustificata, allora la userai.

Esempio illustrativo sciocco:

interface FamilyAStrategy { ... }; 
class A1Strategy implements FamilyAStrategy { ... }; 
class A2Strategy implements FamilyAStrategy { ... }; 

interface FamilyBStrategy { ... }; 
class B1Strategy implements FamilyBStrategy { ... }; 
class B2Strategy implements FamilyBStrategy { ... }; 

// context can combine several families strategies:  
class EventProcessor {
     AStrategy as; 
     BStrategy bs;        
     ...
};

Se si è tentati di utilizzare l'ereditarietà:

interface SStrategy { ... }; 
class A1SStrategy implements SStrategy { ... }; 
class A1B1Strategy extends A1SStrategy { ... }; 
class A1B2SStrategy extends A1SStrategy { ... }; 
... // the rest is too awful that I expose it to your sight ;-)  
    
risposta data 09.03.2018 - 22:21
fonte

Leggi altre domande sui tag