Una quantità significativa di tempo, non riesco a pensare a un motivo per avere un oggetto invece di una classe statica. Gli oggetti hanno più benefici di quanto penso? [chiuso]


9

Capisco il concetto di un oggetto e, come programmatore Java, sento che il paradigma OO mi viene in pratica in modo naturale.

Tuttavia recentemente mi sono ritrovato a pensare:

Aspetta un secondo, quali sono in realtà i vantaggi pratici dell'utilizzo di un oggetto rispetto all'utilizzo di una classe statica (con incapsulamento corretto e pratiche OO)?

Potrei pensare a due vantaggi dell'utilizzo di un oggetto (entrambi sono significativi e potenti):

  1. Polimorfismo: consente di scambiare funzionalità in modo dinamico e flessibile durante il runtime. Consente inoltre di aggiungere facilmente nuove funzionalità "parti" e alternative al sistema. Ad esempio, se esiste una Carclasse progettata per funzionare con gli Engineoggetti e si desidera aggiungere un nuovo motore al sistema che l'automobile può utilizzare, è possibile creare una nuova Enginesottoclasse e semplicemente passare un oggetto di questa classe Carnell'oggetto, senza dover cambiare qualcosa Car. E puoi decidere di farlo durante il runtime.

  2. Essere in grado di "passare funzionalità": è possibile passare un oggetto attorno al sistema in modo dinamico.

Ma ci sono altri vantaggi per gli oggetti rispetto alle classi statiche?

Spesso quando aggiungo nuove 'parti' a un sistema, lo faccio creando una nuova classe e istanziando oggetti da esso.

Ma di recente, quando mi sono fermato a pensarci, mi sono reso conto che una classe statica avrebbe fatto lo stesso di un oggetto, in molti dei luoghi in cui normalmente uso un oggetto.

Ad esempio, sto lavorando per aggiungere un meccanismo di salvataggio / caricamento file alla mia app.

Con un oggetto, la riga chiamante del codice sarà simile alla seguente: Thing thing = fileLoader.load(file);

Con una classe statica, sarebbe simile al seguente: Thing thing = FileLoader.load(file);

Qual è la differenza?

Abbastanza spesso non riesco proprio a pensare a un motivo per istanziare un oggetto quando una semplice classe statica avrebbe agito allo stesso modo. Ma nei sistemi OO, le classi statiche sono abbastanza rare. Quindi mi manca qualcosa.

Ci sono altri vantaggi per gli oggetti diversi dai due che ho elencato? Spiega per favore.

EDIT: per chiarire. Trovo gli oggetti molto utili quando si scambiano funzionalità o si passano dati. Ad esempio ho scritto un'app che compone melodie. MelodyGeneratoraveva diverse sottoclassi che creano melodie in modo diverso e gli oggetti di queste classi erano intercambiabili (modello di strategia).

Anche le melodie erano oggetti, poiché è utile passarle in giro. Così erano gli accordi e le scale.

Ma che dire delle parti "statiche" del sistema - che non passeranno in giro? Ad esempio: un meccanismo "salva file". Perché dovrei implementarlo in un oggetto e non in una classe statica?


Quindi, invece di un oggetto con campi e forse metodi, nessun oggetto, nessun record, solo un sacco di valori scalari passati e restituiti da metodi statici? Modifica: il tuo nuovo esempio suggerisce diversamente: Oggetti, solo senza metodi di istanza? Altrimenti cosa Thing?

Cosa succede quando devi scambiare il tuo FileLoadercon uno che legge da un socket? O un finto per i test? O uno che apre un file zip?
Benjamin Hodgson,


1
@delnan Okay ho capito una domanda che la risposta mi aiuterà a capire: perché dovresti implementare una parte 'statica' del sistema come oggetto? Come nell'esempio nella domanda: un meccanismo di salvataggio del file. Cosa ottengo implementandolo in un oggetto?
Aviv Cohn,

1
L'impostazione predefinita dovrebbe essere l'uso di un oggetto non statico. Usa le classi statiche solo se ritieni che esprima davvero meglio le tue intenzioni. System.Mathin .NET è un esempio di qualcosa che ha molto più senso come classe statica: non avrai mai bisogno di cambiarlo o deriderlo e nessuna delle operazioni potrebbe logicamente far parte di un'istanza. Non credo proprio che il tuo esempio di "risparmio" si adatti a quel conto.
Benjamin Hodgson,

