Si dispone di un numero di opzioni che tutte riducono per garantire che Class1 e Class2 utilizzino la stessa istanza dell '"oggetto condiviso".
- Esprimere espressamente la stessa istanza in Class1 e Class2 dall'esterno, anziché consentire a Class1 e Class2 di creare un'istanza autonomamente; oppure
- Fornire un inizializzatore singleton alla classe Functions, quindi assicurarsi che Class1 e Class2 lo utilizzino; oppure
- Utilizzare un modello di progettazione noto come modello di registro e assicurarsi che Class1 e Class2 ottengano la loro istanza della classe di funzioni dal registro; oppure
- Utilizzare l'iniezione di dipendenza (complessa).
Scenario (1) è il più semplice da comprendere poiché va esattamente così:
Functions *funcs = [[Functions alloc] init];
Class1 *obj1 = [[Class1 alloc] initWithFunctions:funcs];
Class2 *obj2 = [[Class2 alloc] initWithFunctions:funcs];
/* or alternatively use setters after initialization */
Scenario (2) è il seguente semplice ed è molto comune. Il cacao fornisce inizializzatori singleton a molte delle sue classi. Ogni volta che vedi +shared...
come prefisso per un inizializzatore, probabilmente restituisce un singleton.
@implementation Functions
+(id)sharedFunctions {
static Functions *sharedFunctions = nil;
@synchronized(self) {
if (sharedFunctions == nil) {
sharedFunctions = [[self alloc] init];
}
return sharedFunctions;
}
}
@end
La variabile statica viene inizializzata una sola volta, il che significa che è possibile pigramente caricare un'istanza dell'oggetto in esso mediante un controllo per il suo valore iniziale (che si verifica una sola volta). Ogni chiamata successiva al metodo restituisce la stessa istanza.
Functions *f1 = [Functions sharedFunctions];
Functions *f2 = [Functions sharedFunctions];
/* f1 == f2; */ // always
Opzione (3) non è visto molto in Objective-C rispetto ad in altre lingue e raggiunge più o meno gli stessi obiettivi come il Singleton. L'idea è che tu abbia un dizionario di oggetti che possono essere cercati per chiave. Tutto ciò che ha bisogno di accedere agli elementi nel registro richiede solo il registro per la propria istanza. In genere il registro stesso sarebbe un singleton.
L'opzione (4), l'iniezione di dipendenza, è in realtà una soluzione molto elegante con una serie di vantaggi a costo di complessità aggiuntiva. I benefici derivano dal fatto che hai assicurato che le dipendenze siano sempre liberamente accoppiate (il che rende lo swapping delle implementazioni, e l'unità testare le dipendenze in modo indipendente molto più semplice). La complessità deriva dai meccanismi non standard per il recupero delle istanze di ciò che è necessario.
L'iniezione di dipendenza ruota intorno all'idea che nessun oggetto istanzia le proprie dipendenze. Invece di fare affidamento su qualcos'altro per fornire quelle dipendenze. Quel "qualcos'altro" è noto come contenitore per l'iniezione di dipendenza. Un contenitore per le dipendenze è effettivamente un livello in cima al modello del registro, poiché il contenitore può contenere istanze predefinite delle dipendenze o saprà come istanziare nuove istanze.
Nel caso più semplice, l'iniezione di dipendenza è esattamente ciò che ho dimostrato nell'opzione (1). Non hai nemmeno bisogno di un contenitore per l'iniezione di dipendenza per raggiungere questo obiettivo.
Spostando verso l'alto un livello di complessità, l'iniezione di dipendenza introduce il concetto di un contenitore DI, che incapsula un numero di schemi di progettazione esistenti (il registro, come mostrato nell'opzione (3), il singleton (probabile, ma non strettamente) e la fabbrica (per sapere come creare nuove istanze di oggetti che gestisce)
La dimostrazione di un'implementazione completa di un contenitore DI verrebbe probabilmente oltre lo scopo di questa domanda, ma da un punto di vista dell'interfaccia pubblica, un'implementazione potrebbe essere simile a questa:
DIContainer *container = [DIContainer sharedContainer];
[container registerClass:[ClassA class]];
[container registerClass:[ClassB class]];
[container registerDependentProperty:@selector(setClassA:)
withInstanceOf:[ClassA class]
forClass:[ClassB class]];
ClassB *obj = [container makeInstanceOfClass:[ClassB class]];
NSLog(@"ClassB's -classA type = %@", [[obj classA] class]);
Ho appena digitato quello in cima alla mia testa in al centro di questo post, quindi non dare per scontato che sia accurato al 100%, ma ottieni il concetto. Il contenitore è stato informato che quando inizializza istanze di ClassB, deve quindi richiamare -setClassA:
, utilizzando un'istanza di ClassA
, che viene inizializzata anche in base alle regole definite nel contenitore (in questo caso non ci sono dipendenze da ClassA
quindi è solo restituisce un'istanza pianura
Se si prende niente di più lontano da questa risposta, basta ricordare le opzioni (1) e (2);.)
Cheers man, questo è davvero utile. Sono andato per l'opzione 2 e funziona bene. Le opzioni 3 e 4 mi sono un po 'un po' in su la testa, ma le rileggerò domani dopo aver dormito un po '. Potrebbe avere più senso al mattino. Grazie per l'aiuto, però, molto utile. :-D – Baza207
Nessun problema, penso che la tua scelta con l'opzione (2) sia buona.L'opzione (1) è "tecnicamente" la migliore in quanto il singleton ha una certa cattiva reputazione tra molti sviluppatori, a causa della difficoltà con cui può essere testato (ambiente di test non pulito). Tuttavia, usato correttamente è perfettamente a posto. In realtà, dato che Objective-C non ha un singolo inizializzatore/costruttore esclusivo come gli altri linguaggi, il singleton non è forzato al 100%, quindi può essere comunque lavorato in testing. L'opzione 3 è abbastanza semplice, ma sì, l'opzione (4) è un argomento che confonde anche gli sviluppatori esperti. – d11wtq
So che questa domanda è vecchia, ma questo mi ha totalmente aiutato. +1 –