2009-05-09 6 views
16

Dopo aver letto un sacco di blog, voci del forum e diversi documenti Apple, Non so ancora se una sottoclasse estesa in Objective-C sia una cosa saggia da fare o meno.La sottoclasse in Objective-C è una cattiva pratica?

Prendiamo ad esempio il caso seguente:

dire che sono lo sviluppo di un gioco di puzzle, che ha un sacco di elementi. Tutti gli elementi condividono un determinato numero di stesso comportamento. Poi, nel mio raccolta di elementi, diversi gruppi di elementi quota pari comportamento, gruppi di distinzione da gruppi, ecc ...

Così, dopo aver determinato quello che eredita da ciò, ho deciso di sottoclasse fuori di oblio. E perché non dovrei? Considerando il semplice comportamento generale del dispositivo con questo modello, I penso di aver realizzato qualcosa per OOP è pensato per.

Ma - e questa è la fonte della mia domanda - Apple cita utilizzando i delegati, i metodi di origini dati e protocolli informali a favore di sottoclassi. Mi fa davvero impazzire il perché?

Sembra che ci siano due campi. Quelli a favore di sottoclassi, quelli in fafor di no. Apparentemente dipende dal gusto personale. Mi chiedo quali sono i pro ei contro della sottoclasse massiva e non la sottoclasse massicciamente?

Per concludere, la mia domanda è semplice: Ho ragione? E perché o perché no?

+10

Kriem, Apple sta parlando dell'utilizzo di Cocoa in generale, non di qualcosa con Objective-C. Per ogni singolo progetto, dovrai decidere come impostare al meglio la tua applicazione e il codice base. Nel caso di Apple, hanno creato Cocoa (specialmente AppKit/UIKit) usando i paradigmi MVC e IoC, quindi suggeriscono di non creare sottoclassi di cose come NSControl, ecc. Quando invece si possono usare i delegati. Per riassumere: questo avviso è specifico per il framework Cocoa, non per Objecive-C in generale. –

+0

@ Jason: questa è una buona risposta, perché lasciarla come un semplice commento? – mouviciel

+0

@mouviciel - Non ci ho pensato, suppongo. Ora ci sono un certo numero di risposte che fondamentalmente dicono la stessa cosa, quindi non sembra valsa la pena aggiungere più rumore :) –

risposta

10

Personalmente, seguo questa regola: posso creare una sottoclasse se rispetta lo Liskov substitution principle.

+0

Buona informazione. Penso che si applichi al mio caso. Ma, - e questa è la fonte della mia domanda - Apple menziona l'uso di delegati, metodi di fonti di dati e protocolli informali a favore della sottoclasse. Mi fa davvero impazzire il perché? - Vorrei modificare la domanda per sottolineare questo. – Kriem

+1

Sospetto che sia molto più semplice utilizzare i delegati nella maggior parte dei casi, da qui la raccomandazione di Apple. La sottoclasse potrebbe essere appropriata, ma sarebbe più difficile ottenere il diritto. Solo la mia opinione. –

+1

Penso che la definizione sia terribilmente generica. Se leggi il mathese, quello che sostanzialmente sta dicendo è che qualsiasi cosa che usi già la superclasse dovrebbe essere in grado di usare la sottoclasse senza modifiche.Quindi, invece di aiutare a capire quando avere una sottoclasse, sta semplicemente affermando che dovresti impedire alle sottoclassi di rompere una classe. Questa definizione elimina totalmente intere categorie comuni di sottoclassi - come le superclassi astratte che non devono mai essere utilizzate direttamente, solo sottoclasse ... –

4

In generale, è necessario evitare la sottoclassi delle classi API se esistono delegati, ecc. Che soddisfano ciò che si desidera fare. Nel tuo codice la sottoclasse è spesso più bella, ma dipende davvero dai tuoi obiettivi, ad es. se stai fornendo un'API dovresti fornire un'API basata sul delegato piuttosto che ipotizzare una sottoclasse.

