Entità logiche vs fisiche nella progettazione della classe OO

1

Passando alle classi CS OOP un paio di anni fa, gli esempi che hanno sempre utilizzato per gli esempi di classe OO erano gli esempi standard di auto / pneumatico / motore e forma.

Mi sono seduto con il libro "Agile Principles, Patterns and Practices in C #" e l'esempio Coffee Maker ha tentato di cambiare la mia visione del design della classe OO. Il libro diceva che le classi NON dovrebbero essere oggetti fisici come una macchina o un pneumatico, ma dovrebbero rappresentare un comportamento.

L'esempio (cattivo) che ha dato è stato un punto di vista di un programmatore intermedio su come progettare classi correlate a una macchina per caffè (e esattamente come l'avrei progettata). Alla fine ha continuato a creare meno classi come CoffeeMakerUI, HotWaterSource e ContainmentVessel (nessuna delle quali rappresenta alcuna parte fisica di una vera caffettiera).

Ora mi sono bloccato chiedendomi se ho pensato al design di OO in modo errato in tutti questi anni e come posso pensare in modo più astratto quando disegno le mie classi. Il comportamento dovrebbe guidare come disegno le mie classi?

    
posta keelerjr12 07.11.2017 - 16:56
fonte

3 risposte

6

C'è nessun consenso su cosa distingue esattamente la programmazione orientata agli oggetti.

Storicamente, OOP è stato dapprima "scoperto" nel contesto delle simulazioni, in cui gli oggetti rappresentavano letteralmente oggetti e metodi fisici su questi oggetti rappresentavano eventi esterni a cui gli oggetti possono reagire. Il consenso generale è che questo non è un buon modo per scrivere software, ma che i principi sottostanti possono essere molto preziosi.

Un problema con la modellazione di oggetti fisici è che ciò induce in errore il design a includere relazioni irrilevanti tra oggetti e proprietà irrilevanti degli oggetti. Inoltre, spesso è più utile modellare concetti astratti anziché oggetti fisici.

Una visione più moderna non vede OOP come una tecnica per tradurre i modelli in codice, ma vede l'orientamento agli oggetti come un modo per risolvere vari problemi attraverso il polimorfismo , principalmente l'ereditarietà dell'interfaccia.

Questo di solito è un approccio dal basso verso l'alto: ho cose diverse che si comportano diversamente, ma voglio usarle come se fossero lo stesso tipo di cosa. Senza OOP, devo esaminare ogni cosa per scoprire di cosa si tratta, quindi eseguire il comportamento corretto.

Invece, posso definire un'interfaccia che descriva come voglio usarli. Quindi renderò le mie cose esistenti conformi a questa interfaccia, sia se ereditano l'interfaccia o se creo un adattatore tra l'interfaccia e una cosa specifica. Ora posso dire le cose cosa fare e possono eseguire il loro comportamento individuale senza che io conosca il loro vero tipo, perché sono conformi all'interfaccia.

Si scopre che l'OOP è un modo eccellente per ottenere modularità ed estensibilità. Dipendendo solo dalle interfacce invece che dai tipi concreti, posso sostituire le implementazioni senza ulteriori modifiche al sistema (spesso attraverso un meccanismo di "dipendenza dall'iniezione"). Una conseguenza è che i sistemi orientati agli oggetti sono più facili da testare in isolamento rispetto ai sistemi procedurali, perché posso facilmente sostituire le dipendenze reali con stub o oggetti fittizi.

Non tutti i sistemi software richiedono questo tipo di flessibilità. Molti problemi si adattano molto bene ai modelli non polimorfici. Molto spesso, un class non viene usato perché vogliamo fare OOP, ma perché questo è l'unico meccanismo nel linguaggio per implementare tipi di record . La maggior parte delle chiamate di metodo instance.Method() non sono polimorfiche, ma solo una comoda abbreviazione di ciò che è effettivamente una funzione statica Class.Method(instance) .

