Jak stworzyć menu w konsoli

Koziołek

1 Wstęp
2 Podstawa to interfejs
3 Najprostsza usługa
4 Ładowanie usług
5 Aplikacja kontrolna
6 Podsumowanie

Wstęp

Często stajemy przed problemem podobnym do tego postawionego w tym temacie na forum. Wygodne w obsłudze od strony programu menu konsolowe bywa kluczowym elementem programu. W tym artykule postaram się przybliżyć jedną z metod tworzenia wygodnego menu.
Tekst oparty jest o rozwiązanie szerzej opisane w artykule Własne usługi w JSE. Zachęcam do przeczytania go jako dokładniejszego omówienia wykorzystywanych mechanizmów.

Podstawa to interfejs

Nadrzędnym celem jest pozbycie się konieczności zmieniania kodu programu przez dostawianie kolejnych instrukcji warunkowych wraz z rozszerzaniem programu o kolejne funkcjonalności. Pierwszym krokiem jest stworzenie wspólnego interfejsu dla wszystkich rozszerzeń. Nie musi być on skomplikowany. Wystarczy, że pozwala na identyfikację rozszerzenia i zawiera uniwersalną metodę, której zadaniem jest uruchomienie logiki modułu.

package pl.koziolekweb.programmers.consolemenu;

/**
 * Interfejs usługi konsolowej. Każda usługa, którą chcemy uruchomić z konsoli
 * musi go implementować.
 * 
 * @author koziołek
 * 
 */
public interface ConsoleService {

	/**
	 * Nazwa usługi. Będzie wyświetlana w konsoli.
	 * 
	 * @return Nazwa usługi.
	 */
	public String getName();

	/**
	 * Opis usługi. Zostanie wyświetlony po uruchomieniu usługi.
	 * 
	 * @return Opis usługi.
	 */
	public String getDescription();

	/**
	 * Metoda ta jest wywoływana w celu uruchomienia usługi. Nie zwraca żadnych
	 * rezultatów.
	 * 
	 * @throws Throwable
	 *             pozwala aplikacji na bezpieczne opuszczenie kodu usługi w
	 *             razie błędu.
	 */
	public void proceed() throws Throwable;
}

Ten interfejs będzie implementowany przez każdą usługę.

Najprostsza usługa

Najprostszą usługą jaka jest to usługa opuszczania programu. Zazwyczaj realizowana jest za pomocą instrukcji if, która sprawdza czy użytkownik wpisał odpowiednią literę.
Znacznie łatwiej jest jednak traktować opuszczanie programu jako nie różniącą się od innych usługę.

package pl.koziolekweb.programmers.consolemenu;

public class ExitService implements ConsoleService{

	@Override
	public String getDescription() {
		return "Wyjście";
	}

	@Override
	public String getName() {
		return "Wyjście";
	}

	@Override
	public void proceed() throws Throwable {
		System.exit(0);
	}

}

Stwórzmy jeszcze jedną usługę kontrolną:

package pl.koziolekweb.programmers.consolemenu;

public class SimpleConsoleService implements ConsoleService{

	@Override
	public String getDescription() {
		return "Prosty serwis";
	}

	@Override
	public String getName() {
		return "Prosty serwis";
	}

	@Override
	public void proceed() throws Throwable {
		System.out.println("Prosty serwis");
	}
}

Ładowanie usług

W tym celu użyta zostanie zmodyfikowana klasa MyServiceLoader z artykułu Własne usługi w JSE.

package pl.koziolekweb.programmers.consolemenu;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

/**
 * Klasa narzędziowa zarządzająca serwisami. Pozwala na ich ładowanie i
 * wyszukiwanie. Stanowi punkt dostępowy do serwisów dla aplikacji klienckiej.
 * Ładowanie jest procesem zachłannym i następuje przy uruchomieniu aplikacji.
 *
 * @author koziołek
 *
 */
public class ConsoleServiceLoader {

	private static final ServiceLoader<ConsoleService> SERVICE_LOADER = ServiceLoader
			.load(ConsoleService.class);

	private static Map<String, ConsoleService> myServices = new HashMap<String, ConsoleService>();

	static {
		loadServices();
	}

	/**
	 * Wyszukuje serwis na podstawie nazwy. Zwraca
	 * {@link IllegalArgumentException} w przypadku gdy serwis o podanej nazwie
	 * nie istnieje.
	 * 
	 * @param serviceName
	 *            nazwa serwisu.
	 * @return poszukiwany serwis.
	 * @throws IllegalArgumentException
	 *             wyrzucany jeżeli serwis o podanej nazwie nie istnieje.
	 */
	public static ConsoleService getServiceByName(String serviceName)
			throws IllegalArgumentException {
		if (myServices.containsKey(serviceName)) {
			return myServices.get(serviceName);
		}
		throw new IllegalArgumentException("Brak serwisu o nazwie: "
				+ serviceName);
	}

	/**
	 * Pozwala sprawdzić czy serwis o podanej nazwie istnieje.
	 * 
	 * @param serviceName
	 *            nazwa poszukiwanego serwisu.
	 * @return <code&gt;true</code&gt; jeżeli serwis istnieje.
	 */
	public Boolean serviceExist(String serviceName) {
		return myServices.containsKey(serviceName);
	}

	/**
	 * Zwraca listę nazw zarejestrowanych serwisów.
	 * 
	 * @return lista nazw serwisów.
	 */
	public static Set<String> getServicesNames() {
		return myServices.keySet();
	}

	/**
	 * Zwraca mapę serwisów.
	 * 
	 * @return
	 */
	public static Map<String, ConsoleService> getAllServices() {
		return myServices;
	}
	
	/**
	 * Zwraca kolekcję serwisów.
	 * 
	 * @return
	 */
	public static Collection<ConsoleService> getAll(){
		return myServices.values();
	}

	/**
	 * Ładuje serwisy. Wywoływana przy ładowaniu klasy.
	 */
	private static void loadServices() {
		for (ConsoleService myService : SERVICE_LOADER) {
			myServices.put(myService.getName(), myService);
		}
	}
}

Aplikacja kontrolna

Jest to bardzo uproszczona aplikacja, która przedstawia ideę użycia tego rozwiązania. W rzeczywistym rozwiązaniu należało by zagwarantować, że serwis wychodzący będzie miał przypisaną literę oraz, że każdorazowe uruchomienie aplikacji będzie wyświetlało dostępne opcje w takiej samej kolejności.

package pl.koziolekweb.programmers.consolemenu;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * Hello world!
 * 
 */
public class App {
	public static void main(String[] args) {
		Collection<ConsoleService> allServices = ConsoleServiceLoader.getAll();
		Map<String, String> servicesList = new HashMap<String, String>(
				allServices.size());
		int i = 1;
		for (ConsoleService service : allServices) {
			servicesList.put(i+"", service.getName());
			i++;
		}
		Scanner scanner = new Scanner(System.in);
		while (true) {
			System.out.println("Wybierz operację.");
			
			System.out.println(servicesList);
			
			String command = scanner.nextLine();
			
			try {
				ConsoleServiceLoader.getServiceByName(
						servicesList.get(command.toUpperCase()).toString()).proceed();
			} catch (Throwable e) {
				System.out.println("Sie wywaliło");
				e.printStackTrace();
				continue;
			}
		}
	}
}

Podsumowanie

Rozwiązanie to jest jednym z kilku, które pozwalają na łatwe uniknięcie problemu nadmiernie skomplikowanych instrukcji warunkowych. Przedstawiłem tylko pewne założenia, a zadaniem czytelnika jest dostosowanie tego rozwiązania do własnych potrzeb.

0 komentarzy