Interfejsy - czy dobrze myślę

0

Cześć,

zastanawiam się nad następującym zagadnieniem.
Mianowicie mam sobie 2 klasy:

  • jedna do pobierania danych o jakieś encji z bazy danych - do wykorzystania w API.
  • druga do pobierania danych o jakieś encji odpytując się API - do wykorzystania w aplikacji na androida (Xamarin).

Zastanawiam się czy mogę (i czy powinienem) stworzyć jeden interfejs, tak żeby obie te klasy implementowały ten interfejs. I tutaj i tutaj będę pobierał te same informacje, tylko w inny sposób.

6

Czy istnieje miejsce, w którym chciałbyś wykorzystać taki interfejs? Tworzenie interfejsu dla samego faktu jego istnienia jest bezcelowe.

1

Jak pobierasz takie same informacje (rezultat to ta sama klasa) to jak najbardziej ma sens stworzenie wspólnego interfejsu dla tego DAO. Zwłaszcza jak będzie prawdopodobieństwo że jedno DAO możesz zastąpić drugim.
Jak klasy zwracane są różne to w zasadzie będzie to interfejs generycznego DAO (w dawnych czasach było to popularne w Javie, ale zysk z tego średni)

1

Pobieranie jednej sztuki obiektu wydaje się podobne, ale jak sobie zrobimy kawę, okazuje się że inny jest klucz/parametr/filtry zapytania, lecą inne wyjątki, czy inne różnice.

A pobranie zbiorowości to ZUPELNIE inne historie

0

@AnyKtokolwiek: Wydaje mi się, że w moim przypadku nie. Api jest pisane tylko i wyłącznie na potrzeby tej aplikacji. Jeżeli w WebApi wyciągam jakieś dane z bazy to na tych samych danych będę później pracował w aplikacji mobilnej.

Np. jak wykonuje requets do WebApi, /api/costam
i z niego response dla mnie to obiekt A to jak ten request trafi do webApi to WebApi zwróci też obiekt A. I na tym obiekcie A będę dalej coś robił w aplikacji.

Więc zarówno tutaj jak i tutaj miałbym coś typu:

public IEnumerable<A> GetCosTam ()
{
  //i tutaj logika dla pobrania z DB, albo odwolania sie do API
}

1

No dobrze, a po co ten interfejs? Interfejsów używa się tam, gdzie jest szansa, że będzie potrzebna inna implementacja. Czyli w tym przypadku albo aplikacja odpytywałaby bezpośrednio bazę, albo API pytałoby API. Absurd, nie?

A tak przy okazji, to pchanie encji na twarz zawsze jest złym pomysłem. To, co zwraca baza, i to co zwraca API, to nie powinny być te same rzeczy.

0
somekind napisał(a):

No dobrze, a po co ten interfejs? Interfejsów używa się tam, gdzie jest szansa, że będzie potrzebna inna implementacja. Czyli w tym przypadku albo aplikacja odpytywałaby bezpośrednio bazę, albo API pytałoby API. Absurd, nie?

Chyba rozumiem. Nie ma sensu tworzyć jednego interfejsu i nie ma tutaj znaczenia, że w solucji mam projekty, gdzie jeden korzystałby z implementacji dla DB (EF), a drugi z implementacji dla clienta API.

Nabrałem chyba jakiegoś złego przeświadczenia, że jak wstrzykuje gdzieś zależność to powinno być to wstrzyknięte jako interfejs, rozumiem, że jest to złe myślenie?

A tak przy okazji, to pchanie encji na twarz zawsze jest złym pomysłem. To, co zwraca baza, i to co zwraca API, to nie powinny być te same rzeczy.

Rozumiem, że lepiej żeby powstała tutaj jakaś warstwa pośrednia i kolejny zestaw klas?

3
kobi55 napisał(a):

Chyba rozumiem. Nie ma sensu tworzyć jednego interfejsu i nie ma tutaj znaczenia, że w solucji mam projekty, gdzie jeden korzystałby z implementacji dla DB (EF), a drugi z implementacji dla clienta API.

Owszem, w tym przypadku nie ma to sensu.
Interfejsy mogą służyć do wydzielania jakichś wspólnych zachowań, ale aby to miało sens, to te wspólne abstrakcje muszą należeć do jednej klasy abstrakcji, istnieć na jednym poziomie koncepcyjnym, w jednej warstwie. Gdybyś miał tylko aplikację mobilną, która może pobierać takie same dane albo z API albo bezpośrednio z bazy, to taki interfejs miałby sens. Gdybyś miał tylko API, które dostarcza takich danych ze swojej bazy albo z jakiegoś innego API, to taki interfejs miałby sens.
Ale w tej sytuacji, gdy mobilka czyta z API, a API z bazy, to są to dwa różne światy i próba łączenia ich w jeden niczego nie wnosi, bo i tak nigdy nie zastąpisz jednej implementacji drugą. Mam wrażenie, że cały problem wziął się stąd, że masz to wszystko w jednej solucji. Zazwyczaj oddziela się frontend od backendu,

