C++, Objective-C, Smalltalk: “estendere” una classe

byte_1981_08

Qualche anno fa (sicuramente meno di 10) scoprii l’Objective-C e mi piacque molto. Ci sono almeno due cose da considerare: ① non sono mai stato un amante del C++ (potendo ho sempre provato ad evitarlo; ho iniziato ad usarlo per me stesso giusto con il C++11); ② per non ricordo quale motivo mi ero imbattuto già nello Smalltalk.

Rispetto al C++, l’Objective-C mi sembrò un modo migliore di costruire un linguaggio object oriented sulle fondamenta del C. A molti alcune sue peculiarità sintattiche risultano ostiche o aliene; le cose gli apparirebbero in modo diverso se avessero assaggiato e digerito lo Smalltalk…

Quando si dice Objective-C si pensa alla Apple e ai vari iCosi; l’associazione è legittima, ma il linguaggio è in realtà indipendente da quel mondo e naturalmente anche dalle classi “standard” che fornisce e senza le quali bisognerebbe reinvetare un bel po’ di cosette: immaginate il C++ senza le STL… Comunque si risolve con GNUstep.

(A proposito di Objective-C e Apple: è chiaro che la popolarità dell’Objective-C, purtroppo per lui, veniva proprio dal fatto che fosse necessario per programmare i vari iCosi, al punto che ora la sua stella è un po’ oscurata da Swift… le nuove iGenerazioni vedranno l’Objective-C solo nelle foto del nonno?)

Cose scritte in Objective-C che non appartengano all’iMondo? C’è Oolite, chiaramente “ispirato” a Elite, di cui ricordo la versione Amiga.

screen1

Il C++ non può, l’Objective-C sì

In Objective-C è possibile estendere una classe esistente. Guarda il caso, è una cosa che si può fare pure in Smalltalk.

Il “problema” esemplificativo è questo: voglio avere un metodo che cripti (e decripti) una stringa secondo un certo algoritmo. Quando dico stringa, intendo la classe stringa, non una stringa del C… Come avete già imparato da questo blog, in Smalltalk tutto è un oggetto; non è così in C++ e nemmeno in Objective-C, ma tutti e due hanno sia il “tipo primitivo” stringa C, sia la classe “stringa” — std::string in C++ con le STL, e NSString in Objetive-C usando un framework simil-“OpenStep”.

Ora, una stringa è una stringa e non vedo perché dovrei creare una classe ad hoc: cryptablestring? Se poi voglio aggiungere un’altra funzionalità, creo un’altra classe? E come la chiamo? Se poi le voglio usare assieme sfrutto l’ereditarietà multipla facendo un’altra classe ancora?

#include <string>

class cryptablestring : public std::string
{
public:
    cryptablestring(const char* s) :
        std::string(s)
        // ... altre inizializzazioni ...
    {
        // ...
    }

    void crypt() {
        // ...
    }

    void decrypt() {
        // ...
    }
    // ...
};

Bello quanto vi pare, ma perché una cosa che a conti fatti è una stringa non appartiene alla classe stringa? D’accordo, è una classe derivata e alla fine possiamo usarla praticamente come una stringa… però…

std::string esclamante(const std::string& in)
{
    std::string s(in);
    s.append(1, '!');
    return s;
}

// ...

int main()
{
    cryptablestring a("test");
    a.crypt();
    std::cout << a << "\n";
    a.decrypt();
    std::cout << a << "\n";
    auto r = esclamante(a);
    r.crypt(); // ...... errore .......
    // ...
}

Per la verità in parte si risolve con un template:

template<typename T>
T fai_qualcosa(const T& in)
{
    T s(in);
    s.append(1, '!');
    return s;
}

Poi auto r = esclamante(a) ci dà in realtà una cryptablestring per la quale r.crypt() funziona. Ma se la funzione esclamante non l’abbiamo scritta noi e restituisce solo una std::string?

