Klassediagrammer er en illustrasjon av innholdet i og sammenhengen mellom klasser, og fungerer som et supplement til tekstlig kode. Et klassediagram viser klasser som bokser, attributter og operasjoner som tekstlinjer inni boksene (i hver sine deler) og assosiasjoner og arv som streker med og uten piler. I tillegg annoteres assosiasjonsstreker med informasjon om navn og såkalt multiplisitet (også kalt kardinalitet). Figuren under viser et enkelt klassediagram med tilsvarende Java-kode til høyre.

Attributter og assosiasjoner

class Person {
	String firstName;
	String familyName;
	String getFullName();
	Collection<Car> cars;
 }

class Car {
	String brand;
	Person owner;
}

Figuren til venstre viser et UML-klassediagram med to klasser: Person og Car. Person-klassen har to attributter (firstName og familyName) og én metode (getFullName()), mens Car-klassen har ett attributt (brand). Klassene er knyttet sammen gjennom en ownership-assosiasjon, som viser et en-til-mange-forhold mellom Person og Car.

Assosiasjonen er annotert med:

  • owner: 1 i Person-enden, noe som betyr at én bil (Car) kun kan ha én eier (Person).
  • cars: * i Car-enden, som indikerer at én person kan eie flere biler.

Dette betyr at navigasjonen kan foregå slik:

  • Fra Person til Car via cars-rollen, hvor *-tegnet angir at en Person-instans kan være knyttet til flere Car-instanser.
  • Fra Car til Person via owner-rollen, hvor 1-tegnet viser at en Car-instans kun kan ha én Person-eier.

Diagrammet tilsvarer Java-koden til høyre, hvor:

  • Attributtene i UML-diagrammet er representert som felt i klassene.
  • Metoden getFullName() i Person-klassen gjenspeiles direkte i Java-koden.
  • Assosiasjonene mellom klassene implementeres ved at Person har en Collection<Car> for å håndtere flere relasjoner, mens Car har et enkelt Person owner-felt for å vise én-til-en-forholdet.

Assosiasjoner i UML har ingen direkte Java-motstykke, men implementeres typisk ved hjelp av referanser mellom objekter. Når én instans kan være koblet til flere andre instanser, brukes Collection, List, Set, eller array i stedet for en enkel variabel.

Dersom en assosiasjon hadde et pilhode (retningsbestemt assosiasjon), ville det bety at navigasjonen kun er mulig én vei. I praksis betyr dette at klassen i pilhode-enden ikke ville ha en referanse tilbake til den andre klassen. Hvis for eksempel assosiasjonen i diagrammet pekte fra Person til Car, ville ikke Car-klassen trengt et owner-felt.

Arv (extends og implements)

Arv gjør at nye klasser kan bygge på eller utvide andre klasser. I klassediagrammer angis det med en spesiell pil fra subklassen til superklassen, med et litt stort pilhode som ikke er fylt. En bruker stiplet linje hvis det er snakk om implementasjon av grensesnitt, og ikke vanlig arv mellom klasser (eller mellom grensesnitt).

interface Observer {
	public void update();
}

abstract class AbstractObservable {

	private Collection<Observer> observers = new ArrayList<Observer>();

	public void addObserver(Observer observer) {
		observers.add(observer);
	}	

	public void removeObserver(Observer observer) {
		observers.remove(observer);
	}

	protected void fireUpdate() {
		for (Observer observer: observers) {
			observer.update(this);
		}
	}
}

class ConcreteObservable extends AbstractObservable {
}

class ConcreteObserver implements Observer {
	public void update(AbstractObservable) {
		// method body
	}
}

Figuren til venstre viser et UML-klassediagram for Observatør-observert-teknikken (Observer Pattern). Dette mønsteret brukes for å etablere en en-til-mange-relasjon mellom objekter, der flere observatører kan abonnere på oppdateringer fra et observerbart objekt.

  • AbstractObservable er en abstrakt klasse som håndterer en samling av observatører (lyttere). Den tilbyr metoder for å:

    • Registrere en observatør (addObserver(Observer o)).
    • Fjerne en observatør (removeObserver(Observer o)).
    • Varsle observatørene om endringer (fireUpdate()).
  • ConcreteObservable er en konkret implementasjon av AbstractObservable. Den arver all funksjonalitet for å administrere observatører, men legger ikke til ny funksjonalitet i dette eksempelet.

  • Observer er et grensesnitt som definerer én metode:

    • update(AbstractObservable o), som brukes til å motta oppdateringer fra det observerte objektet.
  • ConcreteObserver implementerer Observer-grensesnittet og gir en spesifikk implementasjon av update(AbstractObservable o)-metoden.

