Czy obiekty zbudowane z tej samej klasy mogą mieć unikalne definicje metod?


14

Wiem, że wydaje się to dziwnym pytaniem, ponieważ celem dwóch lub więcej obiektów dzielących tę samą klasę jest to, że ich zachowanie jest takie samo, tzn. Ich metody są identyczne.

Jestem jednak ciekawy, czy istnieją języki OOP, które pozwalają na nowo zdefiniować metody obiektów w taki sam sposób, w jaki można przypisać różne wartości dla ich pól. W rezultacie powstałyby obiekty zbudowane z tej samej klasy, które nie wykazują już dokładnie takiego samego zachowania.

Jeśli się nie mylę, możesz zrobić ten JavaScript? Wraz z tym pytaniem pytam, dlaczego ktoś miałby to zrobić?


9
Termin techniczny dotyczy „prototypu”. Poszukując go, uzyskasz wiele materiałów na temat tego smaku OOP. (Należy pamiętać, że wiele osób nie uważa takich struktur za „klasy”, właśnie dlatego, że nie mają jednolitych atrybutów i metod.)
Kilian Foth,

1
masz na myśli, jak dwa różne wystąpienia przycisku mogą robić różne rzeczy po ich kliknięciu, ponieważ każdy z nich jest połączony z inną obsługą przycisku? Oczywiście zawsze można argumentować, że robią to samo - wywołują funkcję obsługi przycisków. W każdym razie, jeśli Twój język obsługuje delegatów lub wskaźniki funkcji, jest to łatwe - masz jakąś zmienną właściwość / pole / członek, która przechowuje wskaźnik delegata lub funkcji, a implementacja metod wywołuje to i od razu.
Kate Gregory

2
To skomplikowane :) W językach, w których często występują odwołania do funkcji procy, łatwo jest przekazać kod do setClickHandler()metody i sprawić, że różne instancje tej samej klasy robią bardzo różne rzeczy. W językach, które nie mają wygodnych wyrażeń lambda, łatwiej jest utworzyć nową anonimową podklasę tylko dla nowego modułu obsługi. Tradycyjnie metody zastępujące były uważane za znak rozpoznawczy nowej klasy, podczas gdy ustawianie wartości atrybutów nie było, ale zwłaszcza w przypadku funkcji obsługi, oba mają bardzo podobne efekty, więc rozróżnienie staje się wojną o słowa.
Kilian Foth,

1
Nie do końca to, o co pytasz, ale to brzmi jak wzór strategii. Tutaj masz instancję klasy, która skutecznie zmienia jej typ w czasie wykonywania. To trochę nie na temat, ponieważ nie jest to język, który pozwala na to, ale warto o tym wspomnieć, ponieważ powiedziałeś „dlaczego ktoś miałby to zrobić”
Tony

@Killian Forth Protokół OOP oparty na prototypach nie ma klas z definicji, a zatem nie do końca pasuje do tego, o co prosi OP. Kluczową różnicą jest to, że klasa nie jest instancją.
eques

Odpowiedzi:


9

Metody w większości (opartych na klasach) językach OOP są ustalane według typu.

JavaScript jest oparty na prototypach, a nie na klasach, więc możesz nadpisywać metody w oparciu o instancje, ponieważ nie ma wyraźnego rozróżnienia między „klasą” a obiektem; w rzeczywistości „klasa” w JavaScript jest obiektem, który przypomina szablon, w którym powinny działać instancje.

Każdy język, który zezwala na pierwszorzędne funkcje, Scala, Java 8, C # (przez delegatów) itp., Może działać tak, jakbyś miał przesłonięcia metod dla poszczególnych instancji; musisz zdefiniować pole z typem funkcji, a następnie zastąpić je w każdej instancji.

Scala ma inną możliwość; W Scali możesz tworzyć singletony obiektowe (używając słowa kluczowego object zamiast słowa kluczowego class), dzięki czemu możesz rozszerzyć klasę i zastąpić metody, w wyniku czego nowa instancja tej klasy bazowej zostanie zastąpiona.

Dlaczego ktoś miałby to robić? Powodów może być dziesiątki. Może być tak, że zachowanie musi być dla mnie ściślej zdefiniowane, niż byłoby to możliwe przy użyciu różnych kombinacji pól. Może także lepiej oddzielić kod i lepiej go zorganizować. Ogólnie jednak uważam, że te przypadki są rzadsze i często istnieje prostsze rozwiązanie wykorzystujące wartości pól.


