Un wrapper (dal verbo inglese to wrap, "avvolgere") o funzione wrapper (in italiano talvolta definita funzione-involucro o funzione di confezionamento), in informatica e in particolare nella programmazione software, è un modulo o una funzione il cui scopo principale consiste nel fungere da tramite chiamando un secondo sottoprogramma, una classe o una chiamata di sistema, eseguendo al contempo poche o nessuna elaborazione aggiuntiva.
A differenza di una generica funzione, un wrapper opera come delegato, "rivestendo" il componente software originale in modo che i client interagiscano esclusivamente con l'interfaccia esposta dal wrapper stesso, mentre l'effettiva esecuzione del servizio viene demandata al modulo sottostante. Questo principio di astrazione e delega può essere applicato a singoli sottoprogrammi (come funzioni e metodi) oppure a interi tipi di dati, classi, oggetti e librerie software.
L'uso dei wrapper semplifica la scrittura di programmi e migliora il disaccoppiamento del codice, in particolare quando si integrano librerie di terze parti. Creando una funzione wrapper per ciascuna delle funzioni fornite dalla libreria esterna, l'applicazione nativa interagirà solo con i wrapper. Qualora la libreria di terze parti venga aggiornata o modificata (ad esempio, con cambi di firma o di parametri nelle sue funzioni), lo sviluppatore dovrà modificare unicamente i wrapper interni, senza dover rintracciare e correggere ogni singola istanza nel codice dell'applicazione. Un esempio di questa astrazione è rappresentato dalle classi di tipo MouseAdapter nella libreria Java AWT.
I wrapper consentono di convertire l'interfaccia di una classe o di un oggetto esistente in un'altra interfaccia attesa dai client, garantendo la compatibilità tra componenti altrimenti non integrabili. Questo concetto si ricollega direttamente al design pattern strutturale noto come Adapter (lett. adattatore).
Scrivere controlli di errore e procedure di verifica per ogni singola chiamata a funzioni di sistema può appesantire notevolmente la lettura del codice. Sostituendo le chiamate dirette con chiamate a una funzione wrapper, i controlli di sicurezza e la gestione delle eccezioni possono essere scritti una sola volta all'interno del wrapper stesso, sollevando il programmatore dal ripeterli nel flusso principale del programma.
Nei linguaggi di programmazione che non supportano l'ereditarietà multipla delle classi (come Java), i wrapper e il meccanismo della delega permettono di simularne il comportamento. Una classe può implementare più interfacce contemporaneamente e delegare l'effettiva esecuzione dei metodi a istanze interne di classi diverse, avvolgendole.
Molte funzioni presenti nelle librerie standard, come la Libreria standard del C (ad esempio all'interno di glibc), agiscono come interfacce e wrapper per le chiamate di sistema (system call) di basso livello del sistema operativo. Funzioni come fork ed execve in glibc chiamano internamente le rispettive e omonime chiamate di sistema del kernel, semplificando l'interazione per il programmatore.
Una funzione di supporto (helper function) è un tipo di wrapper che raggruppa parti di un calcolo o operazioni frequenti assegnando loro nomi descrittivi e facilitandone il riutilizzo. Sebbene non tutti i wrapper siano funzioni di supporto, tutte le funzioni di supporto sono strutturalmente dei wrapper. Esse trovano largo impiego, ad esempio, nella traduzione binaria dinamica per convertire le istruzioni da un set di architettura a un altro.
I wrapper facilitano l'interoperabilità e il riutilizzo del codice in ambienti software eterogenei. Strumenti come SWIG (Simplified Wrapper and Interface Generator) o GNU Guile consentono di generare automaticamente wrapper per collegare e utilizzare librerie scritte in un linguaggio (come C o C++) all'interno di altri linguaggi di programmazione, risolvendo i problemi di binding.
Il seguente esempio in linguaggio Java mostra come una classe (StackSet) possa simulare l'ereditarietà multipla implementando due interfacce diverse (Stack e Set). La classe agisce come un wrapper, delegando internamente l'effettiva logica e memorizzazione dei dati a due oggetti standard della libreria Java (LinkedList e HashSet):
import java.util.HashSet;
import java.util.LinkedList;
// Definizione dell'interfaccia Stack
interface Stack {
boolean push(Object o);
Object pop();
}
// Definizione dell'interfaccia Set
interface Set {
boolean contains(Object o);
}
// Classe wrapper che implementa entrambe le interfacce simulando l'ereditarietà multipla
public class StackSet implements Stack, Set {
// Componenti interni "avvolti" dal wrapper
private LinkedList stack = new LinkedList();
private HashSet set = new HashSet();
// Metodo wrapper che delega l'operazione di push a HashSet e LinkedList
@Override
public boolean push(Object o) {
if (set.add(o)) {
return stack.push(o); // Delega alla struttura LinkedList sottostante
} else {
return false;
}
}
// Metodo wrapper che delega la rimozione dell'elemento
@Override
public Object pop() {
Object o = stack.pop(); // Delega a LinkedList
set.remove(o); // Delega a HashSet
return o;
}
// Metodo wrapper che delega la verifica di presenza del dato
@Override
public boolean contains(Object o) {
return set.contains(o); // Delega a HashSet
}
}