Risposte:


14

lol sembri una squadra su cui lavoravo;)

Java (e probabilmente C #) sicuramente supporta questo stile di programmazione. E lavoro con persone il cui primo istinto è: "Posso renderlo un metodo statico!" Ma ci sono alcuni costi sottili che ti raggiungono nel tempo.

1) Java è un linguaggio orientato agli oggetti. E fa impazzire i ragazzi funzionali, ma in realtà regge abbastanza bene. L'idea alla base di OO è raggruppare la funzionalità con lo stato per avere piccole unità di dati e funzionalità che preservano la loro semantica nascondendo lo stato ed esponendo solo le funzioni che hanno senso in quel contesto.

Passando a una classe con solo metodi statici, stai rompendo la parte "stato" dell'equazione. Ma lo stato deve ancora vivere da qualche parte. Quindi quello che ho visto nel tempo è che una classe con tutti i metodi statici inizia ad avere elenchi di parametri sempre più complicati, perché lo stato si sta spostando dalla classe alle chiamate di funzione.

Dopo aver creato una classe con tutti i metodi statici, esegui e controlla semplicemente quanti di questi metodi hanno un singolo parametro comune. Indica che quel parametro dovrebbe essere la classe contenente per quelle funzioni, oppure quel parametro dovrebbe essere un attributo di un'istanza.

2) Le regole per OO sono abbastanza ben comprese. Dopo un po ', puoi guardare un progetto di classe e vedere se soddisfa criteri come SOLID. E dopo molti test di unità pratica, sviluppi un buon senso di ciò che rende una classe "giusta" e "coerente". Ma non ci sono buone regole per una classe con tutti i metodi statici e nessuna vera ragione per cui non dovresti semplicemente raggruppare tutto lì dentro. La classe è aperta nel tuo editor, quindi che diamine? Aggiungi il tuo nuovo metodo lì. Dopo un po ', l'applicazione si trasforma in una serie di "God Objects" concorrenti, ognuno dei quali cerca di dominare il mondo. Ancora una volta, refactoring in unità più piccole è altamente soggettivo e difficile dire se hai capito bene.

3) Le interfacce sono una delle funzionalità più potenti di Java. L'ereditarietà delle classi si è rivelata problematica, ma la programmazione con le interfacce rimane uno dei trucchi più potenti del linguaggio. (idem C #) Le classi completamente statiche non possono inserirsi in quel modello.

4) Sbatte le porte a importanti tecniche OO di cui non puoi approfittare. Quindi potresti lavorare per anni con solo un martello nella tua cassetta degli attrezzi, senza rendersi conto di quanto sarebbero state più facili le cose se avessi avuto anche un cacciavite.

4.5) Crea le dipendenze tempo di compilazione più difficili e indistruttibili possibili. Quindi, ad esempio, se non lo hai, FileSystem.saveFile()non c'è modo di cambiarlo, a meno di falsificare la tua JVM in fase di esecuzione. Ciò significa che ogni classe che fa riferimento alla tua classe di funzione statica ha una dipendenza dura, in fase di compilazione da quella specifica implementazione, che rende quasi impossibile l'estensione e complica enormemente i test. È possibile testare la classe statica in isolamento, ma diventa molto difficile testare le classi che si riferiscono a quella classe in isolamento.

5) Farai impazzire i tuoi colleghi. La maggior parte dei professionisti con cui lavoro prende sul serio il proprio codice e presta attenzione ad almeno alcuni livelli di principi di progettazione. Mettere da parte l'intento principale di una lingua li farà strappare i capelli perché saranno costantemente refactoring il codice.

Quando sono in una lingua, cerco sempre di usare bene una lingua. Quindi, per esempio, quando sono in Java, uso una buona progettazione OO, perché in questo modo sto davvero sfruttando il linguaggio per quello che fa. Quando sono in Python, mescolo funzioni a livello di modulo con classi occasionali - potrei scrivere solo classi in Python, ma poi penso che non userei il linguaggio per quello che è bravo.

Un'altra tattica è usare male una lingua e si lamentano di tutti i problemi che causa. Ma questo vale praticamente per qualsiasi tecnologia.

