Przekazać obiekt dwukrotnie do tej samej metody lub skonsolidować za pomocą połączonego interfejsu?

15

Mam metodę, która tworzy plik danych po rozmowie z tablicą cyfrową:

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

Oto boardFileAccessi boardMeasurerta sama instancja Boardobiektu, który implementuje zarówno IFileAccessi IMeasurer. IMeasurerjest stosowany w tym przypadku do pojedynczej metody, która spowoduje, że jeden pin na płycie będzie aktywny, aby wykonać prosty pomiar. Dane z tego pomiaru są następnie przechowywane lokalnie na płycie za pomocą IFileAccess. Boardznajduje się w osobnym projekcie.

Doszedłem do wniosku, że CreateDataFilerobienie jednej rzeczy polega na wykonaniu szybkiego pomiaru, a następnie zapisaniu danych, a wykonanie obu w tej samej metodzie jest bardziej intuicyjne dla kogoś innego, kto korzysta z tego kodu, niż konieczności wykonania pomiaru i zapisania do pliku jako osobne wywołania metod.

Wydaje mi się niewygodne dwukrotne przekazanie tego samego obiektu do metody. Zastanawiałem się nad stworzeniem lokalnego interfejsu, IDataFileCreatorktóry będzie rozszerzany, IFileAccessa IMeasurernastępnie będzie miał implementację zawierającą Boardinstancję, która po prostu wywoła wymagane Boardmetody. Biorąc pod uwagę, że ten sam obiekt płytki byłby zawsze używany do pomiaru i zapisu plików, czy złym rozwiązaniem jest dwukrotne przekazywanie tego samego obiektu do metody? Jeśli tak, to czy używanie lokalnego interfejsu i implementacji jest odpowiednim rozwiązaniem?

pavuxun
źródło
2
Trudno jest zrozumieć intencje twojego kodu na podstawie nazw, których używasz. Interfejs o nazwie IDataFileCreator przekazywany do metody o nazwie CreateDataFile jest oszałamiający. Czy konkurują o obowiązek przechowywania danych? Jaką klasą jest metoda CreateDataFile? Pomiary nie mają nic wspólnego z utrwalaniem danych, więc wszystko jest jasne. Twoje pytanie nie dotyczy największego problemu z kodem.
Martin Maat,
Czy kiedykolwiek można sobie wyobrazić, że obiekt dostępu do pliku i obiekt pomiaru mogą być dwoma różnymi obiektami? Powiedziałbym tak. Jeśli go teraz zmienić, musisz zmienić go z powrotem w wersji 2, który obsługuje wykonywania pomiarów w całej sieci.
user253751
2
Oto inne pytanie - dlaczego obiekty dostępu do plików danych i obiekty pomiarowe są w pierwszej kolejności takie same?
user253751

Odpowiedzi:

40

Nie, w porządku. Oznacza to jedynie, że interfejs API jest nadmiernie zaprojektowany w odniesieniu do bieżącej aplikacji .

Ale to nie dowodzi, że nigdy nie będzie przypadku użycia, w którym źródło danych i miernik są różne. API ma na celu zaoferowanie możliwości programistom aplikacji, z których nie wszystkie zostaną wykorzystane. Nie powinieneś sztucznie ograniczać, co mogą zrobić użytkownicy interfejsu API, chyba że komplikuje to interfejs API, co obniża zrozumiałość sieci.

Kilian Foth
źródło
7

Zgadzam się z odpowiedzią @ KilianFoth, że jest w porządku.

Jeśli jednak chcesz, możesz utworzyć metodę, która pobiera pojedynczy obiekt, który implementuje oba interfejsy:

public object CreateDataFile<T_BoardInterface>(
             T_BoardInterface boardInterface
    )
    where T_BoardInterface : IFileAccess, IMeasurer
{
    return CreateDataFile(
                boardInterface
            ,   boardInterface
        );
}

Nie ma ogólnego powodu, dla którego argumenty muszą być różnymi obiektami, a jeśli metoda wymagała różnych argumentów, byłby to szczególny wymóg w umowie, którą powinna wyjaśnić.

Nat
źródło
4

Doszedłem do wniosku, że CreateDataFilerobienie jednej rzeczy polega na wykonaniu szybkiego pomiaru, a następnie zapisaniu danych, a wykonanie obu w tej samej metodzie jest bardziej intuicyjne dla kogoś innego, kto korzysta z tego kodu, niż konieczności wykonania pomiaru i zapisania do pliku jako osobne wywołania metod.

