Gli oggetti costruiti dalla stessa classe possono avere definizioni di metodi univoche?


14

So che questa sembra una domanda strana, dal momento che il punto in cui due o più oggetti condividono la stessa classe è che il loro comportamento è lo stesso, cioè i loro metodi sono identici.

Tuttavia, sono curioso di sapere se ci sono linguaggi OOP che ti consentono di ridefinire i metodi degli oggetti nello stesso modo in cui puoi assegnare valori diversi per i loro campi. Il risultato sarebbero oggetti costruiti dalla stessa classe che non presentano più lo stesso identico comportamento.

Se non sbaglio, puoi fare questo JavaScript? Insieme a questa domanda, chiedo, perché qualcuno dovrebbe voler fare questo?


9
Il termine tecnico è "basato sui prototipi". La ricerca ti darà molto materiale su questo sapore di OOP. (Ricorda che molte persone non considerano tali strutture come "classi", proprio perché non hanno attributi e metodi uniformi.)
Kilian Foth,

1
vuoi dire come due diverse istanze di un pulsante potrebbero fare cose diverse quando vengono cliccate, perché ciascuna è collegata a un gestore di pulsanti diverso? Naturalmente, si potrebbe sempre sostenere che fanno la stessa cosa: chiamano il loro gestore dei pulsanti. Ad ogni modo, se la tua lingua supporta delegati o puntatori a funzioni, questo è facile: hai qualche proprietà / campo / variabile membro che contiene il delegato o puntatore a funzione e l'implementazione del metodo chiama questo e via.
Kate Gregory,

2
È complicato :) Nei linguaggi in cui i riferimenti alle funzioni di imbragatura sono comuni, è facile passare del codice in un setClickHandler()metodo e fare in modo che istanze diverse della stessa classe facciano cose molto diverse. In lingue che non hanno comode espressioni lambda, è più semplice creare una nuova sottoclasse anonima solo per un nuovo gestore. Tradizionalmente, i metodi prioritari erano considerati il ​​segno distintivo di una nuova classe, mentre l'impostazione dei valori degli attributi non lo era, ma soprattutto con le funzioni di gestione, i due hanno effetti molto simili, quindi la distinzione diventa una guerra per le parole.
Kilian Foth,

1
Non esattamente quello che stai chiedendo, ma questo suona come il modello di progettazione della strategia. Qui hai un'istanza di una classe che cambia efficacemente il suo tipo in fase di esecuzione. È un po 'fuori tema perché non è il linguaggio che lo consente, ma vale la pena menzionarlo perché hai detto "perché qualcuno dovrebbe voler fare questo"
Tony

@Killian Forth OOP basato su prototipo non ha classi per definizione e quindi non corrisponde esattamente a ciò che l'OP chiede. La differenza chiave è che una classe non è un'istanza.
eques

Risposte:


9

I metodi nella maggior parte dei linguaggi OOP (basati sulla classe) sono fissi per tipo.

JavaScript è basato sul prototipo, non sulla classe e quindi è possibile sovrascrivere i metodi in base alle istanze poiché non esiste una forte distinzione tra "classe" e oggetto; in realtà, una "classe" in JavaScript è un oggetto che è come un modello per come dovrebbero funzionare le istanze.

Qualsiasi linguaggio che consente funzioni di prima classe, Scala, Java 8, C # (tramite delegati), ecc., Può agire come se si avesse la precedenza del metodo per istanza; dovresti definire un campo con un tipo di funzione e quindi sovrascriverlo in ogni istanza.

Scala ha un'altra possibilità; In Scala, è possibile creare singleton oggetto (utilizzando la parola chiave object anziché la parola chiave class), quindi è possibile estendere la classe e sovrascrivere i metodi, risultando in una nuova istanza di quella classe base con le sostituzioni.

Perché qualcuno dovrebbe farlo? Potrebbero esserci dozzine di ragioni. Potrebbe essere che il comportamento abbia bisogno per me di una definizione più rigorosa rispetto all'utilizzo di varie combinazioni di campi. Potrebbe anche mantenere il codice disaccoppiato e organizzato meglio. In generale, tuttavia, ritengo che questi casi siano più rari e spesso esiste una soluzione più semplice utilizzando i valori di campo.


La tua prima frase non ha senso. Cosa ha a che fare OOP basato sulla classe con i tipi fissi?
Bergi,

Voglio dire che per tipo l'insieme e le definizioni dei metodi sono fissi o in altre parole, per aggiungere o modificare un metodo, è necessario creare un nuovo tipo (ad es. Sottotipando).
eques

