Tworzę aplikację, która będzie używana do otwierania i zamykania zaworów w środowisku przemysłowym, i myślałam o czymś prostym:
public static void ValveController
{
public static void OpenValve(string valveName)
{
// Implementation to open the valve
}
public static void CloseValve(string valveName)
{
// Implementation to close the valve
}
}
(Implementacja zapisuje kilka bajtów danych do portu szeregowego w celu sterowania zaworem - „adres” pochodzący od nazwy zaworu oraz „1” lub „0” w celu otwarcia lub zamknięcia zaworu).
Inny twórca zapytał, czy zamiast tego powinniśmy utworzyć osobną klasę dla każdej zastawki fizycznej, której jest kilkadziesiąt. Zgadzam się, że będzie ładniej kodu pisać jak PlasmaValve.Open()
zamiast ValveController.OpenValve("plasma")
, ale jest to przesada?
Zastanawiałem się również, jak najlepiej poradzić sobie z tym projektem, mając na uwadze kilka hipotetycznych przyszłych wymagań:
- Jesteśmy proszeni o wsparcie nowego typu zaworu wymagającego różnych wartości, aby go otworzyć i zamknąć (nie 0 i 1).
- Jesteśmy proszeni o wsparcie zaworu, który można ustawić w dowolnej pozycji od 0-100, zamiast po prostu „otwarte” lub „zamknięte”.
Zwykle do tego rodzaju rzeczy używałbym dziedziczenia, ale ostatnio zacząłem się zastanawiać nad „kompozycją nad dziedziczeniem” i zastanawiam się, czy istnieje lepsze rozwiązanie przy użyciu kompozycji?
źródło
Odpowiedzi:
Jeśli każda instancja obiektu zaworu będzie uruchamiała ten sam kod, co ten ValveController, wydaje się, że wiele instancji jednej klasy byłoby właściwą drogą. W takim przypadku po prostu skonfiguruj, który zawór kontroluje (i jak) w konstruktorze obiektu zaworu.
Jeśli jednak każde sterowanie zaworem potrzebuje innego kodu do uruchomienia, a bieżący ValveController uruchamia gigantyczną instrukcję przełączającą, która robi różne rzeczy w zależności od typu zaworu, oznacza to, że źle zaimplementowano polimorfizm. W takim przypadku przepisz go do wielu klas ze wspólną bazą (jeśli ma to sens) i pozwól, aby zasada pojedynczej odpowiedzialności była Twoim przewodnikiem projektowym.
źródło
Moim głównym problemem jest używanie ciągów jako parametru identyfikującego zawór.
Przynajmniej stwórz
Valve
klasę, która magetAddress
formę wymagającą implementacji i przekaż je doValveController
i upewnij się, że nie możesz stworzyć nieistniejących zaworów. W ten sposób nie będziesz musiał obsługiwać niewłaściwych ciągów w każdej z metod otwierania i zamykania.To, czy stworzysz dogodne metody, które będą wywoływać otwieranie i zamykanie,
ValveController
zależy od ciebie, ale szczerze mówiąc, zachowałbym całą komunikację z portem szeregowym (łącznie z kodowaniem) w jednej klasie, którą inne klasy będą w razie potrzeby wywoływać. Oznacza to, że gdy trzeba przeprowadzić migrację do nowego kontrolera, wystarczy zmodyfikować tylko jedną klasę.Jeśli podoba ci się testowanie, powinieneś zrobić
ValveController
singleton, aby go wyśmiewać (lub stworzyć maszynę szkoleniową dla operatorów).źródło