Myślę, że to właściwie twój problem. Metoda nie robi nic . Wykonuje dwie odrębne operacje, które dotyczą operacji we / wy na różnych urządzeniach , z których obie odciążają inne obiekty:

  • Pobierz pomiar
  • Zapisz ten wynik gdzieś w pliku

Są to dwie różne operacje we / wy. Warto zauważyć, że pierwszy z nich w żaden sposób nie powoduje mutacji systemu plików.

W rzeczywistości powinniśmy zauważyć, że istnieje domniemany środkowy krok:

  • Pobierz pomiar
  • Serializuj pomiar do znanego formatu
  • Zapisz zserializowany pomiar do pliku

Twój interfejs API powinien dostarczyć każdy z nich osobno w jakiejś formie. Skąd wiesz, że dzwoniący nie będzie chciał wykonać pomiaru bez przechowywania go w dowolnym miejscu? Skąd wiesz, że nie będą chcieli uzyskać pomiaru z innego źródła? Skąd wiesz, że nie będą chcieli przechowywać go w innym miejscu niż urządzenie? Istnieje dobry powód, aby oddzielić operacje. Na gołej minimum, każdy poszczególny element powinien być dostępny dla każdego obiektu wywołującego. Nie powinienem być zmuszany do zapisywania pomiaru do pliku, jeśli mój przypadek użycia tego nie wymaga.

Na przykład możesz oddzielić takie operacje.

IMeasurer ma sposób na pobranie pomiaru:

public interface IMeasurer
{
    IMeasurement Measure(int someInput);
}

Twój typ pomiaru może być po prostu czymś prostym, na przykład a stringlub decimal. Nie twierdzę, że potrzebujesz do tego interfejsu lub klasy, ale czyni to przykład bardziej ogólnym.

IFileAccess ma jakąś metodę zapisywania plików:

interface IFileAccess
{
    void SaveFile(string fileContents);
}

Następnie potrzebujesz sposobu serializacji pomiaru. Zbuduj to w klasie lub interfejsie reprezentującym pomiar, lub zastosuj metodę użyteczności:

interface IMeasurement
{
    // As part of the type
    string Serialize();
}

// Utility method. Makes more sense if the measurement is not a custom type.
public static string SerializeMeasurement(IMeasurement m)
{
    return ...
}

Nie jest jasne, czy ta operacja serializacji została jeszcze rozdzielona.

Ten rodzaj separacji poprawia interfejs API. Pozwala to osobie dzwoniącej decydować, czego potrzebują i kiedy, zamiast wymuszać z góry przyjęte pomysły na temat tego, co należy wykonać we / wy. Dzwoniący powinni mieć kontrolę nad wykonywaniem dowolnej prawidłowej operacji, niezależnie od tego, czy uważasz, że jest to przydatne, czy nie.

Gdy będziesz mieć osobne implementacje dla każdej operacji, twoja CreateDataFilemetoda stanie się tylko skrótem

fileAccess.SaveFile(SerializeMeasurement(measurer.Measure()));

Warto zauważyć, że po wykonaniu tego wszystkiego twoja metoda wnosi bardzo niewielką wartość. Powyższy wiersz kodu nie jest trudny dla twoich rozmówców do bezpośredniego użycia, a twoja metoda jest najwyżej dla wygody. Powinno być i jest czymś opcjonalnym . I to jest poprawny sposób działania API.


Po uwzględnieniu wszystkich istotnych części i stwierdzeniu, że metoda jest jedynie wygodą, musimy ponownie sformułować pytanie:

Jaki byłby najczęstszy przypadek użycia dla Twoich rozmówców?

Jeśli chodzi o to, aby typowy przypadek użycia pomiaru i pisania na tej samej płycie był nieco wygodniejszy, wówczas sensowne jest po prostu udostępnienie go Boardbezpośrednio w klasie:

public class Board : IMeasurer, IFileAccess
{
    // Interface methods...

    /// <summary>
    /// Convenience method to measure and immediate record measurement in
    /// default location.
    /// </summary>
    public void ReadAndSaveMeasurement()
    {
        this.SaveFile(SerializeMeasurement(this.Measure()));
    }
}

Jeśli to nie poprawi wygody, nie zawracałbym sobie głowy tą metodą.


Ta metoda wygody rodzi jeszcze jedno pytanie.

Czy IFileAccessinterfejs powinien wiedzieć o typie pomiaru i jak go serializować? Jeśli tak, możesz dodać metodę do IFileAccess:

