Powiedzmy, że tworzymy parser. Jednym z wdrożeń może być:
public sealed class Parser1
{
public string Parse(string text)
{
...
}
}
Lub możemy zamiast tego przekazać tekst do konstruktora:
public sealed class Parser2
{
public Parser2(string text)
{
this.text = text;
}
public string Parse()
{
...
}
}
Użycie jest proste w obu przypadkach, ale co to znaczy umożliwić wprowadzanie parametrów w Parser1
porównaniu do innych? Jaką wiadomość wysłałem do innego programisty, gdy spojrzał na interfejs API? Czy w niektórych przypadkach są jakieś zalety / wady techniczne?
Pojawia się kolejne pytanie, kiedy zdaję sobie sprawę, że interfejs byłby zupełnie bez znaczenia w drugiej implementacji:
public interface IParser
{
string Parse();
}
... gdzie interfejs na pierwszym może służyć przynajmniej celowi. Czy to w szczególności oznacza, że klasa jest „możliwa do zinterpretowania”, czy nie?
object-oriented
interfaces
methods
construction
Ciscoheat
źródło
źródło
Odpowiedzi:
Semantycznie rzecz biorąc, w OOP powinieneś przekazać konstruktorowi tylko zestaw parametrów, które są potrzebne do zbudowania klasy - podobnie, gdy wywołujesz metodę, powinieneś przekazywać tylko parametry potrzebne do wykonania jej logiki biznesowej.
Parametry, które należy przekazać w konstruktorze, są parametrami, które nie mają sensownej wartości domyślnej, a jeśli twoja klasa jest niezmienna (a nawet a
struct
), wówczas należy przekazać właściwości inne niż domyślne.Jeśli chodzi o twój dwa przykład:
text
konstruktora sugeruje, żeParser2
klasa zostanie zbudowana specjalnie w celu późniejszego przeanalizowania tego wystąpienia tekstu. Będzie to konkretny parser. Zwykle dzieje się tak, gdy budowanie klasy jest bardzo drogie lub subtelne, RegEx może zostać skompilowany w konstruktorze, więc gdy już utrzymasz instancję, możesz ponownie jej użyć bez ponoszenia kosztów kompilacji; innym przykładem jest inicjowanie PRNG - lepiej, jeśli robi się to rzadko.text
do metody, oznacza to, żeParser1
może ona zostać ponownie użyta do parsowania różnych tekstów przez wywołania.źródło
Przypomnijmy, co to znaczy przekazać zmienną jako parametr konstruktora: Inicjujesz obiekt, aby użyć jego zmiennych instancji w metodach obiektu. Chodzi o to, że prawdopodobnie chcesz użyć go w więcej niż jednej metodzie, ponieważ chcesz mieć wysoką spójność w swojej klasie.
Przekazywanie parametru bezpośrednio do metody oznacza wysłanie wiadomości do obiektu i prawdopodobnie otrzymanie odpowiedzi. W ten sposób klient chce, aby obiekt świadczył dla niego usługę.
Podsumowując, są to dwa bardzo różne sposoby przekazywania parametrów i powinieneś wybrać, czy Twój obiekt ma dostarczać usługę, czy też zapewniać funkcjonalność podczas zarządzania wewnętrznymi informacjami.
źródło
Jest to fundamentalna zmiana projektu. A projekt powinien przekazywać intencje i znaczenie. Czy potrzebujesz osobnych obiektów dla każdego łańcucha, który chcesz przeanalizować? Innymi słowy, dlaczego potrzebujemy wystąpienia parsera z stringX i innego wystąpienia z stringY? Co takiego jest w analizie składni i danym łańcuchu, że oboje muszą żyć i umrzeć razem? Zakładając, że „podstawowa implementacja [parsowania]” (jak mówi Robert Harvey) nie zmienia się, wydaje się, że nie ma sensu. I nawet wtedy jego wątpliwe IMHO.
Parametry konstruktora mówią mi, że te rzeczy są wymagane dla obiektu. Bez nich nie można zagwarantować właściwego stanu. Wiem także, dlaczego / dlaczego jeden parser zasadniczo różni się od drugiego.
Parametry konstruktora uniemożliwiają mi zbyt dużą wiedzę na temat korzystania z klasy. Jeśli zamiast tego mam ustawić określone właściwości - skąd mam to wiedzieć? Otwiera się cała puszka robaków. Jakie właściwości W jakiej kolejności? Przed użyciem jakich metod? i tak dalej.
Interfejs, podobnie jak w API, to metody i właściwości narażone na kod klienta. Nie daj się owinąć
public interface { ... }
wyłącznie. Zatem znaczenie interfejsu wynika z dylematu parametru konstruktor vs konstruktor vs metoda, NIEpublic interface Iparser
vspublic sealed class Parser
sealed
Klasa jest nieparzysta. Jeśli myślę o różnych implementacjach parsera - wspomniałeś o „Iparserze” - to moją pierwszą myślą jest dziedziczenie. To tylko naturalne przedłużenie mojego myślenia. IE wszystkieParserX
są zasadniczoParser
s. Jak inaczej to powiedzieć? ... Owczarek niemiecki jest psem (dziedziczenie), ale mogę wyszkolić papugę do szczekania (zachowywać się jak pies - „interfejs”); ale Polly nie jest psem, udaje tylko, że nauczył się podzbioru psości. Klasy, abstrakcyjne lub inne, doskonale służą jako interfejsy .źródło
Druga wersja klasy może być niezmienna.
Interfejs może być nadal używany, aby zapewnić możliwość wymiany podstawowej implementacji.
źródło
Parser1
Budowanie przy użyciu domyślnego konstruktora i przekazywanie tekstu wejściowego do metody oznacza, że Parser1 może być ponownie użyty.
Parser2
Przekazywanie tekstu wejściowego do konstruktora oznacza, że dla każdego łańcucha wejściowego należy utworzyć nowy Parser2.
źródło