Czy klasa może używać własnej metody publicznej?


23

tło

Obecnie mam sytuację, w której mam obiekt przesyłany i odbierany przez urządzenie. Ten komunikat ma kilka konstrukcji, takich jak:

public void ReverseData()
public void ScheduleTransmission()

ScheduleTransmissionMetoda wymaga , aby wywołać ReverseDatametodę, gdy jest to tzw. Są jednak chwile, w których będę musiał wywoływać ReverseDatazewnętrznie (i powinienem całkowicie dodać poza obszarem nazw ), z którego obiekt jest tworzony w aplikacji.

Jeśli chodzi o „otrzymywanie”, to znaczy, że ReverseDatazostanie ono wywołane zewnętrznie w object_receivedprocedurze obsługi zdarzeń w celu cofnięcia danych.

Pytanie

Czy obiekt ogólnie może nazywać własne metody publiczne?


5
Nie ma nic złego w wywoływaniu metody publicznej z własnej metody. Ale w oparciu o nazwy twoich metod ReverseData () wydaje mi się nieco niebezpieczny, jeśli jest metodą publiczną, jeśli odwraca wewnętrzne dane. Co się stanie, jeśli funkcja ReverseData () zostanie wywołana poza obiektem, a następnie ponownie wywołana za pomocą ScheduleTransmission ().
Kaan

@Kaan To nie są prawdziwe nazwy moich metod, ale ściśle ze sobą powiązane. W rzeczywistości „dane odwrócone” odwracają tylko 8 bitów całego słowa i są wykonywane, gdy otrzymujemy i przesyłamy.
Snoop,

Pytanie wciąż jest aktualne. Co się stanie, jeśli te 8 bitów zostanie dwukrotnie odwróconych przed zaplanowaniem transmisji? To wygląda jak ogromna dziura w publicznym interfejsie. Myślenie o interfejsie publicznym, jak wspomniałem w mojej odpowiedzi, może pomóc w ujawnieniu tego problemu.
Daniel T.

2
@StevieV Wierzę, że to tylko wzmaga obawy, jakie budzi Kaan. To brzmi tak, jakbyś ujawniał metodę, która zmienia stan obiektu, a stan zależy przede wszystkim od tego, ile razy metoda jest wywoływana. To sprawia, że ​​koszmar w próbach śledzenia stanu obiektu w całym kodzie. Wydaje mi się, że bardziej korzystalibyście z osobnych typów danych z tych stanów pojęciowych, abyście mogli powiedzieć, co to jest w danym fragmencie kodu, nie martwiąc się o to.
jpmc26

2
@StevieV coś takiego jak Data i SerializedData. Dane widzą kod, a SerializedData przesyłane są przez sieć. Następnie przekształć dane odwrócone (lub raczej serializuj / odserializuj dane) z jednego typu na drugi.
csiz

Odpowiedzi:


33

Powiedziałbym, że jest to nie tylko dopuszczalne, ale zachęcane, zwłaszcza jeśli planujesz zezwolić na rozszerzenia. Aby obsługiwać rozszerzenia klasy w języku C #, musisz oflagować metodę jako wirtualną zgodnie z komentarzami poniżej. Możesz jednak to udokumentować, aby ktoś nie był zaskoczony, gdy zastąpienie ReverseData () zmienia sposób działania ScheduleTransmission ().

To naprawdę sprowadza się do projektu klasy. ReverseData () brzmi jak podstawowe zachowanie twojej klasy. Jeśli chcesz użyć tego zachowania w innych miejscach, prawdopodobnie nie chcesz mieć innych jego wersji. Musisz tylko uważać, aby nie dopuścić do wycieku danych szczegółowych dotyczących ScheduleTransmission () do ReverseData (). To stworzy problemy. Ale ponieważ już używasz tego poza klasą, prawdopodobnie już to przemyślałeś.


Wow, wydawało mi się to naprawdę dziwne, ale to pomaga. Chciałbym również zobaczyć, co mówią inni ludzie. Dziękuję Ci.
Snoop,

2
„aby ktoś nie był zaskoczony, gdy zastąpienie ReverseData () zmienia sposób działania ScheduleTransmission ()” : nie, nie bardzo. Przynajmniej nie w C # (zwróć uwagę, że pytanie ma tag C #). O ile nie ReverseData()jest abstrakcyjne, bez względu na to, co robisz w klasie potomnej, zawsze będzie to oryginalna metoda, która zostanie wywołana.
Arseni Mourzenko