Quando si ha a che fare con le sottoclassi delle API, sono presenti più potenziali bug, ad es. se una classe nella gerarchia delle classi ottiene un nuovo metodo che ha lo stesso nome della tua aggiunta, fai delle cose da fare. Inoltre, se stai fornendo una funzione di tipo utile/di aiuto, c'è la possibilità che in futuro qualcosa di simile verrà aggiunto alla classe che stavi sottoclassi, e che potrebbe essere più efficiente, ecc. Ma la tua sovrascrittura la nasconderà.

3

Penso davvero che dipenda da cosa stai cercando di fare. Se il gioco di puzzle che descrivi nell'esempio contiene davvero un insieme di elementi unici che condividono attributi comuni e non ci sono classi fornite - ad esempio, "NSPuzzlePiece" - che soddisfano le tue esigenze, allora non vedo un problema con sottoclassi estensivamente.

Nella mia esperienza, delegati, metodi di origine dati e protocolli informali sono molto più utili quando Apple ha fornito una classe che fa già qualcosa di simile a ciò che si desidera che faccia.

Ad esempio, supponi di creare un'app che utilizza un tavolo.C'è (e parlo qui dell'SDK dell'iPhone, poiché è lì che ho esperienza) un UITableView di classe che fa tutte le piccole sfumature di creare una tabella per l'interazione con l'utente, ed è molto più efficiente definire un'origine dati per un istanza di UITableView piuttosto che completamente sottoclasse UITableView e ridefinire o estendere i suoi metodi per personalizzare il suo comportamento.

Concetti simili vanno per delegati e protocolli. Se riesci ad adattare le tue idee alle classi di Apple, in genere è più semplice (e funzionerà in modo più fluido) e utilizza l'origine dati, i delegati e i protocolli piuttosto che creare sottoclassi personalizzate. Ti aiuta a evitare il lavoro extra e a perdere tempo, e di solito è meno soggetto a errori. Le classi di Apple si sono occupate del business di rendere le funzioni efficienti e il debugging; più puoi lavorare con loro, meno errori avranno il tuo programma a lungo termine.

6

Non c'è niente di sbagliato nell'usare l'ereditarietà in Objective-C. Apple lo usa parecchio. Ad esempio, in Cocoa-touch, l'albero di ereditarietà di UIButton è UIControl: UIView: UIResponder: NSObject.

Penso che Martin abbia colto un punto importante nel menzionare il principio di sostituzione di Liskov. Inoltre, l'uso corretto dell'ereditarietà richiede che l'implementatore della sottoclasse abbia una profonda conoscenza della super classe. Se hai mai faticato per estendere una classe non banale in un quadro complesso, sai che c'è sempre una curva di apprendimento. Inoltre, i dettagli di implementazione della super classe spesso "filtrano" nella sottoclasse, il che è un grosso problema per @ $ & per i costruttori di framework.

Apple ha scelto di utilizzare la delega in molti casi per risolvere questi problemi; classi non banali come UIApplication espongono punti di estensione comuni attraverso un oggetto delegato, così la maggior parte degli sviluppatori ha sia una curva di apprendimento più semplice che un modo più agevole per aggiungere un comportamento specifico dell'applicazione - estendere direttamente l'applicazione di UIA è raramente necessario.

Nel tuo caso, per il codice specifico dell'applicazione, utilizza le tecniche che ti sono più adatte e che funzionano meglio per il tuo progetto. L'ereditarietà è un ottimo strumento se usato in modo appropriato.

Vedo spesso i programmatori di applicazioni trarre insegnamenti dai progetti di framework e provare ad applicarli al loro codice di applicazione (questo è comune nei mondi Java, C++ e Python nonché Objective-C). Mentre è bene pensare e capire le scelte fatte dai progettisti del framework, quelle lezioni non si applicano sempre al codice dell'applicazione.

7