Synlighetsmodifikatorer i diagrammet:

  • Grønn sirkel (green circle ): public (tilgjengelig overalt).
  • Gul rute (large orange diamond ): protected (tilgjengelig for subklasser).
  • Rød firkant (red square ): private (skjult for andre klasser, men ikke brukt i dette diagrammet).

Koden under diagrammet:

  • Diagrammet oversettes direkte til Java-kode.
  • AbstractObservable bruker en Collection<Observer> for å holde på observatørene og en ArrayList<Observer> for å lagre dem.
  • fireUpdate()-metoden itererer over observatørene og kaller update(this) for å varsle dem om endringer.

Merknader om navnekonvensjoner:

I Java brukes ofte "Listener" som suffiks for observatørgrensesnitt, og dermed også for relaterte variabler og metoder:

  • XXXListener (f.eks. ActionListener).
  • Collection<XXXListener> xxxListeners for å lagre lyttere.
  • addXXXListener() og removeXXXListener() for å legge til og fjerne observatører.


Oppbygningen til et klassediagram

Et klassediagram består av bokser som representerer klasser og linjer/piler som viser relasjoner mellom dem.

Hver klasseboks er vanligvis delt inn i tre deler:

  1. Øverste del: Klassenavn (skrevet med stor forbokstav som en konvensjon).
  2. Midterste del: Attributter (feltene til klassen, ofte med synlighetsmodifikatorer).
  3. Nederste del: Metoder (operasjoner som klassen kan utføre).




Mellom boksene kan man ha streker og/eller piler som representerer relasjonene mellom klassene. De vanligste er:

  • Arv: representeres med en lukket pil, der subklassen peker på superklassen.


  • Implementasjon av grensesnitt: representeres med stiplet linje og lukket pil, der klassen som implementerer et grensesnitt peker på grensesnittet som implementeres.

  • Toveis assosiasjon: begge klassene vet om hverandre (dvs. at de har en referanse til hverandre). Toveis assosiasjon representeres med en linje mellom to bokser (evt. mellom samme boks om referansen er til et eller flere objekt av samme klasse). På hver ende av linjen står det en rolle og kardinaliteten til referansen på formen "<rolle>: <kardinalitet>". Kardinaliteten kan være representert som ett tall eller *-symbolet. 

Delvis kode til klassediagrammet
class Person{
	Map<Book, Boolean> books = new HashMap<>();
		// Boolean = true => Person has read book
		// Boolean = false => Person has not read book
	boolean bookLover;
	
	boolean hasRead(Book book){
		...
	}
	
	boolean hasBook(Book book){
		...
	}
}


class Book{
	String title;
	int pages;
	Person owner;
	Book sequel;
	Book prequel;
}


  • Enveis assosiasjon: en klasse vet om en annen klasse og interagerer med den, men denne klassen har ingen referanser til den første og "vet" dermed ikke om den. Enveis assosiasjoner representeres med en åpen pil som peker fra klassen med referansen til klassen som blir referert.

Delvis kode til klassediagrammet
class Person{
	Map<Book, Boolean> books = new HashMap<>();
		// Boolean = true => Person has read book
		// Boolean = false => Person has not read book
	boolean bookLover;
	
	boolean hasRead(Book book){
		...
	}
	
	boolean hasBook(Book book){
		...
	}
}


class Book{
	String title;
	int pages;
	Book prequel;
}


Synlighetsmodifikatorer for attributter og metoder har følgende symboler:

  • Public: grønn sirkel
  • Protectedgul ruter/diamant
  • Privaterød firkant

De kan også representeres med følgende symboler:

  • Public: +
  • Protected: #
  • Private: -