Nadal uważam się za programistę-ucznia, więc zawsze szukam „lepszego” sposobu na typowe programowanie. Dzisiaj mój współpracownik argumentował, że mój styl kodowania robi niepotrzebną pracę i chcę usłyszeć opinie innych. Zazwyczaj, gdy projektuję klasę w języku OOP (zwykle C ++ lub Python), dzieliłem inicjalizację na dwie różne części:
class MyClass1 {
public:
Myclass1(type1 arg1, type2 arg2, type3 arg3);
initMyClass1();
private:
type1 param1;
type2 param2;
type3 param3;
type4 anotherParam1;
};
// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
: param1(arg1)
, param2(arg2)
, param3(arg3)
{}
// Any other procedure is done in a separate initialization function
MyClass1::initMyClass1() {
// Validate input arguments before calculations
if (checkInputs()) {
// Do some calculations here to figure out the value of anotherParam1
anotherParam1 = someCalculation();
} else {
printf("Something went wrong!\n");
ASSERT(FALSE)
}
}
(lub odpowiednik w języku Python)
class MyClass1:
def __init__(self, arg1, arg2, arg3):
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = arg3
#optional
self.anotherParam1 = None
def initMyClass1():
if checkInputs():
anotherParam1 = someCalculation()
else:
raise "Something went wrong!"
Jakie jest twoje zdanie na temat tego podejścia? Czy powinienem powstrzymać się od podziału procesu inicjalizacji? Pytanie nie ogranicza się tylko do C ++ i Pythona, a odpowiedzi na inne języki są również mile widziane.
design
object-oriented
coding-style
Caladbolgll
źródło
źródło
Odpowiedzi:
Chociaż czasami jest to problematyczne, istnieje wiele zalet inicjowania wszystkiego w konstruktorze:
initMyClass1()
ponieważ go nie ma . „Najtańsze, najszybsze i najbardziej niezawodne komponenty to te, których nie ma”.źródło
Pomyśl o abstrakcji, którą udostępniasz swoim użytkownikom.
Po co dzielić coś, co można zrobić jednym strzałem na dwa?
Dodatkowa inicjalizacja to coś dodatkowego dla programistów używających interfejsu API do zapamiętania i zapewnia więcej błędów, jeśli nie zrobią tego dobrze, ale jaką wartość mają dla nich to dodatkowe obciążenie?
Chcesz zapewnić śmiertelnie proste, łatwe w użyciu, trudne do popełnienia błędne abstrakcje. Programowanie jest wystarczająco trudne bez zbędnych rzeczy do zapamiętania / obręczy do przeskoczenia. Chcesz, aby użytkownicy interfejsu API (nawet jeśli używasz własnego interfejsu API) wpadli w pułapkę sukcesu .
źródło
Zainicjuj wszystko oprócz obszaru dużych zbiorów danych. Narzędzia analizy statycznej oznaczą pola niezainicjowane w konstruktorze. Jednak najbardziej produktywnym / bezpiecznym sposobem jest posiadanie wszystkich zmiennych składowych z domyślnymi konstruktorami i jawne inicjowanie tylko tych, które wymagają inicjalizacji innej niż domyślna.
źródło
Są przypadki, w których obiekt ma wiele inicjalizacji, które można podzielić na dwie kategorie:
Atrybuty, które są niezmienne lub nie wymagają resetowania.
Atrybuty, które mogą wymagać przywrócenia oryginalnych wartości (lub wartości szablonowych) na podstawie pewnych warunków po spełnieniu swojego zadania, rodzaj miękkiego resetu. np. połączenia w puli połączeń.
Tutaj drugą część inicjalizacji utrzymywaną w osobnej funkcji, powiedzmy InitialiseObject (), można wywołać w ctor.
Tę samą funkcję można wywołać później, jeśli wymagany jest miękki reset, bez konieczności odrzucania i ponownego tworzenia obiektu.
źródło
Jak powiedzieli inni, ogólnie dobrym pomysłem jest zainicjowanie w konstruktorze.
Istnieją jednak powody, dla których może to nie mieć zastosowania w określonych przypadkach.
Obsługa błędów
W wielu językach jedynym sposobem sygnalizowania błędu w konstruktorze jest zgłoszenie wyjątku.
Jeśli Twoja inicjalizacja ma uzasadnioną szansę na zgłoszenie błędu, np. Dotyczy IO lub jego parametry mogą być wprowadzane przez użytkownika, jedynym dostępnym mechanizmem jest zgłoszenie wyjątku. W niektórych przypadkach może nie być to pożądane i bardziej sensowne może być rozdzielenie kodu podatnego na błędy na osobną funkcję inicjalizacji.
Prawdopodobnie najczęstszym tego przykładem jest C ++, jeśli standardem projektu / organizacji jest wyłączenie wyjątków.
Maszyna stanowa
Jest tak w przypadku modelowania obiektu, który ma jawne przejścia stanu. Na przykład plik lub gniazdo, które można otworzyć i zamknąć.
W takim przypadku konstrukcja (i usuwanie) obiektów często zajmuje się tylko atrybutami zorientowanymi na pamięć (nazwa pliku, port itp.). Będą wtedy funkcje do specyficznego zarządzania zmianami stanu, np. Otwieranie, zamykanie, które są skutecznie funkcjami inicjalizacji i rozrywania.
Zaletą jest obsługa błędów, jak wyżej, ale może też istnieć potrzeba oddzielenia konstrukcji od inicjalizacji (powiedzmy, że budujesz wektor plików i otwierasz je asynchronicznie).
Wadą, jak powiedzieli inni, jest to, że obciążasz teraz zarządzanie stanem użytkownikiem twoich klas. Jeśli potrafisz poradzić sobie z samą konstrukcją, możesz, powiedzmy, użyć RAII, aby zrobić to automatycznie.
źródło