Menu aplikacji konsolowej jako singleton

0

Cześć, mam zamiar napisać prostą aplikacje konsolową jednak z zachowaniem "dobrych praktyk programowania". Moje pytanie dotyczy głównie klasy, która będzie pokazywała opcje do wyboru dla użytkownika(instrukcja switch, wykonywana w pętli do czasu aż użytkownik nie wybierze opcji zamknięcia programu). Miałbym parę serwisów w tej aplikacji i w zależności od wyboru użytkownika odpowiedni serwis wykonywałby jakąś metodę (np dodawanie obiektu do bazy). Nie za bardzo wiem jak mógłbym w przypadku aplikacji tego typu zastosować dependecy injection. Stąd też pojawiają się moje wątpliwości czy zrobienie z tej klasy singletona to dobry pomysł? Jeśli nie to byłbym wdzięczny za jakieś propozycje.

Ps.
Po to aby klasa menu nie była zależna bezpośrednio od klas serwisów, chciałem zrobić coś w tym stylu:

private ServiceInterface service;
....
case "2":
service = new SmsService();
service.metoda();
break;
case "3":
service = new MailService();
service.metoda();
...
0

Raczej singleton'y to słaby pomysł.

No ale tak, mniej więcej tak to powinno wyglądać. Szkoda że nie masz więcej kodu który mógłbyś pokazać.

2

Po to aby klasa menu nie była zależna bezpośrednio od klas serwisów, chciałem zrobić coś w tym stylu:

Podana przez Ciebie klasa w dalszym ciągu jest bezpośrednio zależna od przytoczonych serwisów - fakt, że tworzysz instancje na własną rękę nic tu nie zmienia :-)

Powinno się postawić w tym miejscu pytanie: czy zależności są złe?, prowadzące do mojej skromnej odpowiedzi: zależności nie tylko nie są złe - są naturalne!

Menu reprezentuje akcje, które mogą zostać wykonane przez użytkownika, więc siłą rzeczy musi być zależne od tych serwisów. Tworzenie setek warstw abstrakcji, unikanie DI itd. jedynie utrudnia analizę kodu, nie wnosząc przy tym żadnej faktycznej wartości.

Jeśli chcesz zrobić to porządnie i nie przesadzić z abstrakcjami, powinieneś IMO podejść w taki sposób:

// Interfejs opisujący kontrakt serwisu odpowiedzialnego za rozsyłanie e-maili
interface EmailSender {
  void sendEmail(...);
}

// Przykładowa implementacja rozsyłacza e-maili, zapisująca e-maile do logów
class DummyEmailSender implements EmailSender {
  /* ... */
}

// Przykładowa implementacja rozsyłacza e-maili, oparta o hipotetycznego faktycznego klienta SMTP
class SmtpEmailSender implements EmailSender {
  /* ... */
}

// Interfejs opisujący kontrakt serwisu odpowiedzialnego za rozsyłanie SMSów
interface SmsSender {
  void sendSms(...);
}

// Przykładowa implementacja rozsyłacza SMSów, zapisująca SMSy do logów
class DummySmsSender implements SmsSender {
  /* ... */
}

// Przykładowa implementacja rozsyłacza SMSów oparta o Twilio
class TwilioSmsSender implements SmsSender {
  /* ... */
}

class SomeMenu {
  private EmailSender emailSender; // <- opieramy się na interfejsach, dzięki czemu możemy tutaj podać zarówno faktyczną implementację, jak i naszą testową ("dummy")
  private SmsSender smsSender;

  public constructor(EmailSender emailSender, SmsSender smsSender) {
    /* ... */
  }

  /* ... */
}

Zauważ, że zależność pozostała (od interfejsu), lecz dostaliśmy w zamian dowolność w dobieraniu implementacji - możemy sobie zrobić new SomeMenu(new DummyEmailSender(), new DummySmsSender()) i boom, mamy menu, które działa tak jak prawdziwe, a jednocześnie nie rozsyła żadnych faktycznych maili oraz SMSów.

1

Rozwiązanie @Patryk27 ma to do siebie że instancjonujesz implementację nawet jak nie wiadomo jeszcze czy jej potrzeba - w rozwiązaniu pytającego new DobraImplementacja() jest dopiero wołana jak na prawdę potrzeba (wiem że jak się uprzesz to wystarczy factory).

0

Celem problemu nie było opracowanie leniwego instancjonowania, tylko wymyślenie, jak podejść do problemu w sensowny (z punktu widzenia architektury) sposób.

Z drugiej strony można zauważyć, że rozwiązanie OPa ma tę wadę, że tworzy nowy serwis za każdym wywołanym razem (zamiast wykorzystywać już istniejącą instancję, jeśli jest dostępna) - jest to tak drobny szczegół, który nie ma żadnego znaczenia w dzisiejszych czasach, że aż szkoda byłoby się w te fabryki bawić ;-)

0

Dzięki za odpowiedzi. Moim założeniem jest, że z klasy Menu wywoływana jest tylko jeden raz metoda np start() w klasie Main, która w pętli wyświetla opcje do wybory userowi. Nie wiem czy w takim przypadku Twoje rozwiązanie @Patryk27 można zastosować?

public Interface InterfaceService {
 void metoda();
}

public class JakisSerwis implements InterfaceService {
...
}

public class InnySerwis implements InterfaceService {
...
}
public class Menu{
private InterfaceService service;

public Menu(InterfaceService service){
this.service=service;
}
...
public void start(){

//wypisanie użytkownikowi opcji do wyboru

...
case"2":
new Menu(new JakisSerwis()).service.metoda();// ma to jakis sens?
break;
..
}
}
0

Ad 1: Po co Ci InterfaceService?
Ad 2: Tak, jak najbardziej. UI nie wpływa tu na nic.

0

W tym interfejsie będą metody CRUDowe, i interfejs ten parę różnych serwisów będzie implementować, a każdy z nich w inny sposób operuje na danych i inne rzeczy będą np wyświetlane.
Czyli mój kod jest poprawny?

0
Tgomek napisał(a):

Czyli mój kod jest poprawny?

Najpierw pokaż jakieś repozytorium z aplikacją

0

W tym interfejsie będą metody CRUDowe, i interfejs ten parę różnych serwisów będzie implementować, a każdy z nich w inny sposób operuje na danych i inne rzeczy będą np wyświetlane.

To nie jest odpowiedź na moje pytanie.

0

Nie wspomniałeś nic czy korzystasz z jakiegoś ORM'a czy coś, ale jeśli piszesz własną klasę do Sql'a, Dao etc to tu możesz zastosować singletona jeśli chcesz "przećwiczyć" tego typu rozwiązania.
Klasa komunikująca się z bazą może być singletonem, w klasach DAO bierzesz instancję i wykonujesz potrzebne zapytanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1