2
@MainMa Lub virtual... A jeśli zastępujesz metodę, to z definicji musi to być virtuallub abstract.
Pokechu22

1
@ Pokechu22: rzeczywiście virtualteż działa. We wszystkich przypadkach podpis, według OP, jest public void ReverseData(), więc część odpowiedzi na temat nadpisywania rzeczy jest nieco myląca.
Arseni Mourzenko

@MainMa mówisz, że jeśli metoda klasy bazowej wywołuje metodę wirtualną, nie zachowuje się w zwykły sposób wirtualny, chyba że tak było abstract? Szukałem odpowiedzi na abstractvs virtuali nie widziałem tego wspomnianego: raczej abstrakcyjny oznacza po prostu, że nie ma wersji podanej w tej bazie.
JDługosz

20

Absolutnie.

Widoczność metody ma na celu wyłącznie umożliwienie lub odmowę dostępu do metody poza klasą lub w klasie potomnej; metody publiczne, chronione i prywatne zawsze mogą być wywoływane wewnątrz samej klasy.

Nie ma nic złego w wywoływaniu metod publicznych. Ilustracja w twoim pytaniu jest doskonałym przykładem sytuacji, w której posiadanie dwóch publicznych metod jest dokładnie tym, co powinieneś zrobić.

Możesz jednak uważnie obserwować następujące wzorce:

  1. Hello()Wywołanie metody World(string, int, int)wygląda następująco:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Unikaj tego wzoru. Zamiast tego użyj opcjonalnych argumentów . Opcjonalne argumenty ułatwiają wykrywanie: wywołujący, który pisze, Hello(niekoniecznie będzie wiedział, że istnieje metoda, która umożliwia wywołanie metody z wartościami domyślnymi. Opcjonalne argumenty są również samo-dokumentujące. World()nie pokazuje dzwoniącemu, jakie są rzeczywiste wartości domyślne.

  2. Hello(ComplexEntity)Wywołanie metody World(string, int, int)wygląda następująco:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Zamiast tego użyj przeciążeń . Ten sam powód: lepsza wykrywalność. Dzwoniący może natychmiast zobaczyć wszystkie przeciążenia za pomocą IntelliSense i wybrać właściwy.

  3. Metoda po prostu wywołuje inne metody publiczne, nie dodając żadnej istotnej wartości.

    Pomyśl dwa razy. Czy naprawdę potrzebujesz tej metody? A może powinieneś go usunąć i pozwolić dzwoniącemu wywołać różne metody? Wskazówka: jeśli nazwa metody wydaje się nieprawidłowa lub trudna do znalezienia, prawdopodobnie należy ją usunąć.

  4. Metoda sprawdza poprawność danych wejściowych, a następnie wywołuje drugą metodę:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Zamiast tego Worldpowinien sam zweryfikować swoje parametry lub być prywatny.


Czy te same myśli miałyby zastosowanie, gdyby funkcja ReverseData była rzeczywiście wewnętrzna i używana w przestrzeni nazw zawierającej wystąpienia „obiektu”?
Snoop

1
@StevieV: tak, oczywiście.
Arseni Mourzenko

@MainMa Nie rozumiem, co jest przyczyną punktów 1 i 2
Kapol

@Kapol: dziękuję za opinię. Zredagowałem swoją odpowiedź, dodając wyjaśnienie dotyczące pierwszych dwóch punktów.
Arseni Mourzenko

Nawet w przypadku 1 zwykle wolę przeciążenia niż opcjonalne argumenty. Jeśli opcje są takie, że użycie przeciążenia nie jest opcją lub powoduje szczególnie dużą liczbę przeciążeń, utworzę niestandardowy typ i użyję go jako argumentu (jak w sugestii 2) lub zmienimy kod.
Brian

8

Jeśli coś jest publiczne, może zostać wywołane w dowolnym momencie przez dowolny system. Nie ma powodu, dla którego nie możesz być jednym z tych systemów!

W wysoce zoptymalizowanych bibliotekach chcesz skopiować wprowadzony idiom java.util.ArrayList#ensureCapacity(int).

zapewnić pojemność

  • ensureCapacity jest publiczny
  • ensureCapacity ma wszystkie niezbędne sprawdzanie granic i wartości domyślne itp.
  • ensureCapacity połączenia ensureExplicitCapacity

sureCapacityInternal

  • ensureCapacityInternal jest prywatne
  • ensureCapacityInternal ma minimalne sprawdzanie błędów, ponieważ wszystkie dane wejściowe pochodzą z klasy
  • ensureCapacityInternal TAKŻE połączenia ensureExplicitCapacity

zapewnićExplicitCapacity

  • ensureExplicitCapacity jest również prywatny
  • ensureExplicitCapacitynie ma sprawdzania błędów
  • ensureExplicitCapacity wykonuje rzeczywistą pracę
  • ensureExplicitCapacitynie jest wywoływany z dowolnego miejsca oprócz ensureCapacityiensureCapacityInternal

W ten sposób zaufany kod uzyskuje uprzywilejowany (i szybszy!) Dostęp, ponieważ wiesz, że jego dane wejściowe są dobre. Kod, któremu nie ufasz, przechodzi pewien rygor, aby zweryfikować jego integralność i bombardować lub zapewnić domyślne lub w inny sposób obsłużyć złe dane wejściowe. Oba prowadzą do miejsca, które faktycznie działa.

Jest to jednak używane w ArrayList, jednej z najczęściej używanych klas w JDK. Jest bardzo możliwe, że twoja obudowa nie wymaga takiego poziomu złożoności i rygoru. Krótko mówiąc, gdyby wszystkie ensureCapacityInternalpołączenia zostały zastąpione ensureCapacitypołączeniami, wydajność byłaby naprawdę bardzo dobra. Jest to mikrooptymalizacja prawdopodobnie dokonana dopiero po dogłębnym rozważeniu.


3

Podano już kilka dobrych odpowiedzi i zgodziłbym się z nimi, że tak, obiekt może wywoływać swoje metody publiczne z innych metod. Istnieje jednak niewielkie zastrzeżenie projektowe, na które trzeba uważać.

Metody publiczne zwykle zawierają umowę „weź obiekt w spójny stan, zrób coś sensownego, pozostaw obiekt w (możliwie innym) spójnym stanie”. W tym przypadku „spójny” może oznaczać na przykład, że Lengtha List<T>nie jest większe niż jego Capacityi że odniesienia do elementów w indeksach od 0do Length-1nie rzucą.

Ale w metodach obiektu obiekt może znajdować się w niespójnym stanie, więc gdy wywołasz jedną z publicznych metod, może to zrobić coś bardzo złego, ponieważ nie zostało napisane z myślą o takiej możliwości. Jeśli więc planujesz wywołać swoje metody publiczne z innych metod, upewnij się, że ich umowa polega na „zabraniu obiektu w jakimś stanie, zrób coś sensownego, pozostaw obiekt w (być może innym) (być może niespójnym - ale tylko jeśli początkowy stan był niespójny).


1

Innym przykładem, gdy wywoływanie metody publicznej w innej metodzie publicznej jest całkowicie w porządku, jest podejście CanExecute / Execute. Używam go, gdy potrzebuję zarówno walidacji, jak i niezmiennej konserwacji .

Ale generalnie zawsze jestem ostrożny. Jeśli metoda a()jest wywoływana w metodzie wewnętrznej b(), oznacza to, że metoda a()ta jest szczegółem implementacji b(). Bardzo często oznacza to, że należą one do różnych poziomów abstrakcji. A fakt, że oba są publiczne, sprawia, że ​​zastanawiam się, czy jest to naruszenie zasady pojedynczej odpowiedzialności, czy nie.


-5

Przepraszam, ale muszę się nie zgodzić z większością innych odpowiedzi „tak, możesz” i powiedzieć, że:

Zniechęciłbym klasę, która nazywa jedną metodę publiczną inną

Istnieje kilka potencjalnych problemów z tą praktyką.

1: Nieskończona pętla w odziedziczonej klasie

Więc twoja klasa podstawowa wywołuje metodę1 z metody2, ale potem ty lub ktoś inny dziedziczy po niej i ukrywa metodę1 za pomocą nowej metody, która wywołuje metodę2.

2: Zdarzenia, rejestrowanie itp.

np. mam metodę Add1, która uruchamia zdarzenie „1 added!” Prawdopodobnie nie chcę, aby metoda Add10 wywoływała to zdarzenie, zapisywała dziennik lub cokolwiek innego, dziesięć razy.

3: gwintowanie i inne zakleszczenia

Np. InsertComplexData otwiera połączenie db, rozpoczyna transakcję, blokuje tabelę, a następnie wywołuje InsertSimpleData, otwierając połączenie, rozpoczyna transakcję, czeka na odblokowanie tabeli ...

Jestem pewien, że jest więcej powodów, jedna z pozostałych odpowiedzi dotyczy „edytujesz metodę 1 i dziwisz się, że metoda 2 zaczyna zachowywać się inaczej”

Ogólnie, jeśli masz dwie metody publiczne, które współużytkują kod, lepiej, aby obie wywoływały metodę prywatną, a nie jedną.

Edytować ----

Rozwińmy konkretny przypadek w PO.

nie mamy zbyt wielu szczegółów, ale wiemy, że funkcja ReverseData jest wywoływana przez moduł obsługi zdarzeń, a także metodę ScheduleTransmission.

Zakładam, że odwrotne dane również zmieniają wewnętrzny stan obiektu

Biorąc pod uwagę ten przypadek, uważam, że bezpieczeństwo wątków byłoby ważne, a zatem mój trzeci sprzeciw wobec tej praktyki ma zastosowanie.

Aby zapewnić bezpieczeństwo wątku ReverseData, możesz dodać blokadę. Ale jeśli ScheduleTransmission również musi być bezpieczny dla wątków, będziesz chciał udostępnić tę samą blokadę.

Najłatwiejszym sposobem jest przeniesienie kodu ReverseData na metodę prywatną i wywołanie go przez obie metody publiczne. Następnie można umieścić instrukcję blokady w metodach publicznych i udostępnić obiekt blokady.

Oczywiście możesz argumentować „to się nigdy nie wydarzy!” lub „Mógłbym zaprogramować blokadę w inny sposób”, ale najważniejszą kwestią dotyczącą dobrej praktyki kodowania jest dobre ustrukturyzowanie kodu.

W kategoriach akademickich powiedziałbym, że narusza to literę L na stałe. Metody publiczne są więcej niż publicznie dostępne. Można je również modyfikować przez jego spadkobierców. Twój kod powinien zostać zamknięty w celu modyfikacji, co oznacza, że ​​musisz pomyśleć o tym, co robisz w metodach publicznych i chronionych.

Oto jeszcze jedna: potencjalnie naruszasz DDD. Jeśli twój obiekt jest obiektem domeny, jego publicznymi metodami powinny być Warunki Domeny, które coś znaczą dla firmy. W tym przypadku jest bardzo mało prawdopodobne, aby „kupić tuzin jaj” jest tym samym, co „kupić 1 jajko 12 razy”, nawet jeśli zaczyna się w ten sposób.


3
Zagadnienia te brzmią bardziej jak źle przemyślana architektura niż ogólne potępienie zasady projektowania. (Być może za każdym razem dobrze jest zadzwonić do „dodanego 1”. Jeśli nie, powinniśmy zaprogramować inaczej. Może jeśli dwie metody próbują zablokować ten sam zasób na zasadzie wyłączności, nie powinny się do siebie nawiązywać lub źle napisaliśmy .) Zamiast przedstawiać złe implementacje, możesz skupić się na bardziej solidnych argumentach, które dotyczą dostępu do metod publicznych jako uniwersalnej zasady. Na przykład, biorąc pod uwagę dobry porządny kod, dlaczego jest zły?
doppelgreener

Dokładnie, jeśli tak nie jest, nie masz miejsca na zorganizowanie imprezy. Podobnie z numerem 3 wszystko jest w porządku, dopóki jedna z twoich metod w dół łańcucha nie wymaga transakcji, a potem nie masz racji
Ewan

Wszystkie te problemy są bardziej odzwierciedleniem tego, że sam kod jest złej jakości niż błędna ogólna zasada. Wszystkie trzy przypadki to po prostu zły kod. Mogą one być rozwiązane przez jakiegoś wersji „nie rób tego, to zamiast robić” (być może z pytaniem „co nie chcesz dokładnie?”), A nie podważa ogólnej zasady klasie nazywając własnego społeczeństwa metody Niejasny potencjał nieskończonej pętli jest tutaj jedynym, który z zasady podchodzi do tej kwestii, a nawet wtedy nie jest dokładnie rozwiązany.
doppelgreener

jedynym kodem w moich przykładowych udziałach jest jedna metoda publiczna wywołująca inną. Jeśli uważasz, że jego „zły kod” jest dobry! Takie jest moje twierdzenie
Ewan

To, że możesz użyć młotka do złamania własnej ręki, nie oznacza, że ​​młot jest zły. Oznacza to, że nie musisz robić z nim rzeczy, które łamią ci rękę.
doppelgreener
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.