Dovrei usare una classe astratta o un'interfaccia per il mio modello di dottrina?

0

In Doctrine, supponendo che voglio implementare diversi tipi di veicoli. Forse una macchina, un aereo, una bicicletta e così via ... tutti questi veicoli hanno molte proprietà diverse ma anche cose molto comuni come sono tutti fatti di un materiale.

Quindi, se voglio ottenere questo materiale ... dovrei implementare una classe astratta Vehicle con una proprietà material e implementare getter / setter o dovrei definire un'interfaccia IVehicle e definire il getter / setter in per essere sicuri che ci sia davvero un metodo per ottenere e impostare il materiale? O dovrei anche usarli entrambi in combinazione?

Sembra "professionale" utilizzare l'interfaccia ... ma è sbagliato definire getter / setter nelle interfacce, quindi la mia sensazione personale è:

Non utilizzare interface , usa solo abstract class .

Ma l'approccio di classe astratto è protetto contro l'abuso? Per esempio su un altro posto mi aspetto sicuramente un tipo Material che ritorna dalla funzione getMaterial() ... è l'approccio astratto della classe che salva anche per non restituire cose inaspettate complete (come fa l'interfaccia)?

Quindi se estendo questo veicolo per un'altra classe concreta, uno sviluppatore non dovrebbe essere in grado di restituire cose inaspettate, ma dovrebbe anche essere in grado di cambiare la logica nel metodo particolare se necessario.

    
posta Jim Panse 19.07.2018 - 10:58
fonte

2 risposte

1

Non useresti un'interfaccia a meno che tu non ne abbia assolutamente bisogno. Usando il tuo esempio di veicoli, supponendo che il tuo programma supporti Vehicle classe astratta. Le classi che ne derivano includono Car , Motorcycle , Bus , ecc. Lo usi nel tuo programma e tutto va bene.

Allora domani il tuo capo ti viene incontro e ti chiede di occuparti anche dei treni .. I treni sono diversi. Lavorano su binari, hanno fermate, non possono invertire. Esse sono implementate in modo completamente diverso dalla tua classe Vehicle abstract che consente almeno un intervallo di direzione libero e sono implementate come tali.

A questo punto è logico aggiungere un'interfaccia chiamata Transportation che si occupa di prendere qualcosa dal punto A al punto B. Ciò si adatta sia a Vehicle sia alla tua nuova classe Train . Il tuo programma preferirebbe quindi Transportation quando applicabile in tutto e lo utilizzeresti solo in termini di assunzione di una persona dal punto A al punto B.

L'interfaccia si adatta qui perché:

  1. Quello che ti serve dalla classe non è specifico per Vehicle (devi semplicemente spostarti da un punto a un altro).
  2. L'implementazione di una classe è completamente diversa dall'implementazione di un'altra.

Inutile dire che se solo hai un'implementazione (vale a dire, prima della necessità di creare la classe Train ), allora una classe Transportation è superflua e non necessaria. Aggiunge solo alla complessità del tuo programma senza alcun beneficio evidente.

In altre parole, non si tratta di guardare professionale o no. Scrivi il programma in modo tale da ridurre al minimo la complessità ogni volta che è possibile. Le interfacce sono una complicazione accettabile del tuo programma nel caso in cui si adatti ai due punti precedenti. Altrimenti, non aver paura di usare invece la classe reale o astratta.

    
risposta data 19.07.2018 - 13:58
fonte
1

La regola d'oro è favorire la composizione sull'eredità . Ciò significa che non dovresti usare la classe base astratta.

Nonostante ciò devi distinguere tra "Oggetti dati" e Oggetti che forniscono "comportamento aziendale".

Le classi di dati dovrebbero avere (quasi) nessuna logica. Forniscono l'accesso ai valori contenuti tramite il metodo getter / setter. Le classi di attività non dovrebbero esporre i loro dettagli di implementazione e quindi non hanno alcun getter / setter, solo metodi per applicare il comportamento aziendale agli oggetti dati passati come parametri o mantenuti come stati interni.

Gli oggetti composti potrebbero non esporre i loro componenti.
Un esempio prominente è Point2D e Point3D . Questo esempio viene in genere utilizzato per introdurre l'ereditarietà, ma quando Point3D estende Point2D fallisce il contratto equals poiché point2D.equals(point3D) è true (con la maggior parte delle implementazioni basate sull'ereditarietà) mentre reverse point3D.equals(point2D) è false.
( uguale al contratto richiede che entrambi falliscano o passino per gli stessi due oggetti.)

Ma un'implementazione basata sulla composizione di Point3D non dovrebbe avere un getter per il componente Poind2D ma dovrebbe delegare l'accesso agli attributi Point2D.

Conclusione

  • L'ereditarietà deve essere utilizzata con le interfacce.
  • le classi concrete dovrebbero essere composte.
  • Mai classi di business che espongono le loro variabili membro (né per accesso diretto né tramite getter / setter)
  • Pensa attentamente se classi di dati devono esporre le loro variabili membro o se preferiscono delegare l'accesso.
risposta data 19.07.2018 - 12:40
fonte

Leggi altre domande sui tag