Twoje pierwsze zdanie nie ma sensu. Co OOP oparty na klasach ma wspólnego z typami stałymi?
Bergi,

Mam na myśli, że dla każdego typu zestaw i definicje metod są stałe lub innymi słowy, aby dodać lub zmienić metodę, musisz utworzyć nowy typ (np. Przez podtyp).
eques

@Bergi: W OOP opartym na klasach słowo „klasa” i „typ” zasadniczo oznaczają to samo. Ponieważ nie ma sensu dopuszczać, aby liczba całkowita typu miała wartość „cześć”, nie ma również sensu, aby typ Pracownik miał wartości lub metody, które nie należą do klasy Pracownik.
slebetman

@slebetman: Miałem na myśli, że oparty na klasach język OOP niekoniecznie ma silne, statycznie wymuszone pisanie.
Bergi,

@Bergi: To znaczy „większość” w powyższej odpowiedzi (większość oznacza nie wszystkie). Komentuję tylko twój komentarz „co to musi zrobić”.
slebetman

6

Trudno odgadnąć motywację twojego pytania, więc niektóre możliwe odpowiedzi mogą, ale nie muszą dotyczyć twojego prawdziwego zainteresowania.

Nawet w niektórych nieprototypowych językach możliwe jest przybliżenie tego efektu.

Na przykład w Javie anonimowa klasa wewnętrzna jest bardzo zbliżona do tego, co opisujesz - możesz utworzyć i utworzyć instancję podklasy oryginału, zastępując tylko metodę lub metody, które chcesz. Klasa wynikowa będzie klasą instanceoforyginalną, ale nie będzie tą samą klasą.

Dlaczego chcesz to zrobić? W przypadku wyrażeń lambda Java 8 myślę, że wiele najlepszych przypadków użycia zniknie. Przynajmniej we wcześniejszych wersjach Javy można uniknąć rozprzestrzeniania się prostych i wąskich klas. To znaczy, gdy masz dużą liczbę powiązanych przypadków użycia, różniących się tylko niewielkim funkcjonalnym sposobem, możesz tworzyć je niemal w locie (prawie), z różnicą behawioralną wprowadzaną w odpowiednim momencie.

To powiedziawszy, nawet przed J8, często można to zmienić, aby przesunąć różnicę na pole lub trzy i wstrzyknąć je do konstruktora. Oczywiście w przypadku J8 samą metodę można wstrzyknąć do klasy, choć może pojawić się pokusa, aby zrobić to, gdy kolejne refaktoryzowanie może być czystsze (jeśli nie tak fajne).


6

Poprosiłeś o dowolny język zapewniający metody dla poszczególnych instancji. Jest już odpowiedź na Javascript, więc zobaczmy, jak to się robi w Common Lisp, gdzie można używać specjalizatorów 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

Dlaczego?

Specjalizatory EQL są przydatne, gdy argument podlegający wysłaniu ma typ, który eqlma sens: liczbę, symbol itp. Ogólnie rzecz biorąc, nie potrzebujesz go i po prostu musisz zdefiniować tyle podklas, ile potrzebne przez twój problem. Ale czasami wystarczy wysłać tylko zgodnie z parametrem, który jest na przykład symbolem: casewyrażenie byłoby ograniczone do znanych przypadków w funkcji wysyłania, podczas gdy metody można dodawać i usuwać w dowolnym momencie.

Specjalizacja w instancjach jest również przydatna do celów debugowania, gdy chcesz tymczasowo sprawdzić, co dzieje się z określonym obiektem w uruchomionej aplikacji.


5

Możesz to również zrobić w Ruby, używając obiektów singletonowych:

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

Produkuje:

Hello!
Hello world!

Jeśli chodzi o zastosowania, tak właśnie Ruby wykonuje metody klas i modułów. Na przykład:

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

W rzeczywistości definiuje metodę singleton hellona Classobiekcie SomeClass.


Czy chcesz rozszerzyć swoją odpowiedź o moduły i metodę rozszerzania? (Aby pokazać inne sposoby rozszerzania obiektów o nowe metody)?
knut

@knut: Jestem prawie pewien, że extendtak naprawdę tworzy klasę singleton dla obiektu, a następnie importuje moduł do klasy singleton.
Linuxios,

5

Można pomyśleć o metodach dla poszczególnych instancji, które pozwalają na tworzenie własnej klasy w czasie wykonywania. Może to wyeliminować wiele kodów kleju, które nie mają innego celu niż połączenie dwóch klas, aby ze sobą rozmawiać. Mixiny są nieco bardziej ustrukturyzowanym rozwiązaniem tego samego rodzaju problemów.