@Bergi: In OOP basato sulla classe la parola "classe" e "tipo" significano essenzialmente la stessa cosa. Dal momento che non ha senso consentire al tipo intero di avere il valore "ciao", non ha nemmeno senso avere tipo Employee per avere valori o metodi che non appartengono alla classe Employee.
Slebetman,

@slebetman: intendevo dire che un linguaggio OOP basato su classi non ha necessariamente una tipizzazione forte e staticamente forzata.
Bergi,

@Bergi: questo è il significato di "most" nella risposta sopra (la maggior parte significa non tutto). Sto semplicemente commentando il tuo commento "cosa deve fare".
slebetman,

6

È difficile indovinare la motivazione della tua domanda, quindi alcune possibili risposte potrebbero o meno indirizzare il tuo reale interesse.

Anche in alcuni linguaggi non prototipo è possibile approssimare questo effetto.

In Java, ad esempio, una classe interna anonima è abbastanza vicina a ciò che stai descrivendo: puoi creare e creare un'istanza di una sottoclasse dell'originale, ignorando solo il metodo o i metodi che desideri. La classe risultante sarà una instanceofclasse originale, ma non sarà la stessa classe.

Quanto al motivo per cui vorresti farlo? Con le espressioni lambda Java 8, penso che molti dei migliori casi d'uso scompaiano. Con le versioni precedenti di Java, almeno, questo può evitare una proliferazione di banali classi di uso ristretto. Cioè, quando hai un gran numero di casi d'uso correlati, che differiscono solo in un piccolo modo funzionale, puoi crearli quasi al volo (quasi), con la differenza comportamentale iniettata nel momento in cui ne hai bisogno.

Detto questo, anche prima del J8, questo può spesso essere refactored per spostare la differenza in un campo o tre, e iniettarli nel costruttore. Con J8, ovviamente, il metodo stesso può essere iniettato nella classe, anche se potrebbe esserci la tentazione di farlo quando un altro refactoring può essere più pulito (se non altrettanto interessante).


6

Hai richiesto qualsiasi lingua che fornisca metodi per istanza. Esiste già una risposta per Javascript, quindi vediamo come viene eseguito in Common Lisp, dove è possibile utilizzare gli specialisti EQL:

;; define a class
(defclass some-class () ())

;; declare a generic method
(defgeneric some-method (x))

