La composizione può violare l'O in SOLID

0

Ero leggendo sulla composizione e mi chiedo se viola l'O in SOLID e cosa dovrebbe essere fatto per risolvere la situazione. Nell'articolo l'autore usa un frutto e una mela. Alla fine lo scrittore ammette che sarebbe meglio come una relazione "è-a" piuttosto che "ha-a".

Supponiamo tuttavia che tu abbia deciso di mantenere la composizione e hai costruito la tua classe Fruit in questo modo:

class Fruit {
    public void peel(){
      //code to peel fruit.
    }
}

e Apple:

public void peel() {
    fruit.peel();
}

Se aggiornassi la mia classe Fruit, ad esempio, un metodo chiamato eat , dovrei quindi aggiornare anche la mia classe Apple per aggiornare il nuovo metodo eat.

Domande:

  1. Questo viola l'O è SOLIDO? Per me, lo fa perché ogni volta che Fruit cambia, devi aggiornare Apple per implementarlo.
  2. Fornire un getter per Fruit e quindi chiamare il metodo richiesto è meglio? Se fossi determinato a far funzionare la composizione, come gestiresti gli aggiornamenti?

Esempio:

di Apple:

  public Fruit getFruit(){
       return this.fruit;
  }

Cliente:

Fruit fruit = apple.getFruit();
fruit.eat();

Idealmente lo si ristrongrà ad una relazione is-a e indipendentemente dal tipo di frutto, la sottoclasse può sovrascrivere eat() .

    
posta Vincent Savard 29.10.2017 - 20:35
fonte

3 risposte

6

Il principio O / C parla della fragilità del modulo o dell'interfaccia - passa alla tensione tra il voler introdurre nuove funzionalità e fare una modifica che interrompe clienti esistenti e consumatori che li costringono a fare anche un cambio di codice corrispondente o subiscono una rottura, come forse non la compilazione o la mancata esecuzione.

O / C ci dice di considerare la protezione delle basi di codice esistenti in modo che, senza modifiche, funzionino come sono state progettate originariamente anche in presenza di altri elementi del sistema che si sta aggiornando.

To me, it does because every time the Fruit changes, you have to update Apple to implement it.

Al contrario, lo scenario riguarda l'upgrade delle funzionalità: desideri specificamente che i client e i consumatori utilizzino le nuove funzionalità offerte come nuovi metodi (quindi devono essere modificati per definizione).

L'ammonimento del principio O / C non si applica, in quanto questa situazione non riguarda la protezione da una base di codice esistente da rottura senza modifiche comparabili, ma piuttosto quello che serve per offrire o esporre nuove funzionalità ai consumatori finali o clienti.

Nel tuo scenario, ottenere / utilizzare nuove funzionalità (e propagare questo attraverso la base di codice) è la preoccupazione piuttosto che la preoccupazione di rompere il codice esistente per funzionare così come avviene quando una parte del sistema cambia offrendo più funzionalità.

    
risposta data 29.10.2017 - 21:49
fonte
1

If I updated my Fruit class, for example, a method called eat, I would then have to also update my Apple class to accomdate the new eat method.

Perché dovresti avere per aggiornare Apply per soddisfare il nuovo metodo di mangiare? Il tipo Apple non è associato a Fruit , quindi l'unica volta che devi cambiare Apple è quando cambia la firma di peel .

Does this violate the O is SOLID? To me, it does because every time the Fruit changes, you have to update Apple to implement it.

Anche in questo caso, è necessario aggiornare Apple se è ereditato da Fruit . Poiché non c'è eredità, non devi aggiornare Apple ogni tempo Fruit modifiche.

Would providing a getter for the Fruit and then calling the required method be better? If you were determined to make composition work, how would you handle updates?

Assolutamente no. Lo scopo alla base della composizione è quello di nascondere i collaboratori. I client che utilizzano Apple non dovrebbero sapere o preoccuparsi del fatto che Apple abbia un'istanza di Fruit .

At the end the writer admits it would be better as a "is-a" relationship rather than "has-a".

Penso che questo possa spiegare parte della tua confusione. Apple e Fruit è un esempio scarso perché nel mondo reale una mela è un frutto. Una mela non usa un frutto per sbucciarsi. Permettetemi di proporre un esempio diverso. Abbiamo Apple e ApplePeeler dove ApplePealer assomiglia a questo:

interface ApplePeeler {
    void peel(Apple apple);
}

Ora ha (in qualche modo) più senso per Apple per avere un ApplePeeler e usarlo per sbucciarsi stesso

class Apple {
    private ApplePeeler peeler;

    public peel() {
        peeler.peel(this);
    }
}

Se aggiungo un nuovo metodo a ApplePeeler , non devo cambiare Apple , ma se cambio la firma di ApplePeeler.peel può rompere Apple .

Andando oltre, non ha senso che una mela si sbucci da sola, quindi non definirei peel su Apple . La composizione si verificherebbe dove un Apple deve essere sbucciato.

class Apple {
}

class AppleSauceMaker {
    private ApplePeeler peeler;
    private ApplePress press;

    Sauce makeSauce(Apple apple) {
        return press.press(peeler.peel(apple));
    }
}
    
risposta data 29.10.2017 - 21:01
fonte
-1

If I add a new method to ApplePeeler, I don't have to change Apple,

Devi cambiare Apple per accettare il nuovo metodo. Quando si implementa l'interfaccia, è necessario implementare tutti i suoi metodi. Ma sono d'accordo con te in senso lato.

:)

    
risposta data 29.10.2017 - 22:14
fonte

Leggi altre domande sui tag