Oltre ai "Modelli OOP nel mondo reale" e "OOP utilizza tecniche polimorfiche", esistono anche altre interpretazioni OOP:

  • OOP è definito da Ereditarietà e incapsulamento (spesso insegnato in introduzioni alla programmazione, ma manca il punto)
  • Oggetti = Dati + Comportamento
  • Gli oggetti usano aprire la ricorsione (B. Pierce)
  • I sistemi orientati agli oggetti sono SOLID (Robert C. Martin)
  • Gli oggetti comunicano attraverso il passaggio dei messaggi (Alan Kay)
risposta data 07.11.2017 - 18:03
fonte
4

Prima di tutto, pensa al dominio concreto che cerchi di modellare. Se stai modellando un dominio della fabbrica di caffettiere, devi conoscere tutti i suoi interni e ci sono buone probabilità che il tuo design possa mappare 1: 1 sul design fisico di una caffettiera.

Se il tuo dominio sull'avere un caffè, perché devi essere consapevole di tutti i suoi interni? Tutto quello su cui dovresti concentrarti è solo un processo per preparare il caffè. Allora, qual è la storia del design del tuo dominio? Di ', potrebbe il seguente:

Every day I go to work. I start my day with having some coffee. To do that, I sometimes need to clean a dispenser. When there is no water, I need to pour it in. If there is no coffee -- I need to fill my coffee maker with it as well. Than I need to put a cup, choose my beverage and push the button.

Quindi, avendo questa storia del design, decidi cosa modellino e cosa lasci dietro. È probabile che contenga alcune responsabilità di base. Probabilmente potresti iniziare con loro, probabilmente potresti iniziare con l'identificazione degli oggetti candidati.

Iniziando con le responsabilità, potresti avere:

  • capacità di riempire la caffettiera con acqua (in questo momento non penso a la cui responsabilità è: un uomo o una caffettiera)
  • capacità di riempirlo con il caffè
  • possibilità di scegliere una bevanda
  • capacità di versarlo in una tazza

Ok, uno per uno. Chi riempirà una caffettiera con il caffè? Primo punto: a quest'ora, non ci sono responsabilità esplicite di un essere umano. Quindi è probabile che non ne abbiamo bisogno. Secondo punto: il pensiero degli oggetti implica che gli oggetti siano sistemi intelligenti e autonomi, che fanno cose a se stessi. Quindi l'oggetto candidato per riempire una caffettiera con il caffè non è un mitico CoffeeService , è una caffettiera stessa. Lo stesso vale per la scelta delle bevande e il riempimento con acqua. Ma ora vedo che ci sono troppe responsabilità assegnate a una caffettiera. Quindi guarda più da vicino. Chi partecipa esattamente al rifornimento di caffè? È una nave da caffè. Grande, la caffettiera è una responsabilità in meno. Seguendo lo stesso principio possiamo ottenere un oggetto Dispenser, ContainmentVessel e UI. E la parte più divertente è che non abbiamo bisogno di un oggetto Coffee-maker. Probabilmente come structurer solo.

Si potrebbe ottenere lo stesso risultato a partire da oggetti candidati - basta vedere quali parti forniscono realmente un certo servizio. Fornire un servizio è una responsabilità primaria in qualsiasi classe.

Ci sono alcuni altri modi per pensare a decomporre il tuo dominio. Ma il miglior consiglio che mi permetto di dare è di pensare prima a un dominio.

    
risposta data 07.11.2017 - 19:12
fonte
0

L'attenzione dovrebbe essere posta sulle responsabilità, inoltre ricorda che non stai provando a modellare un 'Coffee maker' stai creando un modello software che potrebbe includere più classi astratte come fabbriche o classi di strategia che non hanno nulla a che fare con il dominio di 'caffettiera' ma sono necessari per il codice mantenibile.

    
risposta data 07.11.2017 - 17:17
fonte

Leggi altre domande sui tag