Trochę cierpisz z powodu paradoksu blub , w gruncie rzeczy trudno jest dostrzec wartość funkcji języka, dopóki nie użyjesz jej w prawdziwym programie. Szukaj więc okazji, w których Twoim zdaniem może to zadziałać, wypróbuj je i zobacz, co się stanie.

Poszukaj w kodzie grup klas, które różnią się tylko jedną metodą. Poszukaj klas, których jedynym celem jest połączenie dwóch innych klas w różnych kombinacjach. Poszukaj metod, które nie robią nic innego, jak przekazywać wywołanie do innego obiektu. Poszukaj klas, które są tworzone za pomocą złożonych wzorców tworzenia . Są to wszyscy potencjalni kandydaci, którzy zostaną zastąpieni metodami dla poszczególnych instancji.


1

Inne odpowiedzi pokazały, w jaki sposób jest to wspólna cecha dynamicznych języków obiektowych i jak można ją trywialnie emulować w języku statycznym, który ma obiekty funkcji pierwszej klasy (np. Delegaty w c #, obiekty, które zastępują operator () w c ++) . W językach statycznych, które nie mają takiej funkcji, jest to trudniejsze, ale nadal można to osiągnąć za pomocą kombinacji wzorca strategii i metody, która po prostu przekazuje jego implementację strategii. Jest to w rzeczywistości to samo, co robisz w języku c # z delegatami, ale składnia jest nieco nieporządna.


0

Możesz zrobić coś takiego w C # i większości innych podobnych języków.

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

1
Programiści jest o koncepcyjnych pytania i oczekuje odpowiedzi, aby wyjaśnić rzeczy. Rzucanie zrzutów kodu zamiast objaśnień przypomina kopiowanie kodu z IDE na tablicę: może wyglądać znajomo, a czasem nawet być zrozumiałe, ale wydaje się dziwne ... po prostu dziwne. Tablica nie ma kompilatora
gnat

0

Koncepcyjnie, nawet jeśli w języku takim jak Java wszystkie instancje klasy muszą mieć te same metody, możliwe jest, aby wyglądały tak, jakby nie były, poprzez dodanie dodatkowej warstwy pośredniej, być może w połączeniu z klasami zagnieżdżonymi.

Na przykład, jeśli klasa Foomoże zdefiniować statyczną abstrakcyjną klasę zagnieżdżoną, QuackerBasektóra zawiera metodę quack(Foo), a także kilka innych statycznych klas zagnieżdżonych pochodzących z QuackerBasekażdej z nich z własną definicją quack(Foo), to jeśli klasa zewnętrzna ma pole quackertypu QuackerBase, to może ustaw to pole, aby zidentyfikować (ewentualnie singleton) instancję dowolnej z zagnieżdżonych klas. Po wykonaniu tej czynności wywołanie quacker.quack(this)spowoduje wykonanie quackmetody klasy, której instancja została przypisana do tego pola.

Ponieważ jest to dość powszechny wzorzec, Java zawiera mechanizmy do automatycznego deklarowania odpowiednich typów. Takie mechanizmy tak naprawdę nie robią niczego, czego nie można było zrobić, używając po prostu metod wirtualnych i opcjonalnie zagnieżdżonych klas statycznych, ale znacznie zmniejszają ilość płyty kotłowej niezbędną do stworzenia klasy, której jedynym celem jest uruchomienie jednej metody w imieniu inna klasa.


0

Uważam, że taka jest definicja języka „dynamicznego”, takiego jak ruby, groovy i Javascript (i wiele innych). Dynamiczny odnosi się (przynajmniej częściowo) do możliwości dynamicznego redefiniowania zachowania instancji klasy w locie.

Nie jest to ogólnie dobra praktyka OO, ale dla wielu programistów z dynamicznym językiem zasady OO nie są ich najwyższym priorytetem.

Upraszcza to niektóre skomplikowane operacje, takie jak łatanie małp, w których można ulepszyć instancję klasy, aby umożliwić interakcję z zamkniętą biblioteką w sposób, którego nie przewidzieli.


0

Nie twierdzę, że warto to robić, ale w Pythonie jest to trywialnie możliwe. Nie mogę sobie wyobrazić dobrego przypadku użycia z głowy, ale jestem pewien, że istnieją.

    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()
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.