;; specialize the method for SOME-CLASS
(defmethod some-method ((x some-class)) 'default-result)

;; create an instance named *MY-OBJECT* of SOME-CLASS
(defparameter *my-object* (make-instance 'some-class))

;; specialize SOME-METHOD for that specific instance
(defmethod some-method ((x (eql *my-object*))) 'specific-result)

;; Call the method on that instance
(some-method *my-object*)
=> SPECIFIC-RESULT

;; Call the method on a new instance
(some-method (make-instance 'some-class))
=> DEFAULT-RESULT

Perché?

Gli specialisti EQL sono utili quando si suppone che l'argomento soggetto al dispacciamento abbia un tipo per il quale eqlha senso: un numero, un simbolo, ecc. In generale, non ne hai bisogno e devi semplicemente definire quante sottoclassi quante necessario al tuo problema. Ma a volte, devi solo spedire secondo un parametro che è, ad esempio, un simbolo: caseun'espressione sarebbe limitata ai casi noti nella funzione di dispacciamento, mentre i metodi possono essere aggiunti e rimossi in qualsiasi momento.

Inoltre, la specializzazione sulle istanze è utile per scopi di debug, quando si desidera ispezionare temporaneamente ciò che accade con un oggetto specifico nell'applicazione in esecuzione.


5

Puoi anche farlo in Ruby usando oggetti singleton:

class A
  def do_something
    puts "Hello!"
  end
end

obj = A.new
obj.do_something

def obj.do_something
  puts "Hello world!"
end

obj.do_something

produce:

Hello!
Hello world!

Per quanto riguarda gli usi, questo è in realtà il modo in cui Ruby fa i metodi di classe e modulo. Per esempio:

def SomeClass
  def self.hello
    puts "Hello!"
  end
end

È in realtà la definizione di un metodo Singleton hellosul Classoggetto SomeClass.


Vuoi estendere la tua risposta con moduli e metodo di estensione? (Solo per mostrare altri modi per estendere gli oggetti con nuovi metodi)?
annodare il

@knut: Sono abbastanza sicuro che in extendrealtà crea la classe singleton per l'oggetto e quindi importa il modulo nella classe singleton.
Linuxios,

5

Puoi pensare ai metodi per istanza come a permetterti di assemblare la tua classe in fase di esecuzione. Questo può eliminare molto codice colla, quel codice che non ha altro scopo se non quello di mettere insieme due classi per parlare tra loro. I mixin sono una soluzione un po 'più strutturata per gli stessi tipi di problemi.

Soffri un po 'per il paradosso blub , essenzialmente che è difficile vedere il valore di una funzione linguistica fino a quando non la usi in un vero programma. Quindi cerca opportunità dove pensi che possa funzionare, provalo e vedi cosa succede.

Cerca nel tuo gruppo gruppi di classi che differiscono solo per un metodo. Cerca le classi il cui unico scopo è combinare altre due classi in combinazioni diverse. Cerca metodi che non fanno altro che passare una chiamata a un altro oggetto. Cerca le classi che vengono istanziate usando schemi creazionali complessi . Questi sono tutti potenziali candidati da sostituire con metodi per istanza.


1

Altre risposte hanno mostrato come questa sia una caratteristica comune dei linguaggi dinamici orientati agli oggetti e come possa essere emulata banalmente in un linguaggio statico che ha oggetti funzione di prima classe (ad es. Delegati in c #, oggetti che sovrascrivono l'operatore () in c ++) . Nei linguaggi statici privi di tale funzione è più difficile, ma può ancora essere raggiunto utilizzando una combinazione del modello di strategia e un metodo che ne delega semplicemente l'attuazione alla strategia. Questa è in effetti la stessa cosa che faresti in c # con i delegati, ma la sintassi è un po 'più caotica.


0

Puoi fare qualcosa del genere in C # e in molte altre lingue simili.

public class MyClass{
    public Func<A,B> MyABFunc {get;set;}
    public Action<B> MyBAction {get;set;}
    public MyClass(){
        //todo assign MyAFunc and MyBAction
    }
}

1
I programmatori riguardano domande e risposte concettuali che dovrebbero spiegare le cose. Lanciare i dump del codice invece della spiegazione è come copiare il codice dall'IDE alla lavagna: può sembrare familiare e persino a volte comprensibile, ma sembra strano ... solo strano. La lavagna non ha compilatore
moscerino

0

Concettualmente, anche se in un linguaggio come Java tutte le istanze di una classe devono avere gli stessi metodi, è possibile farlo apparire come se non lo facessero aggiungendo un ulteriore livello di riferimento indiretto, possibilmente in combinazione con classi nidificate.

Ad esempio, se una classe Foopuò definire una classe nidificata astratta statica QuackerBaseche contiene un metodo quack(Foo), nonché diverse altre classi nidificate statiche derivanti QuackerBase, ciascuna con una propria definizione di quack(Foo), quindi se la classe esterna ha un campo quackerdi tipo QuackerBase, allora può imposta quel campo per identificare un'istanza (possibilmente singleton) di una delle sue classi nidificate. Dopo averlo fatto, invocando quacker.quack(this)eseguirà il quackmetodo della classe la cui istanza è stata assegnata a quel campo.

Poiché si tratta di un modello abbastanza comune, Java include meccanismi per dichiarare automaticamente i tipi appropriati. Tali meccanismi non stanno realmente facendo nulla che non possa essere fatto semplicemente usando metodi virtuali e classi statiche annidate opzionalmente, ma riducono notevolmente la quantità di piastra di caldaia necessaria per produrre una classe il cui unico scopo è eseguire un singolo metodo per conto di un'altra classe.


0

Credo che sia la definizione di un linguaggio "dinamico" come ruby, groovy e Javascript (e molti altri). La dinamica si riferisce (almeno in parte) alla capacità di ridefinire dinamicamente il modo in cui un'istanza di classe potrebbe comportarsi al volo.

Non è una buona pratica OO in generale, ma per molti programmatori di linguaggi dinamici i principi OO non sono la loro massima priorità.

Semplifica alcune operazioni complicate come Monkey Patching in cui potresti modificare un'istanza di classe per permetterti di interagire con una libreria chiusa in un modo che non prevedevano.


0

Non sto dicendo che è una buona cosa da fare, ma questo è banalmente possibile in Python. Non posso pensare a un buon caso d'uso dalla parte superiore della mia testa, ma sono sicuro che esistono.

    class Foo(object):
        def __init__(self, thing):
            if thing == "Foo":
                def print_something():
                    print "Foo"
            else:
                def print_something():
                    print "Bar"
            self.print_something = print_something

    Foo(thing="Foo").print_something()
    Foo(thing="Bar").print_something()
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.