Cosa significa quando si dice "Incapsula ciò che varia"?


25

Uno dei principi OOP che ho incontrato è: -Incapsulare ciò che varia.

Capisco qual è il significato letterale della frase, cioè nascondere ciò che varia. Tuttavia, non so come contribuirebbe esattamente a un design migliore. Qualcuno può spiegarlo usando un buon esempio?


Vedi en.wikipedia.org/wiki/Encapsulation_(computer_programming) che lo spiega bene. Penso che "ciò che varia" non sia corretto poiché a volte dovresti anche incapsulare le costanti.
qwerty_so,

I don't know how exactly would it contribute to a better designI dettagli incapsulanti riguardano l'accoppiamento libero tra il "modello" e i dettagli di implementazione. Meno è legato il "modello" ai dettagli di implementazione, più flessibile è la soluzione. E rende più semplice evolverlo. "Estratti dai dettagli".
Laiv,

@Laiv Quindi "variabile" si riferisce a ciò che si evolve nel corso del ciclo di vita del software o a ciò che cambia durante l'esecuzione del programma o entrambi?
Haris Ghauri,

2
@HarisGhauri entrambi. Raggruppa ciò che varia insieme. Isolare ciò che varia in modo indipendente. Diffidare di ciò che presumi non cambierà mai.
candied_orange,

1
@laiv pensa che "astratto" sia un buon punto. Può sentirsi schiacciante farlo. In ogni singolo oggetto hai una responsabilità. La cosa bella è che devi solo pensare attentamente a quell'unica cosa qui. Quando i dettagli del resto del problema sono problemi di qualcun altro, questo semplifica le cose.
candied_orange

Risposte:


30

Puoi scrivere un codice simile al seguente:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

oppure puoi scrivere un codice simile al seguente:

pet.speak();

Se ciò che varia è incapsulato, non devi preoccuparti. Ti preoccupi solo di ciò di cui hai bisogno e qualunque cosa tu stia usando, scopri come fare ciò di cui hai veramente bisogno in base a ciò che varia.

Incapsula ciò che varia e non devi diffondere il codice che si preoccupa di ciò che varia. Devi solo impostare l'animale domestico come un certo tipo che sa parlare come quel tipo e dopo puoi dimenticare quale tipo e trattarlo come un animale domestico. Non devi chiedere quale tipo.

Potresti pensare che il tipo sia incapsulato perché è necessario un getter per accedervi. Io non. Getter non si incapsula davvero. Tacchettano quando qualcuno rompe l'incapsulamento. Sono un simpatico decoratore come l'hook orientato all'aspetto che viene spesso utilizzato come codice di debug. Non importa come lo tagli, stai ancora esponendo il tipo.

Potresti guardare questo esempio e pensare che sto fondendo polimorfismo e incapsulamento. Non sono. Sto combinando "ciò che varia" e "i dettagli".

Il fatto che il tuo animale domestico sia un cane è un dettaglio. Uno che potrebbe variare per te. Uno che potrebbe non farlo. Ma sicuramente uno che potrebbe variare da persona a persona. A meno che non crediamo che questo software sarà mai usato solo dagli amanti dei cani, è intelligente trattare il cane come un dettaglio e incapsularlo. In questo modo alcune parti del sistema sono beatamente inconsapevoli del cane e non saranno influenzate quando ci uniamo con "i pappagalli siamo noi".

Disaccoppia, separa e nascondi i dettagli dal resto del codice. Non lasciare che la conoscenza dei dettagli si diffonda nel tuo sistema e seguirai "incapsulare ciò che varia" bene.


3
È davvero strano. "Incapsulare ciò che varia" per me significa nascondere i cambiamenti di stato, ad esempio non avere mai variabili globali. Ma anche la tua risposta ha un senso, anche se sembra più una risposta al polimorfismo che l'incapsulamento :)
David Arno,

2
@DavidArno Il polimorfismo è un modo per farlo funzionare. Avrei potuto solo trasformare la struttura if in un animale domestico e le cose sarebbero andate bene qui grazie all'incapsulamento dell'animale domestico. Ma questo avrebbe semplicemente spostato il disordine invece di ripulirlo.
candied_orange,

1
"Incapsulare ciò che varia" per me significa nascondere i cambiamenti di stato . No, no. Mi piace il commento di CO. La risposta di Derick Elkin va più in profondità, leggila più di una volta. Come diceva @JacquesB "Questo principio è in realtà abbastanza profondo"
radarbob il

16