La sottoclasse ha i suoi vantaggi, ma presenta anche alcuni inconvenienti. Come regola generale, cerco di evitare l'ereditarietà dell'implementazione e utilizzo invece l'ereditarietà e la delega dell'interfaccia.

Uno dei motivi per cui lo faccio è che quando si eredita l'implementazione, è possibile che si verifichino dei problemi se si annullano i metodi ma non si rispettano i loro contratti (a volte non documentati). Inoltre, trovo che le gerarchie delle classi di cammino con l'ereditarietà dell'implementazione siano difficili perché i metodi possono essere ignorati o implementati a qualsiasi livello. Infine, quando la sottoclasse è possibile solo ampliare un'interfaccia, non è possibile restringerla. Ciò porta a astrazioni che perdono. Un buon esempio di ciò è java.util.Stack che estende java.util.Vector. Non dovrei essere in grado di trattare una pila come un vettore. Ciò consente al consumatore di girare intorno all'interfaccia.

Altri hanno menzionato il principio di sostituzione di Liskov. Penso che l'utilizzo di questo avrebbe sicuramente risolto il problema di java.util.Stack ma potrebbe anche portare a gerarchie di classi molto profonde al fine di garantire che le classi ottengano solo i metodi che dovrebbero avere.

Invece, con l'ereditarietà dell'interfaccia non esiste essenzialmente alcuna gerarchia di classi poiché le interfacce raramente hanno bisogno di estendersi l'un l'altra.Le classi implementano semplicemente le interfacce di cui hanno bisogno e possono quindi essere trattate in modo corretto dal consumatore. Inoltre, poiché non esiste un'ereditarietà di implementazione, i consumatori di queste classi non ne dedurranno il comportamento a causa dell'esperienza precedente con una classe genitore.

Alla fine, però, non importa in che direzione vai. Entrambi sono perfettamente accettabili. È davvero più una questione di cosa ti senti più a tuo agio e quali sono le strutture con cui lavori incoraggiando. Come dice il vecchio proverbio: "Quando a Roma fai come fanno i romani".

12

La delega è un mezzo per utilizzare la tecnica di composizione per sostituire alcuni aspetti della codifica che altrimenti sarebbe sottoclasse. Come tale, si riduce alla vecchia questione del compito in questione che richiede una grande cosa che sa fare molto, o se si dispone di una rete di oggetti specializzati (un modello di responsabilità molto UNIX).

L'utilizzo di una combinazione di delegati e protocolli (per definire ciò che i delegati dovrebbero essere in grado di fare) offre una grande flessibilità di comportamento e facilità di codifica - tornando al principio di sostituzione di Liskov, quando si sottoclassi bisogna stare attenti a non fare nulla che un utente di tutta la classe troverebbe inaspettato. Ma se si sta semplicemente creando un oggetto delegato, si ha molto meno da essere responsabile, solo che i metodi delegati che implementate fanno ciò che richiede quel protocollo, oltre a ciò che non interessa.

Ci sono ancora molti buoni motivi per usare le sottoclassi, se si hanno veramente comportamenti e variabili condivisi tra un certo numero di classi può essere molto sensato sottoclasse. Ma se puoi sfruttare il concetto di delegato, spesso le tue lezioni saranno più facili da estendere o usare in modi che il progettista potrebbe non aspettarsi.

Tendo ad essere più un fan di protocolli formali che non informali, perché non solo i protocolli formali assicurano di avere i metodi che una classe ti tratta come previsto da un delegato, ma anche perché la definizione del protocollo è un luogo naturale per documentare ciò che ci si aspetta da un delegato che implementa tali metodi.

2

la mia impressione dell'enfasi di ADC rispetto alla sottoclasse ha più a che fare con l'eredità di come il sistema operativo si è evoluto ... indietro nel tempo (Mac Classic aka os9) quando C++ era l'interfaccia principale per la maggior parte mac toolbox, la subclassing era lo standard di fatto per un programmatore che modificava il comportamento delle funzionalità del sistema operativo comune (e questo era in effetti un problema al collo e significava che bisognava stare molto attenti che tutte le modifiche si fossero comportate in modo prevedibile e non ha infranto alcun comportamento standard).

