Code review: Inicjalizacja obiektu, wywoływanie metody wirtualnej w konstruktorze

Witam, co powiedziecie na taki kod?

class Employee
{
   public Employee()
   {
       Init();
   }
   public virtual void Init()
   {
   }
}
class Manager : Employee
{
   public Manager()
   {

   }
   public override void Init()
   {       
   }
}

Czy jest to dobry design? Jeśli ktoś programował w C++, na pewno nie zgodzi się na wywoływanie jakiejkolwiek metody wirtualnej w konstruktorze. W CPP zostałaby wywołana metoda Employee:Init zamiast Manager:Init ponieważ w momencie tworzenia Employee, obiekt Manager jeszcze nie istnieje i dlatego nie może zostać wywołana Manager:Init. Myślę, że jest to bardzo intuicyjne. Obiekty są tworzone od tych podstawowych do tych najbardziej zagnieżdżonych. W momencie tworzenia rodzica, nie mamy dostępu do child’a. Wywoływanie metody wirtualnej jest zatem mylące, ponieważ wykonany zostanie kod, którego się nie spodziewaliśmy (Init Manager’a nigdy nie zostanie wykonany).

A jak to wygląda w przypadku C#? Konstruktory są również wywoływane od rodzica (Employee) do dzieci (Manager). Jednak w przeciwieństwie do CPP, metody wirtualne wywoływane są zawsze dla obiektów dziedziczących – czyli w tym przypadku zostanie wywołana prawidłowa metoda Manager:Init (a nie jak w CPP, Employee:Init). Również, w przeciwieństwie do CPP, wszelkie pola inicjalizowane w momencie deklaracji, będą dostępne z prawidłowymi wartościami w Init.

Czy to znaczy, że w CPP jest to bad practice, a w c# dopuszczalny? Nie, w obu językach opisana sytuacja jest zła i prezentuje brzydki kod. Wyobraźmy sobie taką modyfikację:

class Employee
{
   public Employee()
   {
       Log();
   }

   public virtual void Log()
   {
   }
}
class Manager : Employee
{
   private string _data;
   public Manager(int id)
   {
       // zaladuj dane na podstawie ID
       _data = repository.Get(id);
   }
   public override void Log()
   {
       Console.Write(_data);
   }
}

Co jeśli dodamy np. parametr do konstruktora aby zainicjalizować parę pól, które następnie metoda wirtualna będzie używać? Log zostanie wykonany przed inicjalizacją obiektu (wywołaniem właściwego konstruktora). Konstruktor jest w końcu odpowiedzialny za inicjalizację stanu obiektu i wykonywanie jakichkolwiek operacji na obiekcie, przed wywołaniem konstruktora jest niebezpieczne i może spowodować późniejszą niestabilnością obiektu.

Leave a Reply

Your email address will not be published.