2012-06-17 10 views
9

Data una gerarchia di classe come segue:Come restituire una nuova istanza di sottoclasse durante l'inizializzazione della classe genitore?

class A 
    def initialize(param) 
    if param == 1 then 
     #initialize and return instance of B 
    else 
     #initialize and return instance of C 
    end 
    end 
end 

class B < A 
end 

class C < A 
end 

E 'possibile inizializzare in realtà e restituire un'istanza di B o C durante l'inizializzazione A? Cioè my_obj = A.new(param) risulterebbe in my_obj come un'istanza della classe B o C in base al valore di param, che viene controllato in A.initialize(param).

Nel mio caso d'uso il suo unico noto in fase di esecuzione che sottoclasse (o BC) da utilizzare e la classe padre (A) è praticamente mai usata veramente. Ho pensato che sarebbe una buona idea spostare la logica di decidere se B o C nel loro antenato comune.

Se ciò non è possibile (o in cattivo stile), dove devo inserire il controllo di param e la decisione della classe da inizializzare?

+1

Domande simili hanno avuto risposta prima.Quello che stai probabilmente cercando sono i metodi di fabbrica. Dai un'occhiata a http://stackoverflow.com/questions/1515577/factory-methods-in-ruby. Credo che la risposta su http://stackoverflow.com/a/1515580/1128705 soddisfi le tue esigenze. –

risposta

8

Stai rompendo un principio fondamentale OO qui - classi dovrebbero sapere nulla della loro sottoclassi. Certo, a volte i principi dovrebbero essere infranti, ma non c'è una ragione apparente per farlo qui.

Una soluzione molto migliore è spostare la logica di istanziazione in un metodo di fabbrica in una classe separata. Il metodo factory prende gli stessi argomenti dell'inizializzatore di A sopra e restituisce un'istanza della classe appropriata.

+0

Grazie per averlo detto ad alta voce. :) Ho finalmente scritto un metodo 'initiate_A' in un modulo separato, che prende la decisione e restituisce un nuovo oggetto. –

+0

Ci potrebbero essere uno o due casi in cui questo è appropriato però. Come ho detto nella mia risposta, cosa succede se si dispone di una classe parser, che deve affrontare, ad esempio, diverse versioni di una lingua? Potresti avere un costruttore che restituisce un'istanza della sottoclasse giusta per la corretta versione dei dati. HTML5 è HTML non è una buona relazione di ereditarietà, non so cosa sia. – Linuxios

+0

Siamo d'accordo l'uno con l'altro - questo principio può essere infranto quando ci sono buone ragioni per farlo. Non sono sicuro che mi piaccia il tuo esempio, sembra più un caso per Abstract Factory. – ComDubh

3

Il problema è che il valore di ritorno di initialize viene ignorato. ecco cosa succede quando si chiama A.new:

  • new chiama un metodo di classe speciale chiamata allocate - questo restituisce un'istanza vuota della classe
  • new poi chiama initialize sull'oggetto restituito da allocate, e restituisce l'oggetto

per fare quello che vuoi fare, è necessario eseguire l'override new e fargli fare ciò che si vuole:

class A 
    def self.new(args*) 
    if(args[0]==1) 
     B.new(*args[1..-1]) 
    else 
     C.new(*args[1..-1]) 
    end 
    end 
end 

C'è qualcos'altro da considerare però. Se lo A non viene mai utilizzato da solo, si dovrebbe usare una sorta di metodo factory o, solo una semplice istruzione if. Ad esempio:

def B_or_C(param,args) 
    (param == 1 ? B : C).new(args) 
end 

Il design dipende davvero da cosa li stai usando. Ad esempio, se hai una classe che potrebbe essere utilizzata per gestire più versioni di qualcosa, ad esempio HTML, potresti avere una classe principale HTMLParser che ha superato new e potrebbe restituire qualsiasi delle sue sottoclassi: HTML1Parser, HTML2Parser, HTML3Parser, HTML4Parser e HTML5Parser.

Nota: Devi eseguire l'override del metodo new al default nelle sottoclassi per evitare loop infinito:

def self.new(args*) 
    obj=allocate 
    obj.send(:initialize, *args) 
    obj 
end 
+0

Grazie per la tua risposta. Tuttavia, quando si esegue l'override di 'self.new' in' A' e si chiama 'A.new (params)', sto ricevendo un errore: 'SystemStackError: stack level too deep'. Sembra che chiamare "B.new' (o" C.new') all'interno di "A.new" causi un ciclo infinito poiché i "costruttori" di 'B' e' C' in qualche modo chiamano il "costruttore" di 'A', cioè' A.new'. –

+0

A causa del patrimonio –

+0

@Torbjoern: vedere Modifica. – Linuxios

Problemi correlati