"Varia" in questo caso significa che "può cambiare nel tempo a causa del cambiamento dei requisiti". Questo è un principio progettuale fondamentale: separare e isolare parti di codice o dati che potrebbero dover cambiare separatamente in futuro. Se un singolo requisito cambia, idealmente dovrebbe richiederci di modificare il codice relativo in un unico posto. Ma se la base di codice è mal progettata, cioè altamente interconnessa e la logica per il requisito è diffusa in molti luoghi, allora il cambiamento sarà difficile e rischierebbe di causare effetti inaspettati.

Supponi di avere un'applicazione che utilizza il calcolo dell'imposta sulle vendite in molti luoghi. Se l'aliquota dell'imposta sulle vendite cambia, cosa preferiresti:

  • l'aliquota dell'imposta sulle vendite è un valore letterale codificato ovunque nell'applicazione in cui viene calcolata l'imposta sulle vendite.

  • l'aliquota dell'imposta sulle vendite è una costante globale, che viene utilizzata ovunque nell'applicazione in cui viene calcolata l'imposta sulle vendite.

  • esiste un unico metodo chiamato calculateSalesTax(product)che è l'unico posto in cui viene utilizzata l'aliquota dell'imposta sulle vendite.

  • l'aliquota dell'imposta sulle vendite è specificata in un file di configurazione o campo del database.

Poiché l'aliquota dell'imposta sulle vendite può variare a causa di una decisione politica indipendente da altri requisiti, preferiamo averla isolata in una configurazione, in modo che possa essere modificata senza influire su alcun codice. Ma è anche ipotizzabile che la logica per il calcolo dell'imposta sulle vendite possa cambiare, ad esempio aliquote diverse per prodotto diverso, quindi ci piace anche incapsulare la logica di calcolo. La costante globale potrebbe sembrare una buona idea, ma in realtà è cattiva, poiché potrebbe incoraggiare a utilizzare l'imposta sulle vendite in diversi punti del programma piuttosto che in un unico posto.

Ora considera un'altra costante, Pi, che è anche usata in molti punti del codice. Lo stesso principio di progettazione vale? No, perché Pi non cambierà. L'estrazione in un file di configurazione o in un campo di database introduce solo complessità inutili (e tutto il resto è uguale, preferiamo il codice più semplice). Ha senso renderlo una costante globale anziché codificarla in più punti per evitare incoerenze e migliorare la leggibilità.

Il punto è, se guardiamo solo a come funziona il programma ora , l'aliquota dell'imposta sulle vendite e Pi sono equivalenti, entrambe sono costanti. Solo quando consideriamo ciò che potrebbe variare in futuro , ci rendiamo conto che dobbiamo trattarli in modo diverso nel design.

Questo principio è in realtà abbastanza profondo, perché significa che devi guardare oltre ciò che la base di codice dovrebbe fare oggi , e anche considerare le forze esterne che possono causarne il cambiamento e persino comprendere le diverse parti interessate dietro i requisiti.


2
Le tasse sono un buon esempio. I calcoli di leggi e tasse potrebbero cambiare da un giorno all'altro. Se stai implementando un sistema di dichiarazione delle tasse sei fortemente legato a questo tipo di modifiche. Cambia anche da un locale all'altro (paesi, province, ...)
Laiv

"Pi non cambierà" mi ha fatto ridere. È vero, è improbabile che Pi cambi, ma supponi che non ti sia permesso di usarlo più? Se alcune persone lo fanno, Pi sarà deprecato. Supponiamo che diventi un requisito? Spero che tu abbia un felice giorno Tau . Bella risposta A proposito. Davvero profondo.
candied_orange,

14

Entrambe le risposte attuali sembrano colpire solo parzialmente il segno e si concentrano su esempi che offuscano l'idea di base. Anche questo non è (esclusivamente) un principio OOP ma un principio di progettazione del software in generale.

La cosa che "varia" in questa frase è il codice. Christophe è sul punto di dire che di solito è qualcosa che può variare, cioè spesso lo prevedi . L'obiettivo è proteggersi da future modifiche al codice. Questo è strettamente correlato alla programmazione contro un'interfaccia . Tuttavia, Christophe non è corretto nel limitare i "dettagli di implementazione". In effetti, il valore di questo consiglio è spesso dovuto a cambiamenti nei requisiti .

Questo è solo indirettamente correlato allo stato incapsulante, che è ciò a cui penso David Arno stia pensando. Questo consiglio non sempre (ma spesso) suggerisce uno stato incapsulante, e questo consiglio si applica anche agli oggetti immutabili. In effetti, semplicemente nominare le costanti è una forma (molto basilare) di incapsulare ciò che varia.

