4 minutes
Prywatny konstruktor – kiedy może się przydać?
W tym wpisie opowiem Ci jak użyć konstruktor prywatny do stworzenia obiektu z od razu prawidłowymi danymi, bez potrzeby robienia walidacji parametrów.
Gra karciania “GameOver”
W domu gramy trochę w gry planszowe i karciane. W ramach zabawy i poznania frameworka Blazor postanowiłem zaprogramować grę “GameOver”. Jest to gra karciana wydana przez wydawnictwo Nasza księgarnia.
Więcej o grze przeczytasz tutaj: https://nk.com.pl/game-over/2539/gra.html#.XzEWPogzZhE
Gra jest już prawie na ukończeniu. Został do poprawy bug podczas napotkania smoka + drobne poprawki w interfejsie. Jak skończę ją pisać to opublikuję informację o tym.
Obiekt Card
W swoim kodzie posiadam klasę Card
, która odpowiada za pojedynczą kartę do gry. Taka karta może być różnego typu:
Klucz Skrzynia ze skarbem danego gracza Wróg z którym się walczy Drzwi Smok – też to jest wróg, ale jego nie da się pokonać, dlatego dałem go jako oddzielny typ. Dodatkowo jeśli karta jest skrzynią, musimy określić, którego gracza to skrzynia. A jeśli karta to wróg, to musimy wiedzieć jaką ma broń. W każdym innym przypadku ważny jest tylko typ. Na początku klasę zaprojektowałem tak:
public class Card
{
public CardType Type { get; }
public PlayerType? ChestOwner { get; }
public Weapon? Weapon { get; }
public Card(CardType type)
{
Type = type;
}
public Card(CardType type, Weapon weapon)
{
Type = type;
Weapon = weapon;
}
public Card(CardType type, PlayerType chestOwner)
{
Type = type;
ChestOwner = chestOwner;
}
}
CardType
, PlayerType
oraz Weapon
to enumy.
Klasa zawiera 3 konstruktory publiczne, które mają różne przeznaczenia. Pierwszy służy do stworzenia kart typu: smok, drzwi i klucz. Drugi do stworzenia karty wroga, a trzeci skrzyni gracza.
Czy używając tych konstruktorów mógłbym stworzyć kartę skrzyni wraz z jakąś bronią? Oczywiście, że tak, jeśli użyłbym drugiego konstruktora.
Można się przed tym zabezpieczyć sprawdzają jakie dane są przekazywane do konstruktora i np. wyrzucić wyjątek. Jednak jest lepsze rozwiązanie.
Publiczne statyczne metody
Zamiast tego można zrobić odpowiednie metody statyczne tworzące obiekt. W tych metodach przekazujemy tylko te dane, które są niezbędne. Reszta jest ustawiania wewnątrz metody, dzięki temu nie można już pomylić parametrów. Moje metody tworzące wyglądają tak:
public static Card CreateCard(CardType type)
{
return new Card(type);
}
public static Card CreateChestCard(PlayerType owner)
{
return new Card(CardType.Chest, owner);
}
public static Card CreateEnemyCard(Weapon Weapon)
{
return new Card(CardType.Enemy, Weapon);
}
Pierwszą zaletą tego rozwiązania jest to, że nie stworzymy już obiektu ze złymi właściwościami.
- W metodzie
CreateChestCard
przekazujemy tylko, który gracz będzie właścicielem skrzyni. Typ kartyChest
znajduje się wewnątrz metody. - W metodzie
CreateEnemyCard
przekazujemy tylko typ broni. Typ kartyEnemy
znajduje się wewnątrz metody.
Drugą zaletą jest to, że mamy jasno nazwane metody, widać od razu co robię. Nie musimy przeglądać parametrów konstruktora i czytać komentarzy, aby dowiedzieć się co on robi. Zwiększa się również czytelność kodu.
Konstruktor prywatny
I tutaj właśnie wkraczają całe na biało konstruktory prywatne. Bo żeby stworzyć obiekt, trzeba użyć konstruktora, tak jak zrobiłem w przykładzie wyżej. Ale jeśli zostawimy je publiczne, to ktoś może z nich skorzystać i zrobić obiekt ze złymi parametrami. Po refaktoringu klasa Card
wygląda następująco:
public class Card
{
public CardType Type { get; }
public PlayerType? ChestOwner { get; }
public Weapon? Weapon { get; }
private Card(CardType type)
{
Type = type;
}
private Card(CardType type, Weapon weapon)
{
Type = type;
Weapon = weapon;
}
private Card(CardType type, PlayerType chestOwner)
{
Type = type;
ChestOwner = chestOwner;
}
public static Card CreateCard(CardType type)
{
return new Card(type);
}
public static Card CreateChestCard(PlayerType owner)
{
return new Card(CardType.Chest, owner);
}
public static Card CreateEnemyCard(Weapon Weapon)
{
return new Card(CardType.Enemy, Weapon);
}
}
Klasa zawiera teraz po 3:
- właściwości,
- prywatne konstruktory,
- publiczne statyczne metody, które służą do stworzenia obiektów.
Oczywiście tej gry już nikt nie będzie zmieniał, mógłbym zostawić klasę Card
w takiej postaci jak była na początku wpisu. Ale stwierdziłem, że to akurat jest idealne zastosowanie metod tworzących i dobry temat na wpis, więc postanowiłem zrefaktorować kod :-)
Podsumowanie
W ten oto łatwy sposób, używając metod tworzących i konstruktorów prywatnych, można zabezpieczyć tworzenie obiektów. Od teraz już nikt (no chyba, że ktoś zmieni kod :-) ) nie stworzy nieprawidłowych obiektów kart.