Zasady OOP i nazwy metod

22
class Boxer:

    def punch(self, punching_bag, strength):
        punching_bag.punch(strength)


class PunchingBag:

    def punch(self, strength):
        print "Punching bag punched with strength", strength

boxer = Boxer()
punching_bag = PunchingBag()

boxer.punch(punching_bag, 2)

Bez wątpienia punchto dobra nazwa metody w przypadku boksera. Ale czy nazwa punchnadaje się również do metody wbijania worka? W obu przypadkach mam na myśli cios jako polecenie (tj. Wykonaj cios).

klimat
źródło

Odpowiedzi:

23

Dobrą zasadą jest, aby nazwy metod były czasownikami lub predykatami, tak aby obiekt, do którego je wywołujesz ( selfw standardowej konwencji Pythona, thisw większości innych języków) stał się tematem.

Zgodnie z tą zasadą file.closejest trochę źle, chyba że zastosujesz model mentalny, że plik sam się zamyka lub że fileobiekt nie reprezentuje samego pliku, a raczej uchwyt pliku lub jakiś obiekt proxy.

Worek treningowy nigdy się nie uderza, więc i tak punchingBag.punch()jest źle. be_punched()jest technicznie poprawny, ale brzydki. receive_punch()może działać, lub handle_punch(). Innym podejściem, dość popularnym w JavaScript, jest traktowanie takich wywołań metod jako zdarzeń, a konwencja polega na tym, że nazwa zdarzenia poprzedzona jest prefiksem „on”, więc będzie to on_punched()lub on_hit(). Alternatywnie, możesz przyjąć konwencję, że imiesłów bierny wskazuje głos pasywny, a zgodnie z tą konwencją nazwa metody byłaby sprawiedliwa punched().

Innym aspektem do rozważenia jest to, czy worek treningowy rzeczywiście wie, co go uderzyło: czy ma to znaczenie, czy uderzysz go, uderzysz kijem, czy wpadniesz na niego ciężarówką? Jeśli tak, jaka jest różnica? Czy potrafisz sprowadzić różnicę do argumentu, czy potrzebujesz różnych metod dla różnych rodzajów otrzymywanej kary? Pojedyncza metoda z parametrem generycznego jest prawdopodobnie najbardziej eleganckie rozwiązanie, ponieważ utrzymuje stopień łączący niskie, a taka metoda nie powinna być wywołana punched()albo handle_punch(), ale raczej coś bardziej ogólne jak receive_hit(). Dzięki takiej metodzie możesz wdrożyć różnego rodzaju aktorów, którzy mogą uderzać worki treningowe, bez zmiany samej torby treningowej.

tdammers
źródło
4
@Artur: tak i nie. Pliki mogą (mówiąc koncepcyjnie) zamykać się na żądanie; tablice mogą się sortować; ale worki treningowe same się nie przebijają.
tdammers
2
Ok, jeśli nasz worek treningowy uderzy w ścianę z niesamowitą prędkością, to czy to ściana go uderzyła, czy też worek treningowy doznał uderzenia sam w sobie?
1
@tdammers: Twoja sugestia uogólnienia może również prowadzić do interfejsu o nazwie Hitable.
Jens Piegsa
2
@Artur: Myślę, że załamuje się założenie OOP, że każde zdanie ma naturalny przedmiot i że ta idea dotyczy programowania.
tdammers
1
Więc główne pytanie brzmi. Jeśli pliki mogą się zamykać, tablice same się sortują itp., Dlaczego worki treningowe nie potrafią się dziurkować? Czy jest jakaś prawdziwa różnica, czy tylko w pierwszym przypadku jesteśmy do tego przyzwyczajeni, a w drugim nie?
klimat
6

Myślę, że jest to kwestia konceptualna (jak myślimy o świecie). Można powiedzieć:

  • Spójrz, drzwi się zamykają. door.close()
  • Wow, papier sam się składa. paper.fold()
  • Co do cholery?! Ta teczka na biurku właśnie się zamknęła, nikogo nie ma w pobliżu. file.close()

Dziwnie jest powiedzieć:

  • Worek treningowy na siłowni właśnie się przebił. bag.punch()

Musiałby w pierwszej kolejności mieć coś do uderzenia (np. Ramiona). Prawdopodobnie powiedziałbyś:

  • Worek treningowy zaczął się poruszać sam, jakby ktoś go uderzył. punching_bag.move()

Jest w porządku, że obiekty programistyczne robią rzeczy, które normalnie inni robią z nimi / w nich (w „prawdziwym świecie”). Ale myślę, że zawsze powinno to mieć przynajmniej jakiś sens, że coś robi to samo z sobą . Powinieneś być w stanie sobie to łatwo wyobrazić, nie pozostając niejasnym (jak w przypadku punching_bag).

klimat
źródło
2

