Mini app Android “Gestione Prodotti” in Java: guida completa all’OOP (incapsulamento, ereditarietà, polimorfismo, astrazione)
In questo tutorial creeremo una mini app Android in Java chiamata Gestione Prodotti, pensata per mettere in pratica in modo concreto tutti e quattro i principi fondamentali della programmazione orientata agli oggetti (OOP): incapsulamento, ereditarietà, polimorfismo e astrazione.

L’app mostrerà una lista di prodotti in una RecyclerView, permetterà di aggiungere nuovi prodotti tramite un pulsante FloatingActionButton (FAB) e mostrerà i dettagli del prodotto in un AlertDialog quando l’utente tocca un elemento della lista. Niente database, niente API: solo Java OOP + componenti standard Android.
Obiettivo didattico della mini app gestione prodotti
L’obiettivo è vedere come i concetti OOP si traducono in codice reale Android:
- Incapsulamento: nascondiamo i dati interni della classe
Prodottousando campi privati e metodigetter/setter. - Ereditarietà: usiamo
Prodottocome classe base astratta e creiamoProdottoFisicoeProdottoDigitalecome sottoclassi. - Polimorfismo: definiamo un metodo
calcolaPrezzo()che si comporta in modo diverso in base al tipo di prodotto. - Astrazione: definiamo l’interfaccia
Vendibileche rappresenta il concetto astratto di “cosa può essere venduta”.
Struttura della mini app gestione prodotti Android
- Schermata principale:
MainActivitycon RecyclerView che mostra nome, tipo e prezzo calcolato. - Pulsante FAB: apre un Dialog per aggiungere un nuovo prodotto (nome, tipo, prezzo base).
- Tap su un prodotto: mostra un AlertDialog con dettagli (tipo, prezzo calcolato, descrizione).
- Nessun database: usiamo solo una lista in memoria.
1. Preparazione del progetto Android per la mini app gestione prodotti
Per seguire il tutorial ti serve Android Studio aggiornato.
- Crea un nuovo progetto Empty Activity.
- Linguaggio: Java.
- Minimum SDK: per esempio API 21 (Android 5.0) o superiore.
Assicurati che nel file build.gradle (Module: app) sia presente la dipendenza per RecyclerView (di solito è già inclusa nei template moderni):
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
}
2. Astrazione: interfaccia Vendibile
Partiamo dall’astrazione. Vogliamo rappresentare il concetto generale di qualcosa che può essere venduta. A questo livello non ci importa se sia un prodotto fisico o digitale: ci interessano solo le operazioni comuni.
Creiamo un’interfaccia Vendibile con due metodi:
double calcolaPrezzo(): ritorna il prezzo finale per l’utente.String getDescrizione(): ritorna una descrizione leggibile del prodotto.
Questa interfaccia incarna il principio di astrazione: definiamo che cosa può fare un oggetto vendibile, senza preoccuparci di come lo farà.
Codice: Vendibile.java
package com.example.gestioneprodotti.oop;
/**
* Interfaccia che rappresenta qualcosa che può essere venduta.
* Qui applichiamo il principio di ASTRAZIONE:
* - definiamo solo le operazioni comuni (cosa può fare)
* - non conosciamo ancora i dettagli concreti (come lo farà)
*/
public interface Vendibile {
/**
* Calcola il prezzo finale da far pagare al cliente.
*/
double calcolaPrezzo();
/**
* Ritorna una descrizione testuale del prodotto.
*/
String getDescrizione();
}
3. Incapsulamento + astrazione: classe astratta Prodotto (app gestione prodotti)
Ora creiamo la classe base Prodotto, che rappresenta ciò che hanno in comune tutti i prodotti (fisici o digitali). Qui combiniamo due principi:
- Incapsulamento: i dati (nome, prezzo base, tipo, quantita) sono privati e accessibili solo tramite getter/setter.
- Astrazione: rendiamo la classe astratta e facciamo implementare l’interfaccia
Vendibile, delegando i dettagli alle sottoclassi.
Crea il file Prodotto.java nel package com.example.gestioneprodotti.oop:
package com.example.gestioneprodotti.oop;
/**
* Classe base astratta che rappresenta un prodotto generico.
*
* - Implementa l'interfaccia Vendibile (ASTRAZIONE)
* - Usa campi privati e getter/setter (INCAPSULAMENTO)
*/
public abstract class Prodotto implements Vendibile {
// INCAPSULAMENTO: i campi sono privati
private String nome;
private double prezzoBase;
private String tipo;
private int quantita;
public Prodotto(String nome, double prezzoBase, String tipo, int quantita) {
this.nome = nome;
this.prezzoBase = prezzoBase;
this.tipo = tipo;
this.quantita = quantita;
}
// Getter e setter incapsulano l'accesso ai dati interni
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public double getPrezzoBase() {
return prezzoBase;
}
public void setPrezzoBase(double prezzoBase) {
this.prezzoBase = prezzoBase;
}
public String getTipo() {
return tipo;
}
public void setTipo(String tipo) {
this.tipo = tipo;
}
public int getQuantita() {
return quantita;
}
public void setQuantita(int quantita) {
if (quantita >= 0) this.quantita = quantita;
}
@Override
public String toString() {
return nome + " (" + tipo + ")";
}
}
Nota: non implementiamo qui il metodo calcolaPrezzo() dell’interfaccia Vendibile. Lo lasceremo alle sottoclassi, che applicheranno il polimorfismo calcolando il prezzo in modo diverso.
4. Ereditarietà + polimorfismo: ProdottoFisico e ProdottoDigitale (mini app gestione prodotti)
Ora applichiamo l’ereditarietà: creiamo due sottoclassi che estendono Prodotto e implementano i metodi dell’interfaccia Vendibile in modo diverso (qui entra in gioco il polimorfismo).
ProdottoFisico: aggiunge le spese di spedizione
Per i prodotti fisici vogliamo aggiungere un costo fisso di spedizione, ad esempio €4,99. Quindi:
calcolaPrezzo()=prezzoBase + 4.99getDescrizione()indica che si tratta di un prodotto fisico con spedizione.
package com.example.gestioneprodotti.oop;
/**
* Sottoclasse che rappresenta un prodotto fisico.
*
* - EREDITA da Prodotto (EREDITARIETÀ)
* - Implementa calcolaPrezzo() in modo specifico (POLIMORFISMO)
*/
public class ProdottoFisico extends Prodotto {
private static final double COSTO_SPEDIZIONE = 4.99;
public ProdottoFisico(String nome, double prezzoBase, int quantita) {
// Passiamo "Fisico" come tipo alla superclasse
super(nome, prezzoBase, "Fisico", quantita);
}
@Override
public double calcolaPrezzo() {
// POLIMORFISMO: per il prodotto fisico aggiungiamo le spese di spedizione
return getPrezzoBase() + COSTO_SPEDIZIONE;
}
@Override
public String getDescrizione() {
return "Prodotto fisico: " + getNome() +
"\nPrezzo base: " + String.format("%.2f", getPrezzoBase()) + " €" +
"\nSpedizione: " + String.format("%.2f", COSTO_SPEDIZIONE) + " €" +
"\nPrezzo finale: " + String.format("%.2f", calcolaPrezzo()) + " €";
}
}
ProdottoDigitale: applica una riduzione del 10% (IVA digitale)
Per i prodotti digitali vogliamo simulare una riduzione del 10%, ad esempio per IVA agevolata. Quindi:
calcolaPrezzo()=prezzoBase * 0.90getDescrizione()indica che si tratta di un prodotto digitale con sconto.
package com.example.gestioneprodotti.oop;
/**
* Sottoclasse che rappresenta un prodotto digitale.
*
* - EREDITA da Prodotto (EREDITARIETÀ)
* - Implementa calcolaPrezzo() in modo specifico (POLIMORFISMO)
*/
public class ProdottoDigitale extends Prodotto {
private static final double SCONTO_DIGITALE = 0.10; // 10%
public ProdottoDigitale(String nome, double prezzoBase, int quantita) {
// Passiamo "Digitale" come tipo alla superclasse
super(nome, prezzoBase, "Digitale", quantita);
}
@Override
public double calcolaPrezzo() {
// POLIMORFISMO: per il prodotto digitale applichiamo uno sconto del 10%
return getPrezzoBase() * (1 - SCONTO_DIGITALE);
}
@Override
public String getDescrizione() {
return "Prodotto digitale: " + getNome() +
"\nPrezzo base: " + String.format("%.2f", getPrezzoBase()) + " €" +
"\nSconto digitale: " + (int) (SCONTO_DIGITALE * 100) + "%" +
"\nPrezzo finale: " + String.format("%.2f", calcolaPrezzo()) + " €";
}
}
Ora abbiamo due classi concrete che possono essere trattate in modo generico come Vendibile o Prodotto, ma che si comportano in modo diverso quando chiamiamo calcolaPrezzo(). Questo è polimorfismo in azione.
5. Layout XML: activity_main, item_prodotto e dialog_aggiungi_prodotto
Prima di passare alla logica Java dell’interfaccia utente, creiamo i layout XML che useremo nella mini app.
5.1 activity_main.xml: RecyclerView + FAB
Il layout principale usa un CoordinatorLayout e contiene una RecyclerView a schermo intero e un FloatingActionButton per aggiungere nuovi prodotti.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewProdotti"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:listitem="@layout/item_prodotto" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAggiungiProdotto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="Aggiungi prodotto"
app:srcCompat="@android:drawable/ic_input_add"
app:layout_anchor="@id/recyclerViewProdotti"
app:layout_anchorGravity="bottom|end"
android:layout_margin="16dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
5.2 item_prodotto.xml: layout per ogni elemento della lista
Ogni elemento della RecyclerView mostrerà:
- Nome del prodotto
- Tipo (Fisico/Digitale)
- Prezzo calcolato (usando il polimorfismo)
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/textNomeProdotto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16sp"
android:text="Nome prodotto" />
<TextView
android:id="@+id/textTipoProdotto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tipo" />
<TextView
android:id="@+id/textPrezzoProdotto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0,00 €" />
<TextView
android:id="@+id/textQuantitaProdotto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Qtà: 0" />
</LinearLayout>
</androidx.cardview.widget.CardView>
5.3 dialog_aggiungi_prodotto.xml: dialog per aggiungere un prodotto
Infine, creiamo un layout per il Dialog che permetterà all’utente di inserire:
- Nome del prodotto (EditText)
- Prezzo base (EditText numerico)
- Tipo (RadioGroup con due RadioButton: Fisico, Digitale)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/editNomeProdotto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nome prodotto"
android:inputType="textPersonName" />
<EditText
android:id="@+id/editPrezzoBase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Prezzo base (€)"
android:inputType="numberDecimal"
android:layout_marginTop="8dp" />
<EditText
android:id="@+id/editQuantita"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Quantità"
android:inputType="number"
android:layout_marginTop="8dp" />
<RadioGroup
android:id="@+id/radioGroupTipo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<RadioButton
android:id="@+id/radioFisico"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fisico" />
<RadioButton
android:id="@+id/radioDigitale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Digitale"
android:layout_marginStart="16dp" />
</RadioGroup>
</LinearLayout>
6. RecyclerView e polimorfismo: ProdottoAdapter (gestione prodotti Android)
Ora creiamo l’adapter per la RecyclerView. Questa classe non introduce nuovi concetti OOP, ma usa il polimorfismo delle nostre classi prodotto: per ogni elemento chiamiamo semplicemente calcolaPrezzo() sul riferimento Prodotto senza preoccuparci se sia fisico o digitale.
Crea il file ProdottoAdapter.java nel package principale (com.example.gestioneprodotti):
package com.example.gestioneprodotti;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.gestioneprodotti.oop.Prodotto;
import java.util.List;
/**
* Adapter per la RecyclerView che mostra la lista di prodotti.
* Usa il POLIMORFISMO chiamando calcolaPrezzo() su Prodotto,
* senza conoscere il tipo concreto (fisico o digitale).
*/
public class ProdottoAdapter extends RecyclerView.Adapter<ProdottoAdapter.ProdottoViewHolder> {
public interface OnItemClickListener {
void onItemClick(Prodotto prodotto);
}
private List<Prodotto> prodotti;
private OnItemClickListener listener;
public ProdottoAdapter(List<Prodotto> prodotti, OnItemClickListener listener) {
this.prodotti = prodotti;
this.listener = listener;
}
@NonNull
@Override
public ProdottoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_prodotto, parent, false);
return new ProdottoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ProdottoViewHolder holder, int position) {
final Prodotto prodotto = prodotti.get(position);
holder.bind(prodotto, listener);
}
@Override
public int getItemCount() {
return prodotti.size();
}
static class ProdottoViewHolder extends RecyclerView.ViewHolder {
TextView textNome;
TextView textTipo;
TextView textPrezzo;
TextView textQuantita;
public ProdottoViewHolder(@NonNull View itemView) {
super(itemView);
textNome = itemView.findViewById(R.id.textNomeProdotto);
textTipo = itemView.findViewById(R.id.textTipoProdotto);
textPrezzo = itemView.findViewById(R.id.textPrezzoProdotto);
textQuantita = itemView.findViewById(R.id.textQuantitaProdotto);
}
public void bind(final Prodotto prodotto, final OnItemClickListener listener) {
textNome.setText(prodotto.getNome());
textTipo.setText(prodotto.getTipo());
// POLIMORFISMO: il prezzo è calcolato in modo diverso
// a seconda del tipo concreto di Prodotto
double prezzoFinale = prodotto.calcolaPrezzo();
textPrezzo.setText(String.format("%.2f €", prezzoFinale));
textQuantita.setText("Qtà: " + prodotto.getQuantita());
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(prodotto);
}
}
});
}
}
}
7. MainActivity: gestione lista, FAB e dialog
MainActivity mette insieme tutti i pezzi:
- Inizializza la RecyclerView e l’adapter.
- Crea una lista in memoria di
Prodotto(sia fisici che digitali). - Gestisce il click sul FAB per aprire il Dialog di aggiunta.
- Gestisce il tap su un elemento per mostrare un AlertDialog con i dettagli (usando
getDescrizione()).
Codice: MainActivity.java
package com.example.gestioneprodotti;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.gestioneprodotti.oop.Prodotto;
import com.example.gestioneprodotti.oop.ProdottoDigitale;
import com.example.gestioneprodotti.oop.ProdottoFisico;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.List;
/**
* Activity principale dell'app "Gestione Prodotti".
* Qui utilizziamo le nostre classi OOP per popolare la UI.
*/
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ProdottoAdapter adapter;
private List<Prodotto> listaProdotti;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewProdotti);
FloatingActionButton fab = findViewById(R.id.fabAggiungiProdotto);
// Inizializziamo la lista con qualche prodotto di esempio
listaProdotti = new ArrayList<>();
listaProdotti.add(new ProdottoFisico("Libro Java", 29.90, 10));
listaProdotti.add(new ProdottoDigitale("Corso Android Online", 99.00, 5));
listaProdotti.add(new ProdottoFisico("Mouse wireless", 15.50, 3));
adapter = new ProdottoAdapter(listaProdotti, new ProdottoAdapter.OnItemClickListener() {
@Override
public void onItemClick(Prodotto prodotto) {
mostraDettagliProdotto(prodotto);
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mostraDialogAggiungiProdotto();
}
});
}
private void mostraDettagliProdotto(Prodotto prodotto) {
// Usiamo il metodo getDescrizione() definito nell'interfaccia Vendibile
new AlertDialog.Builder(this)
.setTitle(prodotto.getNome())
.setMessage(prodotto.getDescrizione())
.setPositiveButton("OK", null)
.show();
}
private void mostraDialogAggiungiProdotto() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Aggiungi prodotto");
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_aggiungi_prodotto, null);
builder.setView(dialogView);
EditText editNome = dialogView.findViewById(R.id.editNomeProdotto);
EditText editPrezzo = dialogView.findViewById(R.id.editPrezzoBase);
EditText editQuantita = dialogView.findViewById(R.id.editQuantita);
RadioGroup radioGroupTipo = dialogView.findViewById(R.id.radioGroupTipo);
RadioButton radioFisico = dialogView.findViewById(R.id.radioFisico);
RadioButton radioDigitale = dialogView.findViewById(R.id.radioDigitale);
// Impostiamo di default "Fisico"
radioFisico.setChecked(true);
builder.setPositiveButton("Aggiungi", null);
builder.setNegativeButton("Annulla", null);
AlertDialog dialog = builder.create();
dialog.show();
// Sovrascriviamo il click del pulsante "Aggiungi" per poter validare i dati
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String nome = editNome.getText().toString().trim();
String prezzoStr = editPrezzo.getText().toString().trim();
String quantitaStr = editQuantita.getText().toString().trim();
if (TextUtils.isEmpty(nome)) {
editNome.setError("Inserisci un nome");
return;
}
if (TextUtils.isEmpty(prezzoStr)) {
editPrezzo.setError("Inserisci un prezzo");
return;
}
if (TextUtils.isEmpty(quantitaStr)) {
editQuantita.setError("Inserisci una quantità");
return;
}
double prezzoBase;
try {
prezzoBase = Double.parseDouble(prezzoStr.replace(",", "."));
} catch (NumberFormatException e) {
editPrezzo.setError("Prezzo non valido");
return;
}
int quantita;
try {
quantita = Integer.parseInt(quantitaStr);
} catch (NumberFormatException e) {
editQuantita.setError("Quantità non valida");
return;
}
int selectedId = radioGroupTipo.getCheckedRadioButtonId();
Prodotto nuovoProdotto;
if (selectedId == R.id.radioDigitale) {
nuovoProdotto = new ProdottoDigitale(nome, prezzoBase, quantita);
} else {
nuovoProdotto = new ProdottoFisico(nome, prezzoBase, quantita);
}
// Aggiungiamo il nuovo prodotto alla lista e notifichiamo l'adapter
listaProdotti.add(nuovoProdotto);
adapter.notifyItemInserted(listaProdotti.size() - 1);
recyclerView.scrollToPosition(listaProdotti.size() - 1);
dialog.dismiss();
}
});
}
}
8. Come i 4 principi OOP lavorano insieme nella mini app gestione prodotti
Ricapitoliamo come abbiamo applicato in pratica i quattro pilastri dell’OOP in questa mini app Gestione Prodotti:
- Incapsulamento: la classe
Prodottonasconde i suoi dati interni (nome,prezzoBase,tipo,quantita) usando campi privati e fornendo solo getter/setter. In questo modo il resto dell’app non accede mai direttamente ai campi, ma passa da metodi controllati. Il campoquantita, ad esempio, è aggiornato tramite un setter che valida l’input (if (quantita >= 0)). - Ereditarietà:
ProdottoFisicoeProdottoDigitaleestendonoProdotto, riutilizzando tutto ciò che è comune (nome, prezzo base, tipo) e aggiungendo il proprio comportamento specifico per il calcolo del prezzo e la descrizione. - Polimorfismo: sia l’adapter della RecyclerView sia la
MainActivitylavorano con riferimenti di tipoProdottooVendibile. Quando chiamanocalcolaPrezzo()ogetDescrizione(), viene eseguita automaticamente l’implementazione corretta in base al tipo concreto (fisico o digitale), senza dover scrivereif/switchespliciti. - Astrazione: l’interfaccia
Vendibilee la classe astrattaProdottorappresentano il concetto generale di “qualcosa che può essere venduta”, indipendentemente dai dettagli. In futuro potremmo aggiungere altri tipi di prodotti (es.ServizioInAbbonamento) semplicemente implementando l’interfaccia e, se serve, estendendoProdotto.
Questa architettura rende il codice più leggibile, più estendibile e più facile da manutenere. Puoi usare la stessa struttura per altri mini progetti Android in Java (gestione ordini, gestione utenti, ecc.), sostituendo i “prodotti” con le entità del tuo dominio.
Se vuoi fare un passo oltre, puoi provare a:
- Aggiungere un nuovo tipo di prodotto (es. “Servizio”) con una sua logica di prezzo.
- Mostrare più campi nella lista (es. data di creazione, quantità).
- Persistere i prodotti con Room o SharedPreferences mantenendo invariata la gerarchia OOP.
In questo modo consoliderai ancora di più la comprensione dei principi OOP applicati allo sviluppo Android in Java.
Rispondi