Jak poradzić sobie z przeciążeniami metod, gdy argumenty są tego samego typu, ale pobierają inną informację

0

Witam,

chciałem się dowiedzieć jak najlepiej rozwiązać taki dylemat.

Mamy taką przykładową metodę:


public MyItem GetSomething(int uniqueID, int page = 1, int pageSize = 10) {
// Zrób coś
return myItem;
}

public MyItem GetSomething(int commonID, int page = 1, int pageSize = 10) {
// Zrób coś podobnego
return myItem;
}

Zastosowanie przeciążenia w tym wypadku z takimi samymi typami argumentów nie jest możliwe, ale zastosowanie dwóch metod o różnych nazwach z kolei jest nieestetyczne, bo metoda zwraca coś z API, którego struktura ma jedną metodę, a ta może pobrać oba argumenty.

W tym wypadku dokładnie odzwierciedlająca strukturę API metoda miałaby taki wygląd:

public MyItem GetSomething(in uniqueID, int commonID, int page = 1, int pageSize = 10) {
// Zrób coś podobnego
return myItem;
}

Niby spoko, ale wymusza to na użytkowniku podanie obu parametrów, gdzie priorytet ma commonID i to na jego podstawie wyświetli się wynik. O ile API nie potrzebuje obu, o tyle metoda w C# już w tym wypadku tak.

Kolejna opcja jaka przyszła mi do głowy:

public MyItem GetSomething(int? uniqueID, int? commonID = null, int page = 1, int pageSize = 10) {
// Zrób coś podobnego
return myItem;
}

W powyższym wypadku null zostanie pominięty, a konieczne jest podanie jednego z argumentów. Tutaj jednak z kolei możliwe jest podanie obu null, więc techniczne też może to wprowadzać błąd i co gorsza trzeba w środku to gdzieś sprawdzić.

Czy jest jakiś magiczny myk, który pozwoliłby estetyczne wymusić na użytkowniku podanie poprawnego parametru uniqueID lub commonID, koniecznie jednego z nich, ale nie pozwalał na obie wartości null? Technicznie podanie obu poprawnych wartości też z punktu widzenia konstrukcji metody API jest dopuszczalne. Jak w takiej sytuacji postępować?

2

Teoretycznie mógłbyś jeszcze zastosować coś w stylu specification pattern. Czyli opakować te wszystkie wartości w jakąś klasę,

3

Możesz jako parametru metody użyć obiektu, w którym na etapie konstruowania sprawdzisz czy przynajmniej jeden z identyfikatorów jest ustawiony, ale ja bym tego specyfikacją nie nazywał

0

Sam tytuł sugeruje nieznajomość zasad języka. Po co się głowić nad czymś czego się nie da zrobić ?

0

Nie głowię się nad tym i język znam. Powyższy post sugeruje niezrozumienie treści posta pierwszego. Sugerowanie się samym tytułem jest deczko żałosne bez zapoznania się z treścią posta ze zrozumieniem. Podałem tylko przykład by zobrazować o co mi dokładnie chodzi. Szukam po prostu estetycznego rozwiązania, czegoś co pozwoliłoby w ładny i wygodny sposób dobierać rodzaj ID do zapytania.

1

A dlaczego nie zastosujesz po prostu Get..Smoething..By..SomethingElse? Przynajmniej wiadomo od razu o co chodzi a nie jakieś podchody.

0

Ehh pisałem o tym. Mogę dodać dwie, a nawet 10 metod, które odwołują się do jakiejś prywatnej wspólnej i tam realizują założenia, jeśli zajdzie potrzeba, pytanie tylko, czy nie dałoby się prościej? Jest konkretne API z dokumentacją, jest jedna metoda. Na razie odpowiedź @var i @szydlak wydaje się całkiem spoko, a na to pytanie które zadałeś powyżej jest odpowiedź w pierwszym poście.

Widzisz wyobraź sobie, że otwierasz dokumentację API, widzisz metodę i chcesz ją wykorzystać, więc wywołujesz sobie klasę klienta i szukasz. O jest! Wspaniale! Ale zaraz, przecież ja chcę użyć commonID, a tu podaje się tylko uniqueID i co teraz? Zaczynasz szukać, o jest ByCommonID fajnie. Jest ok, tylko zastanawia mnie czy nie można tego uprościć, zrobić lepiej, fajniej, sprytniej. Ot cała idea wątku.

Na razie na podstawie odpowiedzi @szydlak i @var za co dziękuję:

public class APIDs
    {
        public int? UniqueID { get; set; }
        public int? CommonID { get; set; }
        public string textID{ get; set; }

        public bool IsValid()
        {
            if (UniqueID != null) return true;
            if (CommonID != null) return true;
            if (textID!= null) return true;

            return false;
        }
    }
1

Może taki mały overengineering? Tworzenie osobnych klas dla różnych kryteriów gwarantuje, że nie zostaną podane parametry, których połączenie nie ma sensu i nie będzie możliwości, że jeden nadpisuje drugi itp. co może być mylące.