Nabrałem chyba jakiegoś złego przeświadczenia, że jak wstrzykuje gdzieś zależność to powinno być to wstrzyknięte jako interfejs, rozumiem, że jest to złe myślenie?

Owszem, jest złe. Aczkolwiek mocno promowane w tutorialach, blogach i innych patologicznych miejscach. Ktoś kiedyś powiedział, że należy programować do abstrakcji, ktoś inny, że interfejsy to abstrakcje (co jest bzdurą totalną), no i w efekcie miliony programistów robią interfejs do każdej klasy, w 80-90% całkowicie bezużyteczny.

Rozumiem, że lepiej żeby powstała tutaj jakaś warstwa pośrednia i kolejny zestaw klas?

No tak jak wspomniałem, API i aplikacja mobilna, to są oddzielne warstwy fizyczne, trochę dziwne, że operują na tych samych klasach.
Po drugie, sytuacja, że kształt danych w bazie odpowiada dokładnie temu, co trafia do API jest wręcz niespotykana. W bazie zazwyczaj trzyma się jakieś dane relacyjne, a API operuje na bardziej płaskich strukturach. Prędzej czy później coś się zmieni i rozjedzie. A jak nie ma danych relacyjnych, to baza relacyjna wydaje się zbędna.

1

@somekind:

interfejsy to abstrakcje (co jest bzdurą totalną)

czemu?

1

@WeiXiao: Chyba chodzi o to, że przy pomocy interfejsów można budować abstrakcje, ale nie można przy nich postawić znaku równości. Jeśli ja to dobrze rozumiem, to abstrakcją nie można nazwać na przykład takiego czegoś:

public interface ISomeCollection
{
    IEnumerable<string> GetAll();
}

public class SomeCollection : ISomeCollection
{
    public void Add(string item)
    {
        // ...
    }

    public IEnumerable<string> GetAll()
    {
        // ...
    }
}

public class SomeClass
{
    private readonly SomeCollection _collection = new();

    public ISomeCollection GetCollection()
    {
        return _collection;
    }
}

Rolą interfejsu jest tu enkapsulacja - ukrywanie metody Add i jest to jej jedyne zadanie. Powiedzmy, że nie zostanie zaimplementowany przez żadną inną klasę i nie powiedziałbym, że ISomeCollection jest wtedy abstrakcją, pomimo że jest interfejsem. Choć nie jestem tego na 100% pewien i lepiej niech się mądrzejsi wypowiedzą.

0

Wyjdźmy od definicji:

In software engineering and computer science, abstraction is:

The process of removing physical, spatial, or temporal details[2] or attributes in the study of objects or systems to focus attention on details of greater importance;[3] it is similar in nature to the process of generalization;

the creation of abstract concept-objects by mirroring common features or attributes of various non-abstract objects or systems of study[3] – the result of the process of abstraction.

Obie te funkcje są pełnione przez międzymordzie.

Interfejs zapewnia nam kontrakt na coś i uwalnia nas od jego szczególów, a to że przy okazji ukrywa jakąś metodę to raczej nie ujmuje mu funkcji abstrakcji

0
WeiXiao napisał(a):

Obie te funkcje są pełnione przez międzymordzie.

Raczej przez programistę, któremu może udać się stworzyć dobrą abstrakcję, a może nie.

Interfejs zapewnia nam kontrakt

A w jaki niby sposób interfejs może cokolwiek zapewnić, w szczególności kontrakt? Interfejs może zapewnić jedynie, że jego implementacja będzie miała zdefiniowane metody z tego interfejsu, ale w żaden sposób nie zapewni, że jakikolwiek kontrakt zostanie spełniony.

na coś i uwalnia nas od jego szczególów, a to że przy okazji ukrywa jakąś metodę to raczej nie ujmuje mu funkcji abstrakcji

Interfejs to tylko konstrukcja języka, można jej użyć zarówno do głupich, jak i mądrych rzeczy. Może być używany w jakiejś abstrakcji, ale sam w sobie nie jest abstrakcją.
Można też mieć abstrakcje bez interfejsów, na samych obiektach. Można też bez obiektów, na samych funkcjach.

