Classi astratte

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Danilo Pianini — danilo.pianini@unibo.it

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

Compiled on: 2025-12-05 — versione stampabile

back

Outline

Goal della lezione

  • Capire il concetto di classe astratta in Java
  • Capire come e quando usarle
  • Introdurre il pattern Template Method

Argomenti

  • Classi astratte: motivazioni, sintassi, esempi
  • Argomenti variabili
  • Pattern Template Method basato su classi astratte

Classi astratte

Motivazioni

Fra interfacce e classi

  • Le interfacce descrivono solo un contratto
  • Le classi definiscono un comportamento completo
  • …c’è margine per costrutti intermedi?

Classi astratte

  • Le classi astratte sono usate per descrivere classi dal comportamento parziale (ossia, in cui alcuni metodi sono dicharati ma non implementati)
  • Tali classi non sono istanziabili (l’operatore new non può essere usato)
  • Possono essere estese e ivi completate, da cui la generazione di oggetti

Tipica applicazione: pattern Template Method

Serve a dichiare uno schema di strategia con un metodo “template” (spesso final) che definisce un comportamento comune, basato su metodi astratti da concretizzare in sottoclassi

Classi astratte

Una classe astratta:

  • è dichiarata tale: abstract class C ... { ... }
  • non è istanziabile (in quanto astratta, ovvero non pienamente specificata)
  • può opzionalmente dichiarare metodi astratti:
    • hanno forma ad esempio: abstract int m(int a, String s);
    • ossia senza body, come nelle dichiarazioni delle interfacce

Altri aspetti

  • può definire campi, costruttori, metodi, concreti e non
    • …deve definire con cura il loro livello d’accesso
  • può estendere da una classe astratta o non astratta
  • può implementare interfacce, senza essere tenuta ad ottemperarne il contratto
    • i metodi dell’interfaccia implementata, se non implementati, sono astratti
  • chi estende una classe astratta può essere non-astratto solo se concretizza/implementa tutti i metodi astratti

Esempio: LimitedLamp come classe astratta

Obiettivo

  • Vogliamo progettare una estensione di SimpleLamp col concetto di esaurimento
  • La strategia con la quale gestire tale esaurimento può essere varia
  • Ma bisogna far sì che qualunque strategia si specifichi, sia garantito che:
    • la lampadina si accenda solo se non esaurita
    • in caso di effettiva accensione sia possibile tenerne traccia ai fini della strategia

Soluzione

  • Un uso accurato di abstract, final, e protected
  • Daremo tre possibili specializzazioni per una LimitedLamp
    1. che non si esaurisce mai
    2. che si esaurisce all’n-esima accensione
    3. che si esaurisce dopo un certo tempo dalla prima accensione

UML complessivo

SimpleLamp

public class SimpleLamp {

	private boolean switchedOn;

	public SimpleLamp() {
		this.switchedOn = false;
	}

	public void switchOn() {
		this.switchedOn = true;
	}

	public void switchOff() {
		this.switchedOn = false;
	}

	public boolean isSwitchedOn() {
		return this.switchedOn;
	}
}

LimitedLamp

public abstract class LimitedLamp extends SimpleLamp {

	public LimitedLamp() {
		super();
	}

	/* Questo metodo è finale: regola la coerenza con okSwitch() e isOver() */
	public final void switchOn() { // TEMPLATE METHOD
		if (!this.isSwitchedOn() && !this.isOver()) {
			super.switchOn();
			this.okSwitch();
		}
	}

	// Cosa facciamo se abbiamo effettivamente acceso? Dipende dalla strategia
	protected abstract void okSwitch();

	/* Strategia per riconoscere se la lamp è esaurita */
	public abstract boolean isOver();

	public String toString() {
		return "Over: " + this.isOver() + ", switchedOn: "	+ this.isSwitchedOn();
	}
}

UnlimitedLamp

/* Non si esaurisce mai */
public class UnlimitedLamp extends LimitedLamp {

	/* Nessuna informazione extra da tenere */
	public UnlimitedLamp() {
		super();
	}

	/* Allo switchOn.. non faccio nulla */
	protected void okSwitch() {
	}

	/* Non è mai esaurita */
	public boolean isOver() {
		return false;
	}
}