questo detto, la mia impressione di enfasi di ADC contro la sottoclasse è non mettere avanti un caso per la progettazione di gerarchia delle classi di un'applicazione senza eredità, ma invece sottolineare che nel nuovo modo di fare le cose (cioè OSX) ci sono nella maggior parte dei casi mezzi più appropriati per andare sulla personalizzazione del comportamento standard senza bisogno di sottoclasse.

Quindi, con tutti i mezzi, progetta l'architettura del tuo programma di puzzle nel modo più efficace possibile, sfruttando l'ereditarietà come meglio credi!

Non vedo l'ora di vedere la tua nuova fantastica applicazione di puzzle!

| K <

+0

Questa è un'idea interessante, ma in realtà i principali principi di progettazione di Cocoa sono stati stabiliti molto prima che il C++ diventasse un linguaggio di programmazione comune per Mac OS. Il cacao ha l'accento sulla delega e la composizione da NEXTSTEP, che è stato progettato in questo modo perché i responsabili ritenevano che la composizione fosse un modo più affidabile per costruire grandi sistemi software da componenti riutilizzabili. E quell'idea va davvero fino a Smalltalk e il modello di design Model-View-Conroller. –

+0

C'è anche qualche elemento delle dolorose lezioni delle fragili classi base apprese nel progetto Taligent. Si è trattato di un progetto congiunto Apple-IBM che, in parte, ha cercato di creare una struttura generale per lo sviluppo di app. La metafora della loro gente, dei luoghi e delle cose merita di essere studiata solo per approfondire il concetto di struttura. Quel progetto è stato probabilmente la più grande dimostrazione di come C++ fallisce come linguaggio per la progettazione di framework fondamentali (lo dico come sviluppatore C++ professionale). –

+0

Correzione minore, C++ non è mai stata l'interfaccia principale della maggior parte degli strumenti MacOS. Era sempre C e Pascal. –

4

Si prega di leggere la documentazione di Apple "Adding behavior to a Cocoa program"!. Nella sezione "Eredita da una classe Cocoa", vedere il secondo paragrafo.Apple afferma chiaramente che la sottoclasse è il modo principale di aggiungere un comportamento specifico dell'applicazione al framework (si prega di notare, FRAMEWORK).
Il pattern MVC non impedisce completamente l'utilizzo di sottoclassi o sottotipi. Almeno non ho visto questa raccomandazione da parte di Apple o di altri (se mi sono perso, sentitevi liberi di indicarmi la giusta fonte di informazioni su questo). Se stai sottoclassi le classi API solo all'interno della tua applicazione, per favore vai avanti, nessuno ti ferma, ma fai in modo che non interrompa il comportamento della classe/API nel suo complesso. La sottoclasse è un ottimo modo per estendere le funzionalità dell'API del framework. Vediamo anche molte sottoclassi all'interno delle API di framework Apple IOS. Come sviluppatore ci si deve preoccupare che l'implementazione sia ben documentata e non duplicata accidentalmente da un altro sviluppatore. È un altro gioco con le palle se l'applicazione è un insieme di classi API che si prevede di distribuire come componente riutilizzabile.

IMHO, piuttosto che chiedere in giro quale sia la migliore pratica, prima leggere attentamente la relativa documentazione, implementarla e verificarla. Fai il tuo giudizio. Sai meglio di ciò che stai facendo. È facile per gli altri (come me e molti altri) leggere solo cose da diverse fonti sulla Rete e aggirare i termini. Sii il tuo giudice, ha funzionato per me finora.

0

Apple sembra in effetti scoraggiare passivamente la sottoclasse con Objective-C.

È un assioma del design OOP per favorire la composizione rispetto all'implementazione.