Myślę, że to kwestia gustu. Punching bag„s punch()metoda jest co najmniej zgodne z file.close()lub frame.move()w sensie przeżywania działanie na sobie. Większe pytanie brzmiałoby: dlaczego w Boxerogóle istnieje punch(something)metoda?


źródło
Podoba mi się twój punkt dotyczący file.close (). To było coś, o co mi chodziło. Może bokser ma metodę ciosu, ponieważ trener trenuje również boksera. Cóż, w rzeczywistości próbowałem tylko podać przykład akcji (wiadomości) przechodzącej przez kilka obiektów, przy czym ostatni to „obiekt akcji”. Mam niewielki problem z list.append (4), account.deposit (50), file.close (), paper.fold () vs. boxer.punch (), dog.bark (), logger.log () itp. ,
klimat
Podczas przechodzenia przez kilka obiektów występują 2 przypadki: używasz powiązanego kontekstu (ja), a nie używasz. Jeśli to zrobisz, twoje metody powinny być Coach.sayPunchToBoxer(), Boxer.punchNearestBag()i Bag.punch(). W przeciwnym razie musisz zgadnąć, co się stanie za każdym razem, gdy zadzwonisz Coach.punch(). Ogólna zasada brzmi: jeśli obiekt, który doświadcza akcji, nie jest określony w nazwie metody, wówczas odbiorcą jest ten obiekt.
Cóż, myślę, że jest to również w porządku: coach.say_punch (bokser, punching_bag), boxer.punch (punching_bag). tzn. odbiornik nie jest w nazwie metody, ale w parametrach.
Clime
1
Jasne, miałem na myśli, że odbiornik akcji powinien odgadnąć na podstawie instrukcji wywołania.
2

Masz dwa różne komunikaty: jeden z poleceniem wybicia obiektu i jeden z informowaniem obiektu, że został on uderzony. Weź pod uwagę, że obiekt Boxer prawdopodobnie będzie musiał odpowiedzieć na oba . Różnie . To naprawdę dobry powód, aby nadać im różne nazwy.

Chciałbym zachować punch(boxer, object, strength)i zmienić nazwę metody przeciwnej do punched. Możesz to nazwać handle_punchlub coś w tym rodzaju, ale nadal nie jest jasne, czy ma to być polecenie polecenia ciosu, czy powiadomienie, że został uderzony.

użytkownik2313838
źródło
Dobra uwaga, że ​​Boxer potrzebuje zarówno ciosu, jak i czegoś takiego jak handle_punch ( defendw tym konkretnym przypadku). Ale worek treningowy nigdy nie będzie tak dwukierunkowy. I jest już ten plik.close () ...
clime
defendto polecenie. Jest to jedno z możliwych działań, które obiekt może podjąć w odpowiedzi punched, ale nie chcesz, aby inne obiekty wywoływały defendbezpośrednio.
user2313838
2

Twoje podejście ostatecznie doprowadzi do powstania bardzo sprzężonego kodu.

Podsumowując idealnie Erica Lipperta tutaj, chciałbyś, aby bokser był w stanie uderzyć bardzo wiele rzeczy. Posiadanie worka bokserskiego jako sygnatury funkcji boksera oznacza, że ​​bokser jest tworzony z natychmiastową znajomością wszystkiego (to jest możliwe do wykrawania). Plus dać cios i otrzymać cios to dwie BARDZO różne rzeczy, dlatego nie powinny mieć tej samej nazwy.

Wolałbym modelować to jako bokser, który tworzy cios (inny obiekt zawierający siłę atrybutu ciosu, zasięg, kierunek itp.).

Następnie mieć worek treningowy za pomocą metody takiej jak onPunch, który odbiera ten obiekt uderzenia, aby obliczyć wpływ stempla na siebie.

Pamiętając o tym, nazwa rzeczy ma duże znaczenie. Musi pasować do twojego mentalnego modelu sytuacji. Jeśli próbujesz wyjaśnić, jak może się zdarzyć coś, co na pierwszy rzut oka nie ma sensu, lub jeśli masz trudności z nazwaniem czegoś, być może twój model jest zły i musi się zmienić.

Po rozpoczęciu ciężko jest zmienić model, ludzie zazwyczaj naginają rzeczywistość, aby dopasować do modelu. Problem polega na tym, że kiedy zginasz rzeczy, aby dopasować (na przykład worek treningowy, który może wykrawać rzeczy), świat, który tworzysz, staje się coraz bardziej złożony, a interakcje stają się coraz trudniejsze do wdrożenia. W końcu osiągniesz punkt, w którym dodanie nawet najbardziej trywialnej rzeczy stanie się koszmarem zmian i błędów. Ten koncepcyjny dług techniczny może mieć bardzo wysoką cenę, nawet jeśli początkowy koszt był wówczas postrzegany jako najtańszy.

Newtopian
źródło
1

