classDiagram
class Llibre {
-String titol
-String autor
-boolean prestat
+prestar() void
+retornar() void
+getTitol() String
}
13 Relacions entre classes
Objectius
- Entendre que els programes reals es fan amb moltes classes que col·laboren.
- Distingir entre associació, agregació i composició.
- Representar classes i les seves relacions amb diagrames UML de classes.
- Interpretar i escriure la multiplicitat d’una relació (1, *, 1..*).
- Traduir un diagrama UML a codi Java i viceversa.
- Aplicar el principi de disseny modular: baix acoblament i alta cohesió.
Ja sabem construir classes sòlides i ben encapsulades. Però una sola classe poques vegades resol un problema real. Una biblioteca no és només Llibre: també hi ha Soci, Prestec, Biblioteca… i totes es relacionen i col·laboren entre elles.
13.1 Per què cal relacionar classes?
Imagina que vols modelar una biblioteca amb una única classe gegant que ho contingui tot: títols, socis, préstecs, dates… Seria un monstre impossible de mantenir. El bon disseny orientat a objectes reparteix les responsabilitats: cada classe fa una cosa i la fa bé, i les classes es passen feina les unes a les altres.
Un restaurant no té una sola persona que ho faci tot. Hi ha el xef, el cambrer, el rentaplats… Cadascú té la seva responsabilitat i col·labora amb els altres: el cambrer usa la comanda, la cuina conté els seus fogons, el restaurant té treballadors. Les classes d’un programa s’organitzen igual.
La relació més bàsica és que un objecte contingui una referència a un altre objecte com a atribut.
Per exemple, un préstec disposa d’informació sobre el llibre i el soci que el té:
public class Prestec {
private Llibre llibre; // Un atribut que és un altre objecte
private Soci soci;
private LocalDate dataInici;
public Prestec(Llibre llibre, Soci soci) {
this.llibre = llibre;
this.soci = soci;
this.dataInici = LocalDate.now();
}
}A partir d’aquí, distingirem tres graus d’aquesta relació segons com de fort sigui el lligam:
- Associació
- Agregació
- Composició
13.2 UML: el llenguatge dels diagrames de classes
Abans d’entrar en els tipus de relació, necessitem una manera de representar-les gràficament o dibuixar-les. L’estàndard és l’UML (Unified Modeling Language).
Un diagrama de classes representa cada classe com una caixa de tres compartiments:
- A dalt, el nom de la classe.
- Al mig, els atributs (amb
-privat,+públic). - A baix, els mètodes.
Els símbols de visibilitat que ja coneixes:
| Símbol UML | Visibilitat Java |
|---|---|
+ |
public |
- |
private |
# |
protected |
~ |
De paquet. Cap modificador |
En un diagrama UML sovint només s’inclouen els atributs i mètodes rellevants per a allò que vols explicar.
Un diagrama net que comunica una idea val més que un de sobrecarregat amb tots els getters i setters.
13.3 Associació
L’associació és la relació més feble: un objecte en coneix o utilitza un altre, però tots dos tenen vida independent.
Cap dels dos és propietari de l’altre; simplement col·laboren.
Un metge coneix els seus pacients i un pacient coneix el seu metge, però són persones independents.
Si el metge es jubila, els pacients no desapareixen; si un pacient canvia de ciutat, el metge segueix existint. Es coneixen, però no es posseeixen.
public class Professor {
private String nom;
// El professor pot conèixer els seus alumnes, però no els "conté"
}
public class Alumne {
private String nom;
private Professor tutor; // Associació: l'alumne coneix el seu tutor
public void assignarTutor(Professor tutor) {
this.tutor = tutor;
}
}En UML l’associació es dibuixa amb una línia contínua:
classDiagram
Alumne --> Professor : té com a tutor
class Alumne {
-String nom
}
class Professor {
-String nom
}
Mini-repte. Pensa en una associació del món real (per exemple, Client i Botiga) i escriu la capçalera de les dues classes amb l’atribut que representa la relació.
13.4 Agregació
L’agregació és una associació una mica més forta: expressa una relació “té un / forma part de”, en què un objecte agrupa altres objectes, però aquests poden existir sense el tot.
Un equip de futbol té jugadors. Però si l’equip es dissol, els jugadors segueixen existint: poden fitxar per un altre equip.
El tot (equip) agrupa les parts (jugadors), però les parts tenen vida pròpia més enllà del tot.
public class Equip {
private String nom;
private List<Jugador> jugadors; // Agrega jugadors
public void fitxar(Jugador j) {
jugadors.add(j); // El jugador ja existia; ara forma part de l'equip
}
}Un Jugador es crea fora de l’equip i després s’hi incorpora. Si l’equip desapareix, els objectes Jugador continuen vius. En UML l’agregació es marca amb un rombe buit al costat del “tot”:
classDiagram
Equip o-- Jugador : agrega
class Equip {
-String nom
}
class Jugador {
-String nom
}
Mini-repte. El teu reproductor de música té una llista de reproducció que agrega cançons. Les cançons existeixen encara que esborris la llista. Escriu la capçalera de LlistaReproduccio amb l’atribut que agrega objectes Canco.
13.5 Composició
La composició és la relació més forta: una relació “és part de” en què les parts no poden existir sense el tot.
Si es destrueix el tot, les parts es destrueixen amb ell. El tot és propietari i sol crear les seves parts.
Una casa es compon d’habitacions. Les habitacions no tenen sentit sense la casa: si enderroques la casa, les habitacions desapareixen amb ella.
No pots agafar una habitació i posar-la en una altra casa; només té sentit amb la seva casa.
public class Factura {
private List<LiniaFactura> linies;
public Factura() {
this.linies = new ArrayList<>();
}
public void afegirLinia(String producte, double preu) {
// La factura CREA les seves pròpies línies.
linies.add(new LiniaFactura(producte, preu));
}
}Les LiniaFactura es creen dins de la factura i no existeixen fora d’ella: si la factura desapareix, les seves línies també.
En UML la composició es marca amb un rombe ple al costat del tot:
classDiagram
Factura *-- LiniaFactura : es compon de
class Factura {
-List~LiniaFactura~ linies
+afegirLinia(String, double) void
}
class LiniaFactura {
-String producte
-double preu
}
Mini-repte. Un Cotxe es compon d’un Motor que es crea amb el cotxe i mor amb ell. Escriu la capçalera de Cotxe i el seu constructor creant el motor amb new.
13.6 Comparació de les relacions entre classes
Les tres associacions són graus de la mateixa idea (un objecte relacionat amb un altre), amb un lligam cada vegada més fort:
| Aspecte | Associació | Agregació | Composició |
|---|---|---|---|
| Idea | Usa / coneix | Té / agrupa | És part de |
| Força del lligam | Feble | Mitjana | Forta |
| Les parts viuen sense el tot? | Sí | Sí | No |
| Qui crea les parts | Ningú en concret | Se li passen de fora | El tot (amb new) |
| Símbol UML | Línia | Rombe buit | Rombe ple |
| Exemple | Alumne – Professor | Equip – Jugador | Factura – Línia |
A la pràctica, la línia entre agregació i composició de vegades és difusa i genera debats fins i tot entre professionals.
El que de veritat importa és la pregunta clau: si destrueixo el tot, la part ha de desaparèixer també? Si la resposta és sí, és composició; si no, agregació.
13.7 Multiplicitat
Les relacions tenen una multiplicitat o cardinalitat: quants objectes d’una banda es relacionen amb quants de l’altra. S’escriu als extrems de la línia en UML:
| Notació | Significat |
|---|---|
1 |
Exactament un |
0..1 |
Cap o un |
* o 0..* |
Zero o molts |
1..* |
Un o molts |
2..5 |
Entre dos i cinc |
Per exemple: una biblioteca té molts llibres, i cada llibre pertany a una biblioteca:
classDiagram
Biblioteca "1" *-- "0..*" Llibre : conté
Biblioteca "1" o-- "0..*" Soci : té inscrits
class Biblioteca {
-String nom
+afegirLlibre(Llibre) void
+inscriure(Soci) void
}
class Llibre {
-String titol
}
class Soci {
-String nom
}
El diagrama es llegeix:
- Una
Bibliotecaes compon de zero o moltsLlibre(rombe ple: els llibres del catàleg són seus). - Agrega** zero o molts
Soci(rombe buit: els socis són persones independents).
La multiplicitat es llegeix cap a l’altra banda. En “Biblioteca 1 — 0..* Llibre”,
- El
0..*és a prop deLlibrei vol dir que una biblioteca té molts llibres. - El
1a prop deBibliotecavol dir que un llibre pertany a una biblioteca. És fàcil llegir-ho al revés.
Mini-repte. Dibuixa (o descriu) la multiplicitat entre Comanda i Producte en una botiga: una comanda pot tenir molts productes i un producte pot aparèixer en moltes comandes.
13.8 Un exemple complet
Un exemple sobre les classes d’una biblioteca.
classDiagram
Biblioteca "1" *-- "0..*" Llibre : catàleg
Biblioteca "1" o-- "0..*" Soci : socis
Prestec "0..*" --> "1" Llibre : sobre
Prestec "0..*" --> "1" Soci : de
class Biblioteca {
-String nom
-List~Llibre~ cataleg
-List~Soci~ socis
+prestar(Llibre, Soci) Prestec
}
class Llibre {
-String titol
-boolean prestat
}
class Soci {
-String nom
-int numeroSoci
}
class Prestec {
-LocalDate dataInici
}
I la traducció a codi de la relació central:
public class Biblioteca {
private String nom;
private List<Llibre> cataleg; // Composició: la biblioteca crea/posseeix el catàleg
private List<Soci> socis; // Agregació: els socis són independents
public Biblioteca(String nom) {
this.nom = nom;
this.cataleg = new ArrayList<>();
this.socis = new ArrayList<>();
}
public void afegirLlibre(String titol, String autor, int any) {
cataleg.add(new Llibre(titol, autor, any)); // El crea ella mateixa
}
public void inscriure(Soci soci) {
socis.add(soci); // El soci ja existia; l'agreguem
}
// Associació: el préstec relaciona un llibre i un soci existents
public Prestec prestar(Llibre llibre, Soci soci) {
if (!llibre.isPrestat()) {
llibre.prestar();
return new Prestec(llibre, soci);
}
return null;
}
}Aprèn a fer les dues traduccions.
- Un atribut que és un altre objecte o llista d’objectes → una relació al diagrama.
- Una relació al diagrama → un atribut a la classe.
13.9 Disseny modular
Repartir la feina en classes que col·laboren està molt bé, però com sabem si el disseny és bo?
Dos criteris guien el disseny modular:
- Cohesió (com més alta, millor). Cada classe ha de tenir una responsabilitat clara i única. Una classe
Llibreque gestioni títols, connexions a la base de dades i la interfície gràfica té poca cohesió: fa massa coses inconnexes. - Acoblament (com més baix, millor). Les classes han de dependre les unes de les altres al mínim. Si canviar una classe obliga a tocar-ne cinc més, l’acoblament és massa alt.
Les peces de LEGO tenen baix acoblament: cada peça és independent i encaixa per connectors ben definits; pots canviar-ne una sense desfer la resta.
Una escultura de fang és tot una massa enganxada (alt acoblament): tocar una part deforma les altres.
Volem programes de LEGO, no de fang.
L’encapsulació és la millor eina per baixar l’acoblament: si les classes es comuniquen només a través de mètodes públics ben definits (i no toquen les dades internes de les altres), pots canviar l’interior d’una classe sense trencar les altres.
Una classe Déu (God class) és una classe enorme que ho controla i ho sap tot. Té baixíssima cohesió i altíssim acoblament, i és un malson de mantenir.
Si veus que una classe creix sense aturador i barreja responsabilitats molt diferents, és el senyal per partir-la en classes més petites i especialitzades.
Mini-repte. La classe Biblioteca de dalt també imprimeix informes formatats per pantalla i desa dades en un fitxer. Quantes responsabilitats té? Proposa en quines classes la partiries.
13.10 Resum
- Els programes reals es construeixen amb moltes classes que col·laboren; cada classe té la seva responsabilitat.
- La relació bàsica és tenir un atribut que és un altre objecte (o una llista d’objectes).
- Hi ha tres graus de relació segons la força del lligam:
- Associació.
- Agregació.
- Composició.
- Els diagrames UML de classes representen classes (nom, atributs, mètodes amb
+/-) i les seves relacions, amb la multiplicitat als extrems (1,*,1..*). - El bon disseny modular busca alta cohesió (responsabilitat única) i baix acoblament (dependències mínimes). Evita la “classe Déu”.
13.11 Per practicar
- Classifica relacions. Per a cada parell, digues si és associació, agregació o composició i justifica-ho:
- Universitat–Estudiant,
- Llibre–Pagina,
- Cotxe–Conductor,
- Cos–Cor,
- Playlist–Canco.
- UML → Java. Donat aquest diagrama, escriu les capçaleres de les classes i els atributs de relació:
classDiagram
Comanda "1" *-- "1..*" LiniaComanda : conté
LiniaComanda "*" --> "1" Producte : referencia
Java → UML. Dibuixa el diagrama UML (amb multiplicitats i tipus de relació correctes) que correspon a aquest codi:
public class Gimnas { private List<Soci> socis; // Socis independents private List<Activitat> activitats; // Creades pel gimnàs }Modela GestGim. Fes un diagrama UML del projecte de l’alumnat GestGim: un
GimnasambSoci,Activitat,QuotaiReserva. Decideix les relacions i multiplicitats i justifica-les.Refactorització. Tens una classe
GestorTotamb 40 mètodes que gestiona socis, quotes, activitats i informes. Proposa com partir-la aplicant alta cohesió, i dibuixa el nou disseny.