interface IFileAccess
{
    void SaveFile(string fileContents);
    void SaveMeasurement(IMeasurement m);
}

Teraz dzwoniący po prostu zrób to:

fileAccess.SaveFile(measurer.Measure());

który jest tak samo krótki i prawdopodobnie bardziej przejrzysty niż twoja metoda wygody przewidziana w pytaniu.

jpmc26
źródło
2

Klient konsumujący nie powinien mieć do czynienia z parą przedmiotów, gdy wystarczy jeden przedmiot. W twoim przypadku prawie tak nie jest, dopóki nie zostanie wywołane CreateDataFile.

Potencjalnym rozwiązaniem, które sugerujesz, jest utworzenie połączonego interfejsu pochodnego. Takie podejście wymaga jednak pojedynczego obiektu, który implementuje oba interfejsy, co jest raczej ograniczające, prawdopodobnie nieszczelna abstrakcja, ponieważ jest zasadniczo dostosowane do konkretnej implementacji. Zastanów się, jak skomplikowane byłoby, gdyby ktoś chciał zaimplementować dwa interfejsy w osobnych obiektach: musiałby proxy wszystkich metod w jednym z interfejsów, aby przekazać je do drugiego obiektu. (FWIW, inną opcją jest po prostu scalenie interfejsów zamiast wymagania, aby jeden obiekt musiał zaimplementować dwa interfejsy za pośrednictwem interfejsu pochodnego.)

Jednak inne podejście, które mniej ogranicza / dyktuje implementację, IFileAccessjest połączone z IMeasurerkompozycją, dzięki czemu jedno z nich jest powiązane z drugim i odnosi się do niego. (To nieco zwiększa abstrakcję jednego z nich, ponieważ teraz reprezentuje także parowanie.) Następnie CreateDataFilemożna wziąć tylko jeden z odnośników, powiedzmy IFileAccess, i nadal uzyskać drugi w razie potrzeby. Twoja aktualna implementacja jako obiekt, który implementuje oba interfejsy po prostu return this;na odniesienie kompozycji, tutaj getter dla IMeasurerw IFileAccess.

Jeśli w pewnym momencie programowania parowanie okaże się fałszywe, co oznacza, że ​​czasami używa się innego miernika z tym samym dostępem do pliku, możesz wykonać tę samą parowanie, ale na wyższym poziomie, co oznacza, że ​​wprowadzony dodatkowy interfejs nie może być interfejsem pochodnym, ale interfejsem, który ma dwa moduły pobierające, łączące dostęp do pliku i miernik razem za pomocą kompozycji, a nie pochodnej. Klient konsumujący ma wówczas do czynienia z jednym przedmiotem, o ile trwa parowanie, oraz indywidualnymi przedmiotami, z którymi należy sobie poradzić (w celu skomponowania nowych par), gdy jest to konieczne.


Z drugiej strony mogę zapytać, kto jest właścicielem CreateDataFile, a pytanie dotyczy tego, kim jest ta strona trzecia. Mamy już klienta, który się wywołuje CreateDataFile, obiekt / klasę będącą właścicielem CreateDataFileoraz IFileAccessi IMeasurer. Czasami, gdy przyjrzymy się szerszemu kontekstowi, mogą pojawić się alternatywne, czasem lepsze, organizacje. Trudno tu zrobić, ponieważ kontekst jest niekompletny, więc po prostu jedzenie do namysłu.

Erik Eidt
źródło
0

Niektórzy wychowali się, że CreateDataFilerobią za dużo. Mogę zasugerować, że zamiast tego Boardrobi za dużo, ponieważ dostęp do pliku wydaje się być czymś innym niż reszta forum.

Jeśli jednak założymy, że to nie pomyłka, większym problemem jest to, że interfejs powinien być zdefiniowany przez klienta, w tym przypadku CreateDataFile.

Interfejs Segregacja Zasada mówi, że klient nie powinien polegać na bardziej interfejs niż to, czego potrzebuje. Pożyczając zdanie z tej drugiej odpowiedzi , można to sparafrazować jako „interfejs jest zdefiniowany przez potrzeby klienta”.

Teraz możliwe jest skomponowanie interfejsu specyficznego dla klienta za pomocą IFileAccessi, IMeasurerjak sugerują inne odpowiedzi, ale ostatecznie klient ten powinien mieć interfejs specjalnie dla niego dostosowany.

Xtros
źródło
@Downvoter - co z tym jest niepoprawne lub może zostać poprawione?
Xtros