La caratteristica chiave di Java è la gestione della complessità in piccole unità testabili che si uniscono in modo da essere facilmente comprensibili. Java enfatizza le chiare definizioni dell'interfaccia indipendentemente dall'implementazione, il che è un enorme vantaggio. Ecco perché (e altre lingue OO simili) rimangono così ampiamente utilizzati. Con tutta la verbosità e il ritualismo, quando ho finito con una grande app Java, ho sempre la sensazione che le idee siano separate in modo più chiaro nel codice rispetto ai miei progetti in un linguaggio più dinamico.

È difficile, però. Ho visto persone ottenere il bug "tutto statico", ed è un po 'difficile discuterne. Ma li ho visti avere un grande senso di sollievo quando lo superano.


Parli principalmente di mantenere le cose piccole e modulari e di mantenere insieme stato e funzionalità. Non c'è niente che mi impedisce di farlo con le classi statiche. Possono avere uno stato. E puoi incapsularli con getter-setter. E puoi dividere il sistema in piccole classi che incapsulano funzionalità e stato. Tutto ciò vale sia per le classi che per gli oggetti. E questo è esattamente il mio dilemma: supponiamo di aggiungere nuove funzionalità alla mia app. Ovviamente andrà in una classe separata per obbedire a SRP. Ma perché dovrei istanziare esattamente questa lezione? Questo è quello che non capisco.
Aviv Cohn,

Voglio dire: a volte è chiaro perché voglio oggetti. Userò l'esempio dalla modifica alla mia domanda: avevo un'app che compone melodie. C'erano diverse scale che potevo collegare a MelodyGenerators per generare melodie diverse. E le Melodie erano oggetti, quindi potevo metterli in una pila e tornare a suonare quelli vecchi, ecc. In casi come questi, mi è chiaro perché dovrei usare gli oggetti. Quello che non capisco è perché dovrei usare gli oggetti per rappresentare parti "statiche" del sistema: ad esempio un meccanismo di salvataggio dei file. Perché non dovrebbe essere solo una classe statica?
Aviv Cohn,

Una classe con metodi statici deve esternalizzare il suo stato. Che, a volte, è una tecnica di factoring molto importante. Ma la maggior parte delle volte vuoi incapsulare lo stato. Un esempio concreto: anni fa un collega ha scritto un "selettore di date" in Javascript. Ed è stato un incubo. I clienti si sono lamentati costantemente. Così l'ho rifattorizzato in oggetti "calendario", e improvvisamente ne ho istanziato molti, mettendoli fianco a fianco, saltando tra mesi e anni, ecc. Era così ricco che abbiamo dovuto disattivare le funzionalità. L'istanziazione ti dà quella scala.
Rob,