abstract class Criteria
{
    public int Page { get; set; }
    public int PageSite { get; set; }

    internal abstract MyItem NieWiemJakToNazwać(Func<int?, int?, int, int, MyItem> getSomethingInternal);
}

sealed class CommonCriteria : Criteria
{
    public int CommonId { get; set; }

    internal MyItem NieWiemJakToNazwać(Func<int?, int?, int, int, MyItem> getSomethingInternal)
    {
        return getSomethingInternal(null, CommonId, Page, PageSite);
    }
}

sealed class UniqueCriteria : Criteria
{
    public int UniqueId { get; set; }

    internal MyItem NieWiemJakToNazwać(Func<int?, int?, int, int, MyItem> getSomethingInternal)
    {
        return getSomethingInternal(UniqueId, null, Page, PageSite);
    }
}

public MyItem GetSomething(Criteria criteria)
{
    return criteria.NieWiemJakToNazwać(GetSomethingInternal);
}

private MyItem GetSomethingInternal(in uniqueID, int commonID, int page = 1, int pageSize = 10)
{
    // właściwa implementacja
}
0

Bardzo dziękuję za odpowiedzi. Poeksperymentuję sobie i zobaczę co mi bardziej leży.

1
var napisał(a):

Możesz jako parametru metody użyć obiektu, w którym na etapie konstruowania sprawdzisz czy przynajmniej jeden z identyfikatorów jest ustawiony, ale ja bym tego specyfikacją nie nazywał

Ja bym się przychylał do czegoś takiego właśnie. Tak przykładowo:

public class ApiId
{
   private int? _uniqueId;
   private int? _commonId;
   public int UniqueId => _uniqueId ?? throw new Exception(...);
   public int CommonId => _commonId ?? throw new Exception(...);
   public bool IsUniqueIdSet => _uniqueId.HasValue;
   public bool IsCommonIdSet => _commonId.HasValue;

   public ApiId(int? uniqueId, int? commonId)
   {
      _uniqueId = uniqueId;
      _commonId = commonId;
   }
}

A funkcja by wyglądała jakoś tak:

public MyResult MyApiFunction(ApiId id)
{
    if (id.IsUniqueIdSet)
       return MyApiFunctionUniqueId(id.UniqueId);
   if (id.IsCommonIdSet)
      return MyApiFunctionCommonId(id.CommonId);
   
   throw new Exception(...);
}

private MyResult MyApiFunctionUniqueId(int uniqueId)
{
....
}

private MyResult MyApiFunctionCommonId(int commonId)
{
....
}

Zaznaczam, że to jest jedno z setki możliwych podejść. Pytanie na czy możesz zamiennie pobrać informację mając unique id i common id.
Wiele też zależy od skali problemu, bo gdyby takich różnych identyfikatorów (lub innych danych) po których byś pobierał byłoby więcej to można się pokusić o coś w rodzaju tego co podrzucił @mad_penguin.

EDIT:
Można jeszcze prościej po prostu tworząc osobne struktury dla tych identyfikatorów:

public class UniqueId
{
   public int Value {get; set;}
   public UniqueId(int value) => Value = value;
   public static implicit operator int(UniqueId id) => id.Value;
   public static explicit operator UniqueId(int value) => new UniqueId(value); 
}

Wtedy będziesz mógł mieć swoje przeciążenia metod ;)

0

Wszystko zależy tak na prawdę od tego co się dzieje tu:

public MyItem GetSomething(int uniqueID, int page = 1, int pageSize = 10) {
// Zrób coś
return myItem;
}

Bo jeśli tu jest jakiś klient który woła Api w zależności od parametru można jeszcze inaczej

1

W zależności od Twoich potrzeb, może też przydać się wzorzec STRATEGIA.

1

Ja chciałbym zobaczyć Twój restowy uri i co tam ma się rzeczywiście pobierać. Może problem jest na innym poziomie niż implementacja tutaj.Np jeśli chcesz mieć
GET /posts/{commonid}
GET /posts/{uniqueId}
to faktycznie kiepsko, ale pytanie czemu tego potrzebujesz? W rescie pracujesz na zasobach i jeśli masz ten specyficzny przypadek, że dany zasób ma kilka id, to najlepiej to wyjawić explicit dla klienta z czego korzysta i mieć nie tylko osobne metody ale uri np
GET /posts/title={myTitle}
GET /posts/{uniqueId}

Inaczej, możesz mieć sytuacje, ze klient nie wie z czego korzysta i może to doprowadzić to do jakiś nie ciekawych błędów. (np prosisz o zasob z common ID =1, a dostajesz z Unique ID = 1). Nie bez powodu nie da się tego łatwo zaimplementować. ANi kompilator ani runtime nie powinien próbowa zgadywać co klient danego API chce zrobić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1