0
somekind napisał(a):

Interfejs może zapewnić jedynie, że jego implementacja będzie miała zdefiniowane metody z tego interfejsu

I to jeszcze nie ma pewności, że zostaną wystawione przez klasę implementującą ten interfejs (o czym się niedawno dowiedziałem).

internal interface IInterface
{
    void Method();
}
    
internal class SomeClass : IInterface
{
    void IInterface.Method()
    {
        // ...
    }
}

I takie wywołanie spowoduje błąd:

var some = new SomeClass();
some.Method();

W ogóle zastanawiam się czy takie coś nie łamie jakiś tam zasad OOP?

0

Interfejs może zapewnić jedynie, że jego implementacja będzie miała zdefiniowane metody z tego interfejsu, ale w żaden sposób nie zapewni, że jakikolwiek kontrakt zostanie spełniony.

no i tym kontraktem jest to "zapewnienie, że jego implementacja będzie miała metody z tego interfejsu"

Można też mieć abstrakcje bez interfejsów, na samych obiektach. Można też bez obiektów, na samych funkcjach.

to że 🍏 jest owocem wcale nie czyni 🍉 mniej owocem

czy my dyskutujemy nad "czy narzędzie do tworzenia abstrakcji" jest abstrakcją? jeżeli tak, to zatem what's the point?

coś zyskamy na tej precyzyjności wyrażania się?

@maszrum

Gdy powołasz się na interfejs, a nie wyjdziesz z varem, to już możesz :)

IInterface some = new SomeClass();
some.Method();
1
WeiXiao napisał(a):

no i tym kontraktem jest to "zapewnienie, że jego implementacja będzie miała metody z tego interfejsu"

NotImplementedException rzucony z tej metody też jest zapewnieniem implementacji i spełnienia kontraktu?

to że 🍏 jest owocem wcale nie czyni 🍉 mniej owocem

Owszem, Ty zaś próbujesz udowodnić, że każdy prostokąt jest kwadratem, podczas gdy ja twierdzę, że to nie jest prawda.

czy my dyskutujemy nad "czy narzędzie do tworzenia abstrakcji" jest abstrakcją? jeżeli tak, to zatem what's the point?

To nie jest narzędzie do tworzenia abstrakcji, co najwyżej czasami może zostać tak użyte.

coś zyskamy na tej precyzyjności wyrażania się?

Zaprzestanie powtarzania bzdur.

0

@somekind:

NotImplementedException rzucony z tej metody też jest zapewnieniem implementacji i spełnienia kontraktu?

Zależy kto pyta

Jeżeli biznes, to nie.

Jeżeli kompilator, to tak.

0

Biznes nie ma nic do interfejsów, podobnie jak kompilator do kontraktów. Pyta programista. A programista nie odwraca implikacji i wie, że z tego, ze coś może być użyte do zaimplementowania abstrakcji, nie oznacza, że użycie tego jest automatycznie abstrakcją.

1

W sumie na początku nie rozumiałem o co chodzi @somekind ale teraz rozumiem.
@WeiXiao
załóżmy że masz taki interfacje:

interface CurrencyRateProvider {
  Option<ExchangeRate> findRateByDate(Currency from, Currency to, LocalDate exchangeDate);
}

I to będzie abstrakcja. Ale nikomu nikt nie zabroni (conajwyżej powinni wypowiedzeź umowę :D )zrobić czegoś takieg:

interface CurrencyRateProvider {
  HttpResponseEntity<ExchangeRate> findRateByDate(Currency from, Currency to, LocalDate exchangeDate);//HttpResponseEntity z jakiegoś frameworka
}

No i gdzie w drugim przypadku jest abstrakcja?

0

No czyli tak jak wyżej - rozchodzi się o "narzędzie" "dobre użycie"

Ma to sens

1
somekind napisał(a):

Interfejsy mogą służyć do wydzielania jakichś wspólnych zachowań, ale aby to miało sens, to te wspólne abstrakcje muszą należeć do jednej klasy abstrakcji, istnieć na jednym poziomie koncepcyjnym, w jednej warstwie.

Interfejsy sprawdzają się dobrze e kontekście opisanym powyżej, na przykład w jakiejś strategii. Czy w sytuacji gdzie koniecznie trzeba coś zamockować. Poza tym najczęściej tylko zaciemniają kod i zaśmiecają solucję bo są używane bez namysłu. Ostatnio wyrzuciłem z projektu 80% interfejsów. I nic się nie stało poza zdziwieniem innych że tak można.

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