CountdownLamp

/* Si esaurisce all'n-esima accensione */
public class CountdownLamp extends LimitedLamp {

	/* Quanti switch mancano */
	private int countdown;

	public CountdownLamp(final int countdown) {
		super();
		this.countdown = countdown;
	}

	/* Allo switchOn.. decremento il count */
	protected void okSwitch() {
		this.countdown--;
	}

	/* Finito il count.. lamp esaurita */
	public boolean isOver() {
		return this.countdown == 0;
	}
}

ExpirationTimeLamp

import java.util.Date;

/* Si esaurisce dopo un certo tempo (reale) dopo la prima accensione */
public class ExpirationTimeLamp extends LimitedLamp {
	/* Tengo il momento dell'accensione e la durata */
	private Date firstSwitchDate;
	private long duration;

	public ExpirationTimeLamp(final long duration) {
		super();
		this.duration = duration;
		this.firstSwitchDate = null;
	}

	/* Alla prima accensione, registro la data */
	protected void okSwitch() {
		if (this.firstSwitchDate == null) {
			this.firstSwitchDate = new java.util.Date();
		}
	}

	/* Esaurita se è passato troppo tempo */
	public boolean isOver() {
		return this.firstSwitchDate != null &&
		   (new Date().getTime() - this.firstSwitchDate.getTime() 
				   >= this.duration);
	}
}

UseLamps

public class UseLamps {
    // clausola throws Exception qui sotto necessaria!!
    public static void main(String[] s) throws Exception {
        LimitedLamp lamp = new UnlimitedLamp();
        lamp.switchOn();
        System.out.println("ul| " + lamp);
        for (int i = 0; i < 1000; i++) {
            lamp.switchOff();
            lamp.switchOn();
        }
        System.out.println("ul| " + lamp); // non si è esaurita

        lamp = new CountdownLamp(5);
        for (int i = 0; i < 4; i++) {
            lamp.switchOn();
            lamp.switchOff();
        }
        System.out.println("cl| " + lamp);
        lamp.switchOn();
        System.out.println("cl| " + lamp); // al quinto switch si esaurisce

        lamp = new ExpirationTimeLamp(1000); // 1 sec
        lamp.switchOn();
        System.out.println("el| " + lamp);
        Thread.sleep(3000); // attendo 1.1 secs
        System.out.println("el| " + lamp); // dopo 1.1 secs si è esaurita
        lamp.switchOff();
        lamp.switchOn();
        System.out.println("el| " + lamp);
    }
}

Classi astratte vs interfacce

  • Due versioni quasi equivalenti
  • Unica differenza: ereditarietà singola per classi, ereditarietà multipla per le interfacce
/* Versione interfaccia */
public interface Counter {
    void increment();
    int getValue();
} 

/* Versione classe astratta */
public abstract class Counter {
    public abstract void increment();
    public abstract int getValue();
} 

Approfondimento: classi astratte vs. interfacce con metodi di default

Interfacce con metodi di default …

public interface I4 extends I1, I2, I3 {
    void doSomething(String s);
    // da Java 8
    double E = 2.718282; // implicitamente public, static, final
    default void doSomethingTwice(String s) { doSomething(s); doSomething(s); }
    static double PI() { return Math.PI; }
}

… sembrano piuttosto simili alle classi astratte, in quanto possono fornire, in aggiunta a un contratto, alcune implementazioni di default

Tuttavia, ci sono differenze cruciali:

  • le classi astratte possono definire variabili d’istanza (stato)
  • le classi astratte possono definire costruttori
  • le classi astratte possono definire membri con visibilità diverse
  • le classi astratte possono fare overriding di metodi da Object
  • i default method non possono essere final

Wrap-up su ereditarietà

Il caso più generale:

class C extends D implements I, J, K, L { ... }

Cosa deve/può fare la classe C

  • deve implementare tutti i metodi dichiarati in I,J,K,L e super-interfacce
  • può fare overriding dei metodi (non finali) definiti in D e superclassi

Classe astratta:

abstract class CA extends D implements I, J, K, L { ... }

Cosa deve/può fare la classe CA

  • non è tenuta a implementare alcun metodo
  • può implementare qualche metodo per definire un comportamento parziale