Jest to problem, który nazywam zamieszaniem „obiekt / przedmiot” i jest dość powszechny.

Zdania na ogół mają podmiot, który wykonuje czasownik na swoim obiekcie docelowym .

Jeśli chodzi o programowanie, jedyną rzeczą, która faktycznie robi rzeczy, jest komputer. Lub praktycznie proces, nić lub włókno. Obiekty nie są domyślnie animowane. Nie mają własnych wątków, więc nie mogą nic zrobić.

Oznacza to, że działają na nich metody, są one celem akcji, a nie kto ją wykonuje. Dlatego nazywamy je „przedmiotami”, a nie „podmiotami”!

Kiedy mówisz, File.closeże to nie plik sam się zamyka, to bieżący działający wątek zamyka plik. Jeśli powiesz Array.sort, bieżący działający wątek sortuje tablicę. Jeśli powiesz HttpServer.sendRequest, bieżący działający wątek wysyła żądanie do serwera (nie odwrotnie!). Podobnie powiedzenie PunchingBag.punchoznacza, że ​​bieżący wątek przebija torbę.

Oznacza to, że jeśli chcesz Boxerbyć w stanie dziurkować, musi to być podklasa Threadtak, aby mogła robić rzeczy takie jak dziurkowanie worków w funkcji wątku.

Czasami jednak sensowne jest stwierdzenie, że worek bokserski przebija się sam w przypadku, gdy każdy obiekt ma swój własny wątek, możesz chcieć uniknąć warunków wyścigu i zaimplementować wywołania metod jako przekazywanie wiadomości: przebijasz torbę, wysyłając jej punchwiadomość, to dziurkuje wątek następnie odsyła punch successfulwiadomość, ale to tylko szczegół implementacji.

Calmarius
źródło
0

Zgadzam się, że „cios” jest dobrą nazwą metody dla klasy Boxer, ponieważ (z pewnymi poprawkami) może być ponownie użyty przeciwko innym obiektom. Dokładnie opisuje również, że obiekt klasy wykonuje akcję na innym obiekcie. Chociaż zmieniłbym nazwę metody na „doPunch”, aby jaśniej pokazać związek.

Jednak w przypadku klasy PunchingBag nazwa metody jest albo zbyt niejasna, albo trochę niedokładna w stosunku do tego, co dzieje się w metodzie. Kiedy widzę „uderzenie”, myślę, że coś uderza coś innego. Jednak w tym przypadku obiekt PunchingBag reaguje na uderzenie obiektu (w tym przypadku obiekt Boxer). Chciałbym więc zmienić nazwę metody na „isPunched”, aby zilustrować, że obiekt reaguje na uderzenie.

Jest to jednak moja interpretacja tego, jak nazwałbym metody. Wszystko zależy od gustu i standardów, których przestrzegasz.

Marvon
źródło
3
isPunchedjest naprawdę mylące (mniej więcej, w zależności od schematu nazewnictwa ram).
Zazwyczaj metoda jest stosowana do tego obiektu, do którego jest wywoływana. Co jest nie tak z sprawiedliwym punch()?
Cóż, całkowicie rozumiem tę potrzebę określenia kierunku działania, ale myślę, że jest coś w OOP i jego filozofii, co sprawia, że ​​jest to niepotrzebne. Jakaś abstrakcja związana ze słynnym wyjaśnieniem, że obiekty „wysyłają sobie wiadomości”.
klimat
Jeśli z nazwy metody nie jest oczywiste, co robi metoda, oznacza to problem z nazwą. To nie jest problem z OO ani z jakimkolwiek używanym paradygmatem. Właśnie dlatego punch () na worku treningowym jest niepoprawny w jakimkolwiek kontekście, którego chcesz użyć. Co to znaczy, kiedy mówisz cios w worek treningowy? Dlatego też nie można zakładać w żadnej filozofii, że coś jest niepotrzebne w sytuacjach, w których założenie powoduje dwuznaczność. Są przypadki, w których zasady kciuka działają i sytuacje, w których nie działają. Jeśli reguły praktyczne zawsze działały, wówczas byłyby nazywane regułami (bez „praktycznego”).
Dunk
-2

hmmmm. Pytam worek treningowy jako klasę, ponieważ tak naprawdę nie zależy ci na worku treningowym - zależy ci na uderzeniu i sile pięści bokserów. więc metody powinny dotyczyć wszystkiego, co mierzy i raportuje wpływ ciosu. nawet jeśli pochodzi z „worka treningowego”, nazwa powinna wciąż ujawniać odpowiedzialność - jak punchImpactMeter itp.

Cartalot
źródło
-3

Bokser uderza pięścią worek treningowy -> bokser. Poncz

Worek treningowy zostaje uderzony przez boksera -> punchingbag.get_punch

P3Dr0
źródło
3
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami poczynionymi i wyjaśnionymi w poprzednich 6 odpowiedziach
gnat