To pytanie jest nieco niezależne od języka, ale nie do końca, ponieważ programowanie obiektowe (OOP) różni się na przykład w Javie , która nie ma funkcji pierwszej klasy, niż w Pythonie .
Innymi słowy, czuję się mniej winny z powodu tworzenia niepotrzebnych klas w języku takim jak Java, ale wydaje mi się, że może istnieć lepszy sposób w mniej popularnych językach, takich jak Python.
Mój program musi kilkakrotnie wykonać stosunkowo złożoną operację. Ta operacja wymaga dużo „księgowości”, musi tworzyć i usuwać niektóre pliki tymczasowe itp.
Dlatego też musi wywoływać wiele innych „podoperacji” - umieszczenie wszystkiego w jednej ogromnej metodzie nie jest zbyt ładne, modułowe, czytelne itp.
Teraz przychodzą mi na myśl podejścia:
1. Stwórz klasę, która ma tylko jedną metodę publiczną i zachowuje wewnętrzny stan wymagany dla podoperacji w zmiennych instancji.
Wyglądałoby to tak:
class Thing:
def __init__(self, var1, var2):
self.var1 = var1
self.var2 = var2
self.var3 = []
def the_public_method(self, param1, param2):
self.var4 = param1
self.var5 = param2
self.var6 = param1 + param2 * self.var1
self.__suboperation1()
self.__suboperation2()
self.__suboperation3()
def __suboperation1(self):
# Do something with self.var1, self.var2, self.var6
# Do something with the result and self.var3
# self.var7 = something
# ...
self.__suboperation4()
self.__suboperation5()
# ...
def suboperation2(self):
# Uses self.var1 and self.var3
# ...
# etc.
Problem, jaki widzę w tym podejściu, polega na tym, że stan tej klasy ma sens tylko wewnętrznie i nie może nic zrobić z jej instancjami, z wyjątkiem wywołania ich jedynej publicznej metody.
# Make a thing object
thing = Thing(1,2)
# Call the only method you can call
thing.the_public_method(3,4)
# You don't need thing anymore
2. Utwórz kilka funkcji bez klasy i przekaż między nimi różne wewnętrznie potrzebne zmienne (jako argumenty).
Problem, jaki tu widzę, polega na tym, że muszę przekazywać wiele zmiennych między funkcjami. Ponadto funkcje byłyby ściśle ze sobą powiązane, ale nie byłyby zgrupowane razem.
3. Podobnie jak 2., ale zmienne globalne należy wprowadzać zamiast przekazywać.
To nie byłoby w ogóle dobre, ponieważ muszę wykonać operację więcej niż raz, z różnymi danymi wejściowymi.
Czy istnieje czwarte, lepsze podejście? Jeśli nie, to które z tych podejść byłoby lepsze i dlaczego? Czy czegoś brakuje?
źródło
Odpowiedzi:
Opcja 1 jest dobrym przykładem prawidłowego zastosowania kapsułkowania . Chcesz, aby stan wewnętrzny był ukryty przed kodem zewnętrznym.
Jeśli to oznacza, że twoja klasa ma tylko jedną metodę publiczną, niech tak będzie. Będzie to o wiele łatwiejsze w utrzymaniu.
W OOP, jeśli masz klasę, która robi dokładnie jedną rzecz, ma małą powierzchnię publiczną i ukrywa cały swój stan wewnętrzny, to jesteś (jak powiedziałby Charlie Sheen) ZWYCIĘZCĄ .
Wariant 2 ma niską spójność . Utrudni to konserwację.
Opcja 3, podobnie jak opcja 2, cierpi z powodu niskiej spójności, ale znacznie poważniej!
Historia pokazała, że wygoda zmiennych globalnych jest przeważona przez związane z tym brutalne koszty utrzymania. Właśnie dlatego słyszysz takie stare bąki jak ja, które cały czas gadają o enkapsulacji.
Zwycięską opcją jest nr 1 .
źródło
ThingDoer(var1, var2).do_it()
vsdo_thing(var1, var2)
.def do_thing(var1, var2): return _ThingDoer(var1, var2).run()
, aby zewnętrzny interfejs API był nieco ładniejszy.Myślę, że nr 1 to właściwie zła opcja.
Rozważmy twoją funkcję:
Z jakich danych korzysta podoperacja1? Czy zbiera dane wykorzystywane przez podoperację2? Gdy przekazujesz dane, przechowując je na sobie, nie mogę powiedzieć, w jaki sposób powiązane są funkcje. Kiedy patrzę na siebie, niektóre atrybuty pochodzą z konstruktora, niektóre z wywołania metody_publicznej, a niektóre losowo dodawane w innych miejscach. Moim zdaniem to bałagan.
A co z numerem 2? Po pierwsze, spójrzmy na drugi problem z tym:
Byliby razem w module, więc byliby całkowicie zgrupowani.
Moim zdaniem to dobrze. To wyraźnie określa zależności danych w algorytmie. Przechowując je w zmiennych globalnych lub na sobie, ukrywasz zależności i sprawiasz, że wydają się mniej złe, ale wciąż tam są.
Zwykle, gdy pojawia się taka sytuacja, oznacza to, że nie znalazłeś właściwego sposobu na rozłożenie problemu. Dzielenie się na wiele funkcji jest niewygodne, ponieważ próbujesz podzielić go w niewłaściwy sposób.
Oczywiście, nie widząc rzeczywistej funkcji, trudno zgadnąć, co byłoby dobrą sugestią. Podajesz jednak odrobinę tego, z czym masz do czynienia:
Pozwól, że wybiorę przykład czegoś, co pasuje do twojego opisu, instalatora. Instalator musi skopiować kilka plików, ale jeśli anulujesz go częściowo, musisz przewinąć cały proces, łącznie z cofnięciem wszystkich zastąpionych plików. Algorytm do tego wygląda mniej więcej tak:
Teraz pomnóż tę logikę na potrzeby wykonywania ustawień rejestru, ikon programów itp., A masz dość bałaganu.
Myślę, że twoje rozwiązanie nr 1 wygląda następująco:
To sprawia, że ogólny algorytm jest bardziej przejrzysty, ale ukrywa sposób komunikowania się różnych elementów.
Podejście nr 2 wygląda mniej więcej tak:
Teraz jest jasne, jak fragmenty danych przemieszczają się między funkcjami, ale jest to bardzo niewygodne.
Jak więc napisać tę funkcję?
Obiekt FileTransactionLog jest menedżerem kontekstu. Kiedy copy_new_files kopiuje plik, robi to poprzez FileTransactionLog, który obsługuje tworzenie kopii tymczasowej i śledzi, które pliki zostały skopiowane. W przypadku wyjątku kopiuje oryginalne pliki z powrotem, aw przypadku powodzenia usuwa kopie tymczasowe.
Działa to, ponieważ odkryliśmy bardziej naturalny rozkład zadania. Wcześniej mieszaliśmy logikę o tym, jak zainstalować aplikację z logiką o tym, jak odzyskać anulowaną instalację. Teraz dziennik transakcji obsługuje wszystkie szczegóły dotyczące plików tymczasowych i księgowości, a funkcja może skupić się na podstawowym algorytmie.
Podejrzewam, że twoja sprawa jest na tej samej łodzi. Musisz wyodrębnić elementy księgowości do jakiegoś obiektu, aby złożone zadanie mogło być wyrażone w bardziej prosty i elegancki sposób.
źródło
Ponieważ jedyną widoczną wadą metody 1 jest jej nieoptymalny wzorzec użycia, myślę, że najlepszym rozwiązaniem jest zwiększenie enkapsulacji o krok dalej: użyj klasy, ale także zapewnij funkcję wolnostojącą, która konstruuje tylko obiekt, wywołuje metodę i zwraca :
Dzięki temu masz najmniejszy możliwy interfejs do swojego kodu, a klasa, której używasz wewnętrznie, naprawdę staje się tylko szczegółem implementacji twojej funkcji publicznej. Kod telefoniczny nigdy nie musi wiedzieć o twojej klasie wewnętrznej.
źródło
Z jednej strony pytanie jest w jakiś sposób agnostyczne wobec języka; ale z drugiej strony implementacja zależy od języka i jego paradygmatów. W tym przypadku jest to Python, który obsługuje wiele paradygmatów.
Oprócz twoich rozwiązań istnieje również możliwość wykonania operacji bezstanowych w bardziej funkcjonalny sposób, np
Wszystko sprowadza się do
Ale -Jeśli twoja baza kodu to OOP, to narusza spójność, jeśli nagle niektóre części są pisane w (bardziej) funkcjonalnym stylu.
źródło
Po co projektować, kiedy mogę kodować?
Oferuję sprzeczne spojrzenie na odpowiedzi, które przeczytałem. Z tej perspektywy wszystkie odpowiedzi, a nawet samo pytanie, koncentrują się na mechanice kodowania. Ale to jest kwestia projektu.
Tak, ponieważ ma to sens dla twojego projektu. Może to być klasa sama w sobie lub część innej klasy lub jej zachowanie może być rozdzielone między klasy.
Projektowanie obiektowe polega na złożoności
Celem OO jest skuteczne budowanie i utrzymywanie dużych, złożonych systemów poprzez enkapsulację kodu pod względem samego systemu. „Właściwa” konstrukcja mówi, że wszystko jest w jakiejś klasie.
Projektowanie OO naturalnie zarządza złożonością przede wszystkim poprzez skoncentrowane klasy, które przestrzegają zasady pojedynczej odpowiedzialności. Klasy te nadają strukturę i funkcjonalność podczas oddechu i głębokości systemu, współdziałają i integrują te wymiary.
Biorąc to pod uwagę, często mówi się, że funkcje wiszące wokół systemu - zbyt wszechobecna ogólna klasa użyteczności - to zapach kodu sugerujący niewystarczający projekt. Zwykle się zgadzam.
źródło
Dlaczego nie porównać swoich potrzeb z czymś, co istnieje w standardowej bibliotece Pythona, a następnie zobaczyć, jak to jest zaimplementowane?
Uwaga: jeśli nie potrzebujesz obiektu, nadal możesz definiować funkcje w ramach funkcji. W Pythonie 3 pojawiła się nowa
nonlocal
deklaracja umożliwiająca zmianę zmiennych w funkcji nadrzędnej.Nadal może się przydać kilka prostych klas prywatnych wewnątrz funkcji do implementowania abstrakcji i porządkowania operacji.
źródło
nonlocal
nigdzie w moich obecnie zainstalowanych bibliotekach Pythona. Być może możesz wziąć serce, ztextwrap.py
którego maTextWrapper
klasę, ale takżedef wrap(text)
funkcję, która po prostu tworzyTextWrapper
instancję, wywołuje na niej.wrap()
metodę i zwraca. Więc skorzystaj z klasy, ale dodaj kilka funkcji wygody.