Potremmo usare static_assert e i type_traits visti per assicurarci che T abbia std::string come classe base (is_base_of), tanto per rendere chiaro che certo codice generato dal template, nel caso usassimo fai_qualcosa con l’argomento sbagliato, non può essere compilato.

Ma a questo punto non è meglio fare una funzione crypt che cripta una stringa?

std::string& crypt(std::string& s)
{
    // ...
    return s;
}

Un approccio classico non nasconde il fatto che la stringa resta una stringa, anche se il suo contenuto è stato alterato da crypt (potremmo invece mettere const in ingresso e restituire una stringa nuova di zecca).

In Objective-C abbiamo le categorie (category), che ci permettono di aggiungere funzionalità ad una classe esistente, per esempio a NSString o, meglio (nel nostro caso), a NSMutableString (perché NSString è immutabile).

@interface NSMutableString (CryptableString)
- (NSMutableString *)crypt;
@end

// ...

@implementation NSMutableString (CryptableString)
- (NSMutableString *)crypt {
    // ...
    return self;
}
@end

Ora ogni istanza della classe standard NSMutableString può essere criptata tramite crypt; ma per il resto non ci dobbiamo preoccupare di niente perché abbiamo proprio una NSMutableString indistinguibile dalle altre.

Con l’approccio “immutabile” avremmo il vantaggio di poter scrivere cose come:

NSString *p = [@"testo da criptare" crypt];

Mentre per l’approccio “funzione” userei ancora una volta una categoria per aggiungere alla classe NSString un metodo statico che dà una stringa criptata costruita da una stringa:

@interface NSString (CryptPack)
+ (NSString *)cryptedStringFromString: (NSString *)aString;
@end

@implementation NSString (CryptPack)
+ (NSString *)cryptedStringFromString: (NSString *)aString {
    // ... crea la NSString* s, criptando aString ...
    return s;
}
@end

L’uso sarebbe semplicemente:

[NSString cryptedStringFromString: @"hello"]

Per dire.

164933611_63d255e973

E lo Smalltalk

In realtà faccio riferimento alla “variante” GNU Smalltalk anche perché questa, a differenza di altre, è file based, cioè scriviamo il codice in un editor come faremmo con qualunque altro linguaggio di programmazione. Offre poi un po’ di zucchero sintattico, che non fa mai male.

Visto che lo Smalltalk è uno di quei linguaggi sottorappresentati (poco male) e sottostimati (male), faccio un esempio un po’ più completo.

Estendiamo due classi: Character, per poter scrivere c + 1 o cose simili (quando c è un Character), e String, che ci fornisce il nuovo metodo crypt.

    Character extend [
      + anInteger [
         ^ Character codePoint: (self asInteger + anInteger)
      ]
    ]
     
    String extend [
      crypt [
         ^ self collect:
             [:c|
               ((c isLetter) & ((c+1) isLetter))
                ifTrue: [c+1]
                ifFalse: [c]
             ]
      ]
    ]
     
    'Ciao ok panico' crypt displayNl.

Djbp pl qbojdp.

Ancora sulle categorie

Una categoria nell’Objective-C può anche sostituire un metodo esistente e a quel punto qualunque parte del codice usi quel certo selettore, chiamerà il metodo della categoria e non più l’originale: in modo trasparente possiamo cambiare il comportamento di tutto un programma. Forse non è una buona idea se non si hanno dei motivi più che validi per volerlo fare…

Poiché l’Objective-C ci permette di manipolare a runtime le informazioni di runtime che vengono usate per esempio per chiamare il metodo giusto, possiamo fare cose interessanti come il method swizzling; nel link mi sembra spiegato bene, insieme ad altri aspetti e concetti dell’Objective-C.

Interessante o meno, il method swizzling ci permette di poter accedere all’implementazione “vecchia” di un metodo, quindi è più che altro utile quando una categoria usa un selettore già esistente.

Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch_station_sign_(cropped_version_1)

Posta un commento o usa questo indirizzo per il trackback.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: