[C#] Określenie, która (prywatna) metoda klasy będzie się w niej miała wykonać

Witam,

chciałbym w C# jakoś sprytnie i ciekawie (architektonicznie) rozwiązać pewien problem.

Mianowicie mamy taką sytuację. Piszemy bibliotekę, (API) która realizuje jakiś tam algorytm, podajemy mu na wejście jakieś dane, a na wyjściu dostajemy je jakoś przetworzone. I teraz chcemy dać pewien wybór co do jakichś tam szczegółów tego algorytmu (w zależności od niego dostaniemy różne dane na wyjściu). I tak - mamy sobie grupę metod, np. void FunA(), void FunB(), void FunC(), do których nie może być dostępu z zewnątrz (private/internal), ale jedna z nich musi być wykonana w trakcie działania algorytmu i chcemy dać wybór, która. Oczywiście można by zrobić najprościej jakiegoś enuma i potem switchować na jego podstawie (przykład poniżej), ale z wiadomych względów jest to złe rozwiązanie. Mam pytanie, czym powinienem się zainteresować, żeby to jakoś fajnie rozwiązać? Jak do tego podejść? Jak to ugryźć?

Z góry dziękuję za pomoc!

Pozdrawiam :slight_smile:

enum Fun



{



  A, B, C



}







public Fun fun { get; set; }







private void FunA() {}



private void FunB() {}



private void FunC() {}







public DoWork()



{



  ...



    switch(fun)



  {



    case A: FunA(); break;



    case B: FunB(); break;



    case C: FunC(); break;



  }



  ...



}

EDIT: Przepraszam, za rozjechany pseudokod, w polu edycji wygląda normalnie, po opublikowaniu wszystko się rozjeżdża

A czemu enum i switch to złe rozwiązanie? Bo szczerze mówiąc dla mnie nie jest to wiadome, no chyba że w C# w jakiś dziwny sposób to działa.

 

Ewentualnie enuma można by zastąpić wartościami defined.

Ponieważ, gdybym chciał to w przyszłości rozwijać i dodawać więcej podobnych funkcji, to zawsze musiał bym dodać wartość do enum, dopisać funkcję i dodać kolejnego case’a w switchu. A ja bym chciał najlepiej tylko dopisać metodę i już, ew. jakaś rejestracja na samym początku gdzieś w konstruktorze, że ta metoda również może być tam użyta. Przy trzech funkcjach to nie da jakiejś różnicy i nie utrudni pracy, ale wyobraź sobie tego switcha i czytelność kodu przy powiedzmy już 50 metodzie

Wysłane z mojego Nexus 5X przy użyciu Tapatalka

Nie znam C# więc rozwiązanie musisz znaleźć sam, ja Ci tylko mogę podać ewentualną istniejącą ścieżkę dla rozwiązania Twojego problemu. W tej chwili do głowy przyszły mi dwa rozwiązania:

  1. Wywoływanie funkcji przez dynamiczne podanie jej nazwy, coś w stylu:

    callFunction(‘prefix’ + zmienna)

  2. Tablica asocjacyjna/drzewo ze wskaźnikiem do funkcji. W tedy w konstruktorze dodajesz do takiej tablicy/drzewa wskaźnik do funkcji oraz jej “identyfikator” a potem wywołujesz taką funkcję.

 

Pierwsze rozwiązanie raczej nie będzie możliwe, bo to raczej jest do zrobienia w jeżykach skryptowych, w kompilowalnych zostaje jedyni druga opcja. C# powinien mieć możliwość by zrobić to za pomocą drugiego sposobu. Być może jest coś w nim dodane co pozwoliłoby jeszcze łatwiej takie coś zrobić, ale nie znam na tyle języka by Ci w tym momencie pomóc.

Ja chyba bym wyszedł od rozbudowy hierarchii klas. Skoro funkcje FunA, FunB i FunC mają taki sam zwracany typ i listę parametrów, to:

 

  1. Trzeba stworzyć interfejs, IFun, zawierający definicję metody Fun,

  2. Trzeba stworzyć trzy klasy, FunA, FunB i FunC, implementujące IFun w różny sposób (metodą A, B, C),

  3. Funkcja, która ma za zadanie obliczenia oraz wybór metody obliczeń dostaje dane oraz obiekt implementujący interfejs IFun, z którego odpala odpowiednią funkcję. Albo, alternatywnie, obiekt liczący dostaje obiekt klasy implementującej IFun w konstruktorze.

 

Rozwiązanie o tyle lepsze od enuma, że zapewnia dość łatwy sposób dodawania kolejnych potencjalnych implementacji - po prostu robimy kolejne klasy implementujące IFun.

 

Tak zasadniczo to w C# się da tak zrobić, ale to gwałt na wszystkim :wink:

Dzięki za zainteresowanie.

Fizyda: mktos mnie uprzedził co do odpowiedzi w sprawie wywoływania funkcji po nazwie w stringu :slight_smile:

mktos: W zasadzie już problem rozwiązałem w dość podobny sposób, jednak nie jest to tak proste, gdyż zależało mi na tym, aby te funkcje można było wywołać TYLKO wewnątrz biblioteki - interfejsy w tym nie pomagają, bo z założenia deklarują metody publiczne, a nie internal, czyli dodając bibliotekę do referencji w jakimś tam programie, można byłoby wywołać każdą z tych funkcji osobno, a mają one być możliwe do wywołania tylko wewnątrz biblioteki. Ale i tą trudność udało się pokonać, a zasada działania, jest nawet trochę zbliżona do tego, co opisałeś.

 

W każdym razie dzięki panowie.

Pozdrawiam

Temat mnie nieco zaciekawił.

Możesz zroibić prywatny interfejs - zagnieżdżony w klasie. 
Przykład takiej implementacji masz np tutaj: http://stackoverflow.com/questions/792908/what-is-a-private-interface

Jakie ostatcznie wybrałeś rozwiazanie? Mógłbyś napisać lub pokazać skrótowo ideę?

Całkiem ciekawe, chociaż rozwiązałem to inaczej:

  1. Zasada działania opiera się o dość okrojone dependency injection (przycięte do problemu).

Mianowicie mamy klasę ze słownikiem od danego enuma (dla przykładu z góry od Fun) oraz Type, a w niej publiczny indekser zwracający obiekt typu w zależności od wartości podanego enuma oraz oraz internal metodę dodającą wartości do tego słownika.

 

  1. Z racji, że nie możemy do tego wykorzystać interfejsów, więc tworzymy publiczną klasę abstrakcyjną np. AbstractFun, w której jest owy publiczny enum z możliwymi funkcjiami, czyli np. FunA, FunB, FunC; następnie jest delegat internal z opisem tej metody, a następnie obiekt delegata, do wywoływania (również internal)

 

  1. Następnie implementacja metod - wszystkie dziedziczą oczywiście po AbstractFun (np. internal class FunA : AbstractFun). Do delegata z klasy nadrzędnej przypisujemy odpowiednią implementację danej metody.

 

Oczywiście ideę opisałem w miarę prosto, ale ja to jeszcze zrobiłem generycznie, co mi daje np. takie możliwości (teraz przykład bardziej konkretny)

Tworzę sobie właściwość public Całkowanie Całkowanie { get; set; } oraz np. public Analizator<Całkowanie, Całkowanie.Metody> MetodyCałkowania {get;}

Potem oczywiście tworzę obiekt analizatora i rejestruję metody

MetodyCałkowania.Rejestruj<Kwadraty>(Całkowanie.Metody.Kwadraty)

MetodyCałkowania.Rejestruj<Trapezy>(Całkowanie.Metody.Trapezy)

 

W metodzie, w której będę chciał to wywołać robię Całkowanie.Wywołaj(/*jakieś argumenty zgodne z delegatem*/)

 

A wykorzystanie jest następujące:

var api = new API();

api.Całkowanie = api.MetodyCałkowania[Całkowanie.Metody.Trapezy];

api.Licz(); //metoda w której ma być to wykonanie.

 

Dodanie metody sprowadza się raptem do napisania jej implementacji oraz zarejestrowania jej w ,Analizatorze", no… dobra, dodania jeszcze w abstrakcyjnej klasie wartości do enuma.

Generyczne wykonanie daje natomiast jeszcze jeden plus - jak będę chciał mieć kolejną grupę metod, to dodać jest banalnie łatwo, np. jakbym chciał również na różne sposoby rozwiązywać jakieś macierze, to robię tylko klasę abstrakcyjną, do niej klasy implementacji metod (np. Gauss itp.) a w klasie api dodaję tylko obiekt Analizatora od np. Macierze i od Macierze.Metody i analogicznie jak przy całkach.

 

Mam nadzieję, że w miarę zrozumiale opisałem ideę, nie chciałem konkretami rzucać, bo to do pracy inżynierskiej, a potem bym się musiał tłumaczyć, że to nie plagiat, bo to ja sam napisałem.

Ma to sporo plusów - jest uniwersalne, mogę wiele ,typów" metod przypisywać z zewnątrz do wykonania (np. właśnie metody całkowania czy jakichś wyliczeń na macierzach), łatwo dopisuje się kolejne metody, nie ma ,brzydkiego" kodu we właściwej metodzie (ify, switche - 3 switche, każdy po 10 case’ów); swoją drogą to wgl. nigdzie nie ma ,brzydkiego kodu", oczywiście cel jest osiągnięty - bo z zewnątrz można przypisać właściwą metodę, ale nie można jej wywołać. Ale ma też jeden minus - przy dodawaniu implementacji kolejnej metody trzeba pamiętać, że oprócz jej implementacji, trzeba jeszcze w klasie nadrzędnej dodać wartość enuma, oraz zarejestrować ją w klasie api.

 

Mam nadzieję, że się spodoba, jeśli jednak ma ktoś pomysł jakby to ulepszyć, albo zupełnie inaczej zrobić - ale lepiej (żeby cel był osiągnięty, ale dodanie metody wymagało mniej pracy) - to niech się podzieli, będę wdzięczny.

 

//EDIT

Jak się tak jeszcze nad tym zastanowić, to niewielkim nakładem pracy da się ten przykład poprawić i wyeliminować enum. Wówczas przy dodawaniu nowej metody wystarczy tylko jej implementacja i rejestracja w konstruktorze

 

Pozdrawiam.