Argomenti variabili

Variable arguments

A volte è utile che i metodi abbiano un numero variabile di argomenti

int i = sum(10, 20, 30, 40, 50, 60, 70);
printAll(10, 20, 3.5, new Object());
  • prima di Java 5 si simulava passando un unico array

Variable arguments

  • L’ultimo (o unico) argomento di un metodo può essere del tipo “Type... argname
void m(int a, float b, Object... argname) { ... }
  • Nel body del metodo, argname è trattato come un Type[]
  • Chi chiama il metodo, invece che passare un array, passa una lista di argomenti di tipo Type
  • Funziona automaticamente con polimorfismo, autoboxing, instanceof, …

Uso dei variable arguments

public class VarArgs {
	// somma un numero variabile di Integer
	public static int sum(final Integer... args) {
		int sum = 0;
		for (int i : args) {
			sum = sum + i;
		}
		return sum;
	}

	// stampa il contenuto degli argomenti, se meno di 10
	public static void printAll(final String start, final Object... args) {
		System.out.println(start);
		if (args.length < 10) {
			for (final Object o : args) {
				System.out.println(o);
			}
		}
	}

	public static void main(String[] s) {
		System.out.println(sum(10, 20, 30, 40, 50, 60, 70, 80));
		printAll("inizio", 1, 2, 3.2, true, new int[] { 10 }, new Object());
		
		System.out.format("%d %d\n", 10, 20); // C-like printf
	}
}

Alcuni pattern basati sulle classe astratte

Pattern Template Method: comportamentale, su classi

Intento/motivazione

Definisce lo scheletro (template) di un algoritmo (o comportamento), lasciando l’indicazione di alcuni suoi aspetti alle sottoclassi.

Esempi

  • Definizione della logica di accensione (switchOn) di una lampadina (Lamp)
  • Un comparatore può fornire metodi template per capire se un oggetto è minore/maggiore/uguale di un altro, sulla base di un metodo astratto int compareTo(T a, T b)

Soluzione

  • L’algoritmo è realizzato attraverso un metodo template che realizza un algoritmo chiamando metodi astratti/da specializzare quando servono gli aspetti non noti a priori
  • Una sottoclasse fornisce l’implementazione dei metodi astratti
AbstractClass
TemplateMethod()
PrimitiveOp1()
PrimitiveOp2()
ConcreteClass
PrimitiveOp1()
PrimitiveOp2()
abstract class AbstractClass {
    public void TemplateMethod() {
        // ...
        PrimitiveOp1();
        // ...
        PrimitiveOp2();
        // ...
    }
    public abstract void PrimitiveOp1();
    public abstract void PrimitiveOp2();
}

class ConcreteClass extends AbstractClass {
    public void PrimitiveOp1() { /* ... */ }
    public void PrimitiveOp2() { /* ... */ }
}

Template Method: esempio BankAccount

public abstract class BankAccount {
	private int amount;
	
	public BankAccount(int amount){
		this.amount = amount;
	}
	
	public abstract int operationFee(); // costo bancario operazione
	
	public int getAmount(){
		return this.amount;
	}
	
	public void withdraw(int n){	// template method
		this.amount = this.amount - n - this.operationFee();
	}

	public static void main(String[] args){
		final BankAccount b = new BankAccountWithConstantFee(100);
		b.withdraw(20);
		System.out.println(b.getAmount()); // 79
	}
}

class BankAccountWithConstantFee extends BankAccount {
	public BankAccountWithConstantFee(int amount) {
		super(amount);
	}

	public int operationFee(){ return 1; }	
}
public class BankAccountFixedTax extends AbstractBankAccount {

    public BankAccountFixedTax(int currentAmount) {
        super(currentAmount);
    }

    @Override
    protected int getTax(int currentAmount) {
        return 1;
    }

}
public class BankAccountVariableTax extends AbstractBankAccount {

    public BankAccountVariableTax(int currentAmount) {
        super(currentAmount);
    }

    @Override
    protected int getTax(int currentAmount) {
        return (currentAmount<50 ? 1 : 0);
    }

}

Classi astratte

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Danilo Pianini — danilo.pianini@unibo.it

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

Compiled on: 2025-12-05 — versione stampabile

back