CandiedOrange fonde esplicitamente "ciò che varia" con "dettagli". Questo è solo parzialmente corretto. Concordo sul fatto che qualsiasi codice che varia è in qualche modo "dettagli", ma un "dettaglio" non può variare (a meno che non si definiscano "dettagli" per rendere questo tautologico). Ci possono essere motivi per incapsulare dettagli non variabili, ma questo detto non è uno. In parole povere, se eri sicuro che "cane", "gatto" e "anatra" sarebbero stati gli unici tipi con cui avresti mai avuto a che fare, allora questo detto non suggerisce il refactoring eseguito da CandiedOrange.

Trasmettendo l'esempio di CandiedOrange in un contesto diverso, supponiamo di avere un linguaggio procedurale come C. Se ho un codice che contiene:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Posso ragionevolmente aspettarmi che questo pezzo di codice cambierà in futuro. Posso "incapsularlo" semplicemente definendo una nuova procedura:

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

e utilizzando questa nuova procedura invece del blocco di codice (ovvero un refactoring "metodo di estrazione"). A questo punto l'aggiunta di un tipo "mucca" o qualsiasi altra cosa richiede solo l'aggiornamento della speakprocedura. Naturalmente, in una lingua OO puoi invece sfruttare l'invio dinamico come indicato dalla risposta di CandiedOrange. Questo accadrà naturalmente se accedi pettramite un'interfaccia. L'eliminazione della logica condizionale tramite invio dinamico è una preoccupazione ortogonale che faceva parte del motivo per cui ho realizzato questa interpretazione procedurale. Voglio anche sottolineare che ciò non richiede funzionalità particolari di OOP. Anche in un linguaggio OO, incapsulare ciò che varia non significa necessariamente che è necessario creare una nuova classe o interfaccia.

Come esempio più archetipico (che è più vicino ma non del tutto OO), diciamo che vogliamo rimuovere i duplicati da un elenco. Diciamo che lo implementiamo ripetendo l'elenco tenendo traccia degli elementi che abbiamo visto finora in un altro elenco e rimuovendo tutti gli elementi che abbiamo visto. È ragionevole supporre che potremmo voler cambiare il modo in cui tenere traccia degli oggetti visti può, almeno, per motivi di prestazioni. Il dettato per incapsulare ciò che varia suggerisce che dovremmo costruire un tipo di dati astratto per rappresentare l'insieme di oggetti visti. Il nostro algoritmo è ora definito rispetto a questo tipo di dati Set astratto e se decidiamo di passare a un albero di ricerca binario, il nostro algoritmo non deve cambiare o preoccuparsi. In un linguaggio OO, potremmo utilizzare una classe o un'interfaccia per acquisire questo tipo di dati astratto. In una lingua come SML / O '

Per un esempio basato sui requisiti, supponiamo che sia necessario convalidare un campo per quanto riguarda alcune logiche aziendali. Anche se ora potresti avere requisiti specifici, sospetti fortemente che si evolveranno. È possibile incapsulare la logica corrente nella propria procedura / funzione / regola / classe.

Sebbene questa sia una preoccupazione ortogonale che non fa parte di "incapsulare ciò che varia", è spesso naturale sottrarre, cioè parametrizzare, la logica ora incapsulata. Questo in genere porta a un codice più flessibile e consente di modificare la logica sostituendo in un'implementazione alternativa anziché modificare la logica incapsulata.


Oh dolce amara ironia. Sì, questo non è solo un problema OOP. Mi hai sorpreso a lasciare che un dettaglio del paradigma del linguaggio inquinasse la mia risposta e mi puniva giustamente "variando" il paradigma.
candied_orange,

"Anche in un linguaggio OO, incapsulare ciò che varia non significa necessariamente che debba essere creata una nuova classe o interfaccia" - è difficile immaginare una situazione in cui non creare una nuova classe o interfaccia non violerebbe SRP
taurelas

11

"Incapsula ciò che varia" si riferisce al nascondere i dettagli di implementazione che possono cambiare ed evolversi.

Esempio:

Ad esempio, supponiamo che la classe Coursetenga traccia di Studentsciò che può register (). È possibile implementarlo con a LinkedListed esporre il contenitore per consentire l'iterazione su di esso:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Ma questa non è una buona idea:

  • Innanzitutto, le persone potrebbero non avere un buon comportamento e usarlo come self-service, aggiungendo direttamente gli studenti all'elenco, senza passare attraverso il metodo register ().
  • Ma ancora più fastidioso: questo crea una dipendenza del "usando il codice" dai dettagli di implementazione interna della classe utilizzata. Ciò potrebbe impedire future evoluzioni della classe, ad esempio se si preferisce utilizzare un array, un vettore, una mappa con il numero di posto o la propria struttura di dati persistente.

Se incapsuli ciò che varia (o piuttosto, ciò che potrebbe variare), mantieni la libertà sia per il codice che per l'uso che per la classe incapsulata di evolversi da soli. Ecco perché è un principio importante in OOP.

Letture addizionali:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.