Jak pisać konstruktory, które nie mogą poprawnie utworzyć instancji obiektu

12

Czasami musisz napisać konstruktor, który może zawieść. Powiedzmy na przykład, że chcę utworzyć instancję obiektu ze ścieżką do pliku, coś w rodzaju

obj = new Object("/home/user/foo_file")

Dopóki ścieżka wskazuje odpowiedni plik, wszystko jest w porządku. Ale jeśli łańcuch nie jest prawidłową ścieżką, rzeczy powinny się zepsuć. Ale jak?

Mógłbyś:

  1. rzuć wyjątek
  2. zwróć obiekt zerowy (jeśli twój język programowania pozwala konstruktorom zwracać wartości)
  3. zwraca poprawny obiekt, ale z flagą wskazującą, że jego ścieżka nie została ustawiona poprawnie (ugh)
  4. inni?

Zakładam, że „najlepsze praktyki” różnych języków programowania wdrażałyby to inaczej. Na przykład myślę, że ObjC woli (2). Ale (2) byłoby niemożliwe do zaimplementowania w C ++, gdzie konstruktory muszą mieć void jako typ zwracany. W takim przypadku przyjmuję, że użyto (1).

Czy w wybranym języku programowania możesz pokazać, jak poradzisz sobie z tym problemem i wyjaśnić, dlaczego?

garażtrois
źródło
1
Wyjątki w Javie są jednym ze sposobów radzenia sobie z tym.
Mahmoud Hossam,
Konstruktory C ++ nie zwracają void- zwracają obiekt.
gablin
6
@gablin: Właściwie to też nie jest do końca prawda. newwywołuje operator newalokację pamięci, a następnie konstruktor, aby ją wypełnić. Konstruktor nic nie zwraca i newzwraca wskaźnik, który otrzymał operator new. voidJednak to, czy „nic nie zwraca” oznacza, że ​​„zwraca ”, jest do zdobycia.
Jon Purdy,
@Jon Purdy: Hm, brzmi rozsądnie. Dobra decyzja.
gablin

Odpowiedzi:

8

Nigdy nie warto polegać na konstruktorze, który wykonuje brudną robotę. Poza tym dla innego programisty nie jest jasne, czy w konstruktorze będzie wykonywana praca, chyba że istnieje wyraźna dokumentacja, że ​​tak jest (i że użytkownik klasy ją przeczytał lub o tym powiedział).

Na przykład (w języku C #):

public Sprite
{
    public Sprite(string filename)
    {
    }
}

Co się stanie, jeśli użytkownik nie chce od razu załadować pliku? Co jeśli chcą wykonać buforowanie pliku na żądanie? Nie mogą. Możesz pomyśleć o wstawieniu bool loadFileargumentu do konstruktora, ale powoduje to bałagan, ponieważ nadal potrzebujesz Load()metody wczytywania pliku.

Biorąc pod uwagę obecny scenariusz, użytkownicy klasy będą bardziej elastyczni i jaśniej:

Sprite sprite = new Sprite();
sprite.Load("mario.png");

Lub alternatywnie (dla czegoś takiego jak zasób):

Sprite sprite = new Sprite();
sprite.Source = "mario.png";
sprite.Cache();

// On demand caching of file contents.
public void Cache()
{
     if (image == null)
     {
         try
         {
             image = new Image.FromFile(Source);
         }
         catch(...)
         {
         }
     }
}
Nick Bedford
źródło
w twoim przypadku, jeśli ładowanie (...) nie powiedzie się, mamy całkowicie bezużyteczny obiekt. Nie jestem pewien, czy chcę duszka bez duszka ... Ale to pytanie bardziej przypomina temat holywar niż prawdziwe pytanie :)
avtomaton
7

W Javie możesz użyć wyjątków lub wzorca fabrycznego, który pozwoli ci zwrócić wartość null.

W Scali możesz zwrócić opcję [Foo] z metody fabrycznej. To też działałoby w Javie, ale byłoby bardziej kłopotliwe.

Kim
źródło
Używanie wyjątków lub fabrycznie także w językach .NET.
Beth Whitezel,
3

Rzuć wyjątek.

Należy sprawdzić wartość Null, jeśli możesz ją zwrócić (i nie będzie zaznaczona)

Do tego służą sprawdzone wyjątki. Wiesz, że to może zawieść. Dzwoniący muszą sobie z tym poradzić.

Tim Williscroft
źródło
3

W C ++ konstruktory są używane do tworzenia / inicjowania członków klasy.

Nie ma właściwej odpowiedzi na to pytanie. Ale do tej pory obserwowałem, że w większości przypadków klient (lub ktokolwiek będzie korzystał z twojego API) wybiera sposób, w jaki powinieneś sobie z tym poradzić.

Czasami proszą przeznaczyć wszystkie zasoby, które mogą potrzebować obiekt na konstruktora i rzucić wyjątek, jeśli coś się nie powiedzie (przerywanie tworzenie obiektu), czy nic z tego na konstruktora, i upewnij się, że kreacja będzie zawsze uda , pozostawiając te zadania do wykonania przez funkcję członka.

Jeśli to Ty wybierasz zachowanie, wyjątki są domyślnym sposobem obsługi błędów w C ++ i powinieneś ich używać, kiedy tylko możesz.

karlphillip
źródło
1

Możesz także utworzyć obiekt bez parametrów lub tylko z parametrami, które z pewnością nigdy nie zawiodą, a następnie użyjesz funkcji lub metody inicjalizacji, dzięki której możesz bezpiecznie zgłosić wyjątek lub zrobić wszystko, co chcesz.

gablin
źródło
6
Myślę, że najgorszym wyborem jest zwrócenie nieużywalnego obiektu, który wymaga dalszej inicjalizacji. Teraz masz fragment składający się z wielu linii, który należy skopiować za każdym razem, gdy klasa jest używana, lub uwzględniony w nowej metodzie. Równie dobrze możesz zlecić konstruktorowi wykonanie całej pracy i zgłosić wyjątek, jeśli obiekt nie może zostać zbudowany.
kevin cline
2
@kevin: Zależy od obiektu. Awarie będą prawdopodobnie spowodowane zasobami zewnętrznymi, więc jednostka może chcieć zarejestrować zamiar (np. Nazwa pliku), ale zezwalać na wielokrotne próby pełnej inicjalizacji. W przypadku obiektu wartości prawdopodobnie skłaniam się do zgłoszenia błędu, który może uchwycić błąd podstawowy, więc prawdopodobnie zgłasza wyjątek (zawinięty?).
Dave
-4

Wolę nie inicjować żadnej klasy ani listy w konstruktorze. Zainicjuj klasę lub listę w dowolnym momencie. Na przykład.

public class Test
{
    List<Employee> test = null;
}

Nie rób tego

public class Test
{
    List<Employee> test = null;

    public Test()
    {
        test = new List<Employee>();
    }
}

Zamiast tego zainicjuj, gdy jest to wymagane Np.

public class Test
{
    List<Employee> emp = null;

    public Test()
    { 
    }

    public void SomeFunction()
    {
         emp = new List<Employee>();
    }
}
test
źródło
1
To kiepska rada. Nie należy zezwalać, aby obiekty były w nieprawidłowym stanie. Po to jest konstruktor. Jeśli potrzebujesz złożonej logiki, użyj wzorca fabrycznego.
AlexFoxGill
1
Konstruktory służą do ustalenia niezmienników klas, przykładowy kod wcale nie pomaga w stwierdzeniu tego.
Niall