3
"L'idea alla base di OO è raggruppare la funzionalità con lo stato per avere piccole unità di dati e funzionalità che preservano la loro semantica nascondendo lo stato ed esponendo solo le funzioni che hanno senso in quel contesto." Questo è fondamentalmente sbagliato. OO non implica uno stato mutabile e l'astrazione non è esclusiva di OO. Esistono due modi per raggiungere l'astrazione e gli oggetti sono solo uno di questi (l'altro è un tipo di dati astratto).
Doval,

1
@Doval Non capisco perché ogni discussione su OO debba necessariamente trasformarsi in una discussione sulla programmazione funzionale.
Rob

6

Hai chiesto:

Ma ci sono altri vantaggi per gli oggetti rispetto alle classi statiche?

Prima di questa domanda, hai elencato il polimorfismo e ti sei passato come due vantaggi dell'uso degli oggetti. Voglio dire che queste sono le caratteristiche del paradigma OO. L'incapsulamento è un'altra caratteristica del paradigma OO.

Tuttavia, questi non sono i vantaggi. I vantaggi sono:

  1. Astrazioni migliori
  2. Migliore manutenibilità
  3. Migliore testabilità

Tu hai detto:

Ma di recente, quando mi sono fermato a pensarci, mi sono reso conto che una classe statica avrebbe fatto lo stesso di un oggetto, in molti dei luoghi in cui normalmente uso un oggetto.

Penso che tu abbia un punto valido lì. Alla base, la programmazione non è altro che trasformazione dei dati e creazione di effetti collaterali basati sui dati. A volte la trasformazione dei dati richiede dati ausiliari. Altre volte no.

Quando si ha a che fare con la prima categoria di trasformazioni, i dati ausiliari devono essere passati come input o memorizzati da qualche parte. Un oggetto è l'approccio migliore che le classi statiche per tali trasformazioni. Un oggetto può archiviare i dati ausiliari e usarli al momento giusto.

Per la seconda categoria di trasformazioni, una classe statica è buona come un Oggetto, se non migliore. Le funzioni matematiche sono esempi classici di questa categoria. La maggior parte delle funzioni standard delle librerie C rientrano in questa categoria.

Hai chiesto:

Con un oggetto, la riga chiamante del codice sarà simile alla seguente: Thing thing = fileLoader.load(file);

Con una classe statica, sarebbe simile al seguente: Thing thing = FileLoader.load(file);

Qual è la differenza?

Se FileLoadernon è mai necessario archiviare alcun dato, sceglierei il secondo approccio. Se esiste la possibilità che siano necessari dati ausiliari per eseguire l'operazione, il primo approccio è una scommessa più sicura.

Hai chiesto:

Ci sono altri vantaggi per gli oggetti diversi dai due che ho elencato? Spiega per favore.

Ho elencato i vantaggi (vantaggi) dell'utilizzo del paradigma OO. Spero che siano autoesplicativi. Altrimenti, sarò felice di elaborare.

Hai chiesto:

Ma che dire delle parti "statiche" del sistema - che non passeranno in giro? Ad esempio: un meccanismo "salva file". Perché dovrei implementarlo in un oggetto e non in una classe statica?

Questo è un esempio in cui una classe statica semplicemente non lo farà. Esistono molti modi per salvare i dati dell'applicazione in un file:

  1. Salvalo in un file CSV.
  2. Salvalo in un file XML.
  3. Salvalo in un file json.
  4. Salvalo come file binario, con dump diretto dei dati.
  5. Salvalo direttamente su una tabella in un database.

L'unico modo per fornire tali opzioni è creare un'interfaccia e creare oggetti che implementano l'interfaccia.

In conclusione

Usa l'approccio giusto per il problema in questione. È meglio non essere religiosi riguardo a un approccio rispetto all'altro.


Inoltre, se molti diversi tipi di oggetti (classi) necessità di essere salvato (ad esempio Melody, Chord, Improvisations, SoundSamplee altri), allora sarà economico per semplificare l'implementazione tramite astrazioni.
rwong

5

Dipende dalla lingua e dal contesto, a volte. Ad esempio, gli PHPscript utilizzati per le richieste di manutenzione hanno sempre una richiesta di servizio e una risposta da generare per tutta la durata dello script, pertanto potrebbero essere appropriati metodi statici per agire sulla richiesta e generare una risposta. Ma in un server su cui è scritto Node.js, potrebbero esserci molte richieste e risposte diverse contemporaneamente. Quindi la prima domanda è: sei sicuro che la classe statica corrisponda davvero a un oggetto singleton?

In secondo luogo, anche se disponi di singoli, l'utilizzo degli oggetti ti consente di sfruttare il polimorfismo usando tecniche come Dependency_injection e Factory_method_pattern . Questo è spesso usato in vari schemi Inversion_of_control e utile per creare oggetti finti per test, registrazione, ecc.

Hai citato i vantaggi di cui sopra, quindi eccone uno: eredità. Molte lingue non hanno la possibilità di sovrascrivere i metodi statici allo stesso modo in cui i metodi di istanza possono essere sovrascritti. In generale, è molto più difficile ereditare e sovrascrivere i metodi statici.


Grazie per aver risposto. Secondo te: quando si crea una parte "statica" del sistema, qualcosa che non verrà "superato", ad esempio la parte del sistema che riproduce i suoni o la parte del sistema che salva i dati in un file: dovrei implementarlo in un oggetto o in una classe statica?
Aviv Cohn,

3

Oltre al post di Rob Y.


Finché la funzionalità del tuo load(File file)può essere chiaramente separata da tutte le altre funzioni che usi, è bene usare un metodo / classe statici. Esternalizzi lo stato (cosa non negativa) e non ottieni ridondanza in quanto, ad esempio, puoi applicare parzialmente o modificare la tua funzione in modo da non doverti ripetere. (che è in realtà lo stesso o simile all'utilizzo di un factorymodello)

Tuttavia, non appena due di queste funzioni iniziano ad avere un uso comune, si desidera essere in grado di raggrupparle in qualche modo. Immagina di avere non solo una loadfunzione ma anche una hasValidSyntaxfunzione. Cosa farai?

     if (FileLoader.hasValidSyntax(myfile))
          Thing thing = FileLoader.load(myfile);
     else
          println "oh noes!"

Vedi i due riferimenti a myfilequi? Inizi a ripetere te stesso perché il tuo stato esternalizzato deve essere passato per ogni chiamata. Rob Y ha descritto come interiorizzare lo stato (qui il file) in modo da poterlo fare in questo modo:

     FileLoader myfileLoader = new FileLoader(myfile)
     if (myfileLoader.hasValidSyntax())
          Thing thing = myfileLoader.load();
     else
          println "oh noes!"

Dover passare due volte nello stesso oggetto è un sintomo superficiale; il vero problema che potrebbe indicare è che alcuni lavori vengono eseguiti in modo ridondante: il file potrebbe dover essere aperto due volte, analizzato due volte e chiuso due volte, se hasValidSyntax()e load()non è consentito riutilizzare lo stato (il risultato di un file parzialmente analizzato).
rwong

2

Un grosso problema quando si usano le classi statiche è che ti costringono a nascondere le tue dipendenze e ti costringono a dipendere dalle implementazioni. Nella seguente firma del costruttore, quali sono le tue dipendenze:

public Person(String name)

Bene, a giudicare dalla firma, una persona ha solo bisogno di un nome, giusto? Bene, non se l'implementazione è:

public Person(String name) {
    ResultSet rs = DBConnection.getPersonFilePathByName(name);
    File f = FileLoader.load(rs.getPath());
    PersonData.setDataFile(f);
    this.name = name;
    this.age = PersonData.getAge();
}

Quindi una persona non è solo istanziata. In realtà estraiamo i dati da un database, che ci fornisce un percorso per un file, che quindi deve essere analizzato e quindi i dati reali che vogliamo devono essere presi in giro. Questo esempio è chiaramente esagerato, ma dimostra il punto. Ma aspetta, ci può essere molto di più! Scrivo il seguente test:

public void testPersonConstructor() {
    Person p = new Person("Milhouse van Houten");
    assertEqual(10, p.age);
}

Questo dovrebbe passare, giusto? Voglio dire, abbiamo incapsulato tutte le altre cose, giusto? Bene, in realtà esplode con un'eccezione. Perché? Oh, sì, le dipendenze nascoste di cui non sapevamo hanno uno stato globale. I DBConnectionbisogni inizializzati e connessi. I FileLoaderbisogni inizializzati con un FileFormatoggetto (come XMLFileFormato CSVFileFormat). Mai sentito parlare di quelli? Bene, questo è il punto. Il tuo codice (e il tuo compilatore) non possono dirti che hai bisogno di queste cose perché le chiamate statiche nascondono quelle dipendenze. Ho detto test? Volevo dire che il nuovo sviluppatore junior ha appena spedito qualcosa del genere nella tua ultima versione. Dopotutto, compilato = funziona, giusto?

Inoltre, supponi di essere su un sistema in cui non è in esecuzione un'istanza di MySQL. Oppure, supponi di essere su un sistema Windows ma la tua DBConnectionclasse esce solo sul tuo server MySQL su un box Linux (con percorsi Linux). Oppure, supponiamo di essere su un sistema in cui il percorso restituito da DBConnectionnon è in lettura / scrittura per te. Ciò significa che il tentativo di eseguire o testare questo sistema in una di queste condizioni fallirà, non a causa di un errore del codice, ma a causa di un errore di progettazione che limita la flessibilità del codice e ti lega a un'implementazione.

Ora, diciamo, vogliamo registrare ogni chiamata al database per Personun'istanza specifica , una che attraversa un certo percorso problematico. Potremmo inserire il login DBConnection, ma questo registrerà tutto , mettendo un sacco di disordine e rendendo difficile distinguere il particolare percorso del codice che vogliamo tracciare. Se, tuttavia, stiamo usando l'iniezione di dipendenza con DBConnectionun'istanza, potremmo semplicemente implementare l'interfaccia in un decoratore (o estendere la classe, poiché entrambe le opzioni sono disponibili con un oggetto). Con una classe statica, non possiamo iniettare la dipendenza, non possiamo implementare un'interfaccia, non possiamo avvolgerla in un decoratore e non possiamo estendere la classe. Possiamo solo chiamarlo direttamente, da qualche parte nascosto nel profondo del nostro codice. Quindi siamo costretti avere una dipendenza nascosta da un'implementazione.

È sempre male? Non necessariamente, ma potrebbe essere meglio invertire il tuo punto di vista e dire "C'è una buona ragione per cui non dovrebbe essere un'istanza?" piuttosto che "C'è una buona ragione per cui dovrebbe essere un'istanza?" Se puoi davvero dire che il tuo codice sarà immutabile (e senza stato) come Math.abs(), sia nella sua implementazione che nel modo in cui verrà utilizzato, allora puoi considerare di renderlo statico. Avere istanze su classi statiche, tuttavia, ti dà un mondo di flessibilità che non è sempre facile da recuperare dopo il fatto. Può anche darti più chiarezza sulla vera natura delle dipendenze del tuo codice.


Le dipendenze nascoste sono estremamente valide. Ho quasi dimenticato il mio linguaggio che "la qualità del codice dovrebbe essere giudicata dal modo in cui viene utilizzato, non dal modo in cui è implementato".
Euforico,

Ma dipendenze nascoste possono esistere anche in una classe non statica, giusto? Quindi non vedo perché questo sarebbe uno svantaggio di classi statiche ma non di classi non statiche.
valenterry,

@valenterry Sì, possiamo nascondere anche dipendenze non statiche, ma il punto chiave è che nascondere dipendenze non statiche è una scelta . Potrei newaccumulare un mucchio di oggetti nel costruttore (che non è generalmente consigliabile), ma posso anche iniettarli. Con le classi statiche, non c'è modo di iniettarle (non facilmente in Java comunque, e sarebbe un'altra discussione per i linguaggi che lo consentono), quindi non c'è scelta. Ecco perché metto in evidenza l'idea di essere costretto a nascondere le dipendenze così pesantemente nella mia risposta.
cbojar,

Ma puoi iniettarli ogni volta che chiami il metodo fornendo loro come argomenti. Quindi non sei costretto a nasconderli ma costretti a renderli espliciti in modo che tutti vedano "aha, quel metodo dipende da questi argomenti" piuttosto che "aha, quel metodo dipende da questi (parte di) argomenti e da alcuni stati interali nascosti che non conoscere".
valenterry,

In Java, per quanto ne so, non è possibile fornire una classe statica come parametro senza passare attraverso un'intera rigmarole di riflessione. (Ho provato in tutti i modi che mi viene in mente. Se hai un modo, mi piacerebbe vederlo.) E se scegli di farlo, essenzialmente stai sovvertendo il sistema di tipi incorporato (il parametro è un Class, quindi ci assicuriamo che sia la classe giusta) o che tu stia scrivendo anatra (ci assicuriamo che risponda al metodo giusto). Se si desidera eseguire quest'ultima operazione, è necessario rivalutare la scelta della lingua. Se vuoi fare il primo, le istanze vanno bene e sono integrate.
cbojar

1

Solo i miei due centesimi.

Per la tua domanda:

Ma che dire delle parti "statiche" del sistema - che non passeranno in giro? Ad esempio: un meccanismo "salva file". Perché dovrei implementarlo in un oggetto e non in una classe statica?

Puoi chiederti se il meccanismo della parte del sistema che riproduce i suoni o la parte del sistema che salva i dati in un file CAMBIERÀ . Se la tua risposta è sì, ciò indica che dovresti astrarli usando abstract class/ interface. Potresti chiedere, come posso sapere le cose future? Sicuramente, non possiamo. Quindi, se la cosa è apolide, puoi usare una "classe statica" come java.lang.Math, altrimenti, usare un approccio orientato agli oggetti.


C'è un consiglio ironico: tre colpi e tu refactoring , il che suggerisce che uno può resistere fino a quando il cambiamento è effettivamente necessario. Ciò che intendo per "cambiamento" è: è necessario salvare più di un tipo di oggetto; o necessità di salvare in più di un formato di file (tipo di archiviazione); o un drastico aumento della complessità dei dati salvati. Naturalmente, se si è abbastanza certi che il cambiamento avverrà presto, si può incorporare un design più flessibile in anticipo.
rwong
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.