Aplikacja paragonu w Javie

0

Cześć!
Miło mi zagościć na Waszym forum. Jestem budowlańcem pracującym w zawodzie, jednak z różnych przyczyn uczę się programowania. Padło na Javę. Nie wiem czy dobrze czy źle, jakoś sobie poradzę.
Napisałem prostą aplikację na ArrayList umożliwiającą dodawanie produktów, wpisywanie ceny jednostkowej, ilości i w efekcie otrzymywanie wartości do zapłaty za każdy produkt. Aplikacja potrafi też wyznaczyć wartości netto/brutto/vat. Można dodać rabat w procentach, a także bon obniżający o dowolną kwotę.

Chciałbym dodać jeszcze możliwość usuwania odpowiedniego produktu, ale to nie o tym. Chciałbym tą aplikację ulepszyć, uporządkować w taki sposób, żeby można było ją ewentualnie zamieścić w CV. Wiem, że powinienem dodać także funkcje uniemożliwiające wpisanie złych wartości (np. liczb w nazwę produktu, słów w cenę) by uniknąć wywalania aplikacji.
Problem w tym, że nie wiem jak się do tego zabrać. Czy są do tego jakieś wytyczne? Nie można chyba po prostu napisać wszystkiego na zasadzie "if zły_input wpisz_poprawnie". Chyba, że można?

Aplikacja jest zrobiona na listach. Pytanie brzmi, czy jest to dobre wyjście? Próbowałem wcześniej napisać podobny program, który w liście przechowywał obiekt, na który składało się kilka parametrów (np. produkt, cena, ilość). Ale przyznam szczerze, że potraciłem się gdy miałem się odwoływać do pól obiektu, który tak na prawdę jest jedną pozycją na liście (nie do końca mój umysł to ogarnął). Może jednak warto przerobić tą aplikację na klasy? Wklejam poniżej kod i czekam na uwagi. Pamiętajcie, że nie jestem programistą, nie studiowałem informatyki i mogę nie zrozumieć skrótów myślowych. Ale spróbuję :)

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        List<Product> product = new ArrayList<>();
        Scanner sc = new Scanner(System.in).useLocale(Locale.GERMAN);
        double totalPrice = 0;
        System.out.println("Witaj w programie Paragon v1.0. Autor: Zaklinacz_kabli");
        String login = getLogin(sc);

        while (true)
        {
            printOptions();
            String option = sc.nextLine();
            switch (option)
            {
                case "1":
                    addProducts(product, sc);
                    totalPrice = priceToPay(product, totalPrice);
                    System.out.println("Kwota do zapłaty: " + totalPrice);
                    break;
                case "2":
                    totalPrice = discount(sc, totalPrice);
                    break;
                case "3":
                    totalPrice = coupon(sc, totalPrice);
                    break;
                case "4":
                    netVatGross(totalPrice);
                    break;
                case "5":
                    printRecipe(product, totalPrice, login);
                    break;
                case "6":
                    removeProduct(product, sc);
                    break;
                case "0":
                    exitApp();
                    break;
                default:
                    System.out.println("Nie ma takiej opcji, wprowadź ponownie: ");
                    break;
            }
        }
    }

    //Metoda removeProduct służąca do usuwania produktów na podstawie indeksów przypisywanych w momencie dodawania produktów
    private static void removeProduct(List<Product> product, Scanner sc)
    {
        System.out.println("Wybierz, który produkt chcesz usunąć.");
        product.forEach(System.out::println);
        System.out.println("Podaj nr produktu: ");
        product.remove(sc.nextInt()-1);
        sc.nextLine();
        for (int i=0;i<product.size();i++)
        {
            product.get(i).setIndex(i+1);
        }
        System.out.println("Produkt został usunięty!");
    }

    //Metoda netVatGross obliczająca wartości netto, brutto i vat
    private static void netVatGross(double totalPrice)
    {
        System.out.println("Wartość netto:");
        System.out.println(round(totalPrice * 0.77, 2));
        System.out.println("VAT:");
        System.out.println(round(totalPrice * 0.23, 2));
        System.out.println("Wartość brutto:");
        System.out.println(round(totalPrice, 2));
    }

    /*Metoda discount pobierająca od użytkownika zniżkę wyrażoną w [%],
    * a następnie obniżająca rachunek o podaną zniżkę*/
    private static double discount(Scanner sc, double totalPrice)
    {
        System.out.println("Podaj zniżkę w %:");
        double discount = round(sc.nextDouble(), 2);
        sc.nextLine();
        System.out.println("Kwota przed zniżką:");
        System.out.println(totalPrice);
        System.out.println("Kwota po zniżce:");
        totalPrice = round(totalPrice * (1 - discount / 100), 2);
        System.out.println(totalPrice);
        return totalPrice;
    }

    /*Metoda coupon pobierająca od użytkownika bon zmniejszający rachunek o konkretną wartość*/
    private static double coupon(Scanner sc, double totalPrice)
    {
        System.out.println("Podaj wielkość bonu:");
        double coupon = round(sc.nextDouble(), 2);
        sc.nextLine();
        System.out.println("Kwota przed bonem:");
        System.out.println(totalPrice);
        System.out.println("Kwota po bonie:");
        totalPrice = round(totalPrice - coupon, 2);
        System.out.println(totalPrice);
        return totalPrice;
    }
    //Metoda getLogin pobierająca od użytkownika-kasjera jego numer
    private static String getLogin(Scanner sc)
    {
        System.out.println("Podaj swój numer kasjera: ");
        String login = sc.nextLine();
        System.out.println("Witaj " + login);
        return login;
    }

    private static void addProducts(List<Product> product, Scanner sc)
    {
        System.out.println("Podawaj kolejno: nazwę produktu, ilość i cenę detaliczną.");
        System.out.println("Aby zakonczyć dodawanie produktów w polu nazwy wpisz cyfrą zero ('0') lub 'exit'");
        while (true)
        {
            String name;
            double quantity;
            double pricePerUnit;
            try
            {
                name = sc.nextLine();
                if (name.equals("0") || name.equals("exit"))
                {
                    break;
                }
                quantity = sc.nextDouble();
                sc.nextLine();
                pricePerUnit = sc.nextDouble();
                sc.nextLine();
                product.add(new Product(product.size()+1,name, round(quantity, 2), round(pricePerUnit, 2)));
                System.out.println("Dodano produkt: " + "\n" + product.get(product.size()-1));

            } catch (InputMismatchException e)
            {
                System.out.println("Błąd podczas wprowadzania danych! Powtórz ostatni wpis.");
            }
        }
    }

    private static double priceToPay(List<Product> product, double totalPrice)
    {
        for (Product p : product)
        {
            totalPrice += p.getPrice();
        }
        return round(totalPrice, 2);
    }

    private static void printRecipe(List<Product> product, double totalPrice, String login)
    {
        product.forEach(System.out::println);
        System.out.println("Kwota do zapłaty: " + totalPrice);
        System.out.println("Rachunek utworzony przez kasjera nr: " + login);
    }

    public static double round(double value, int places)
    {
        if (places < 0) throw new IllegalArgumentException();
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    private static void printOptions()
    {
        System.out.println("Wybierz opcję: ");
        System.out.println("1 - dodaj produkt");
        System.out.println("2 - dodaj znizke/upust/rabat");
        System.out.println("3 - dodaj bon");
        System.out.println("4 - wyznacz netto/brutto/vat");
        System.out.println("5 - wyświetl produkty");
        System.out.println("6 - usuń produkt");
        System.out.println("0 - wyjdź z programu ");
    }

    private static void exitApp()
    {
        System.out.println("Do widzenia!");
        System.exit(0);
    }
}

Klasa Product to pola ze zmiennymi, konstruktor, settery i gettery i nadpisana metoda toString() z formatem drukowania:

String.format(""%s\t%s\t%.02f\t%.02f\t%.02f",index,name,quantity,pricePerUnit,price); 

Jeśli coś by było niejasne to odpowiem jak najszybciej. Czy to jest dobry sposób wklejania kodu?

1

Nie do końca jest to dobre rozwiązanie. Wyobraź sobie bazę danych, gdzie masz kilka milionów/miliardów obiektów, zabraknie RAM'u :);

  1. Osobiście przerobiłbym to na "klasy" tak jak napisałeś czyli pełna obiektowość;
  2. Idealnie pasuje tutaj prosty CRUD na SQLite/SQL;
    Obsługa CRUD'a w CV to duży+;(A raczej podstawa?)

To taka moja sugestia;

1

Spróbuj stworzyć klasę z przedmiotami:

public class Item
{
    String name;
    double quantity;
    double pricePerUnit;
    double price;

    public Item (String name, double quantity, double pricePerUnit)
    {
        this.name = name;
        this.quantity = quantity;
        this.pricePerUnit = pricePerUnit;
        this.price = pricePerUnit * quantity;
    }
}

Możesz stworzyć nową listę przedmiotów List<Item> items = new ArrayList<>(); i potem do niej dodawać.

private static void addProduct (Item item)
{
    items.add(item);
}

addProduct (new Item("Szynka", 2, 2));
1

A gdybyś chciał, żeby użyszkodnik mógł to zrobić możesz w ten sposób, uniemożliwia to też podanie nieprawidłowej wartości.

    static void addProduct()
    {
        while (true)
        {
            System.out.println("Podaj nazwę przedmiotu, ilość i cenę detaliczną.");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            String name;
            double quantity;
            double pricePerUnit;

            try
            {
                name = bufferedReader.readLine();
                quantity = Double.parseDouble(bufferedReader.readLine());
                pricePerUnit = Double.parseDouble(bufferedReader.readLine());

                items.add(new Item(name, quantity, pricePerUnit));
                bufferedReader.close();
                break;
            }
            catch (IOException | NumberFormatException e)
            {
                System.out.println("Błąd podczas wprowadzania danych.");
            }
        }
    }
0

O, bufferedReader się wydaje ciekawy. Zapoznam się z tematem, bo faktycznie Scanner jest uciążliwy jeśli chodzi o błędy we wpisywaniu i takie tam. Zapoznam się też z try i catch.
W pierwszej kolejności jednak przerobię to na obiekty. Niech to jakoś wygląda :)
Dzięki za odpowiedzi.

1

Scanner jest uciążliwy?

    static void addProduct()
    {
        while (true)
        {
            System.out.println("Podaj nazwę przedmiotu, ilość i cenę detaliczną.");
            Scanner scanner = new Scanner(System.in);
            String name;
            double quantity;
            double pricePerUnit;
 
            try
            {
                name = scanner.nextLine();
                quantity = scanner.nextInt());
                scanner.nextLine();
                pricePerUnit = scanner.nextDouble());
                scanner.nextLine();
 
                items.add(new Item(name, quantity, pricePerUnit));
                break;
            }
            catch (InputMismatchException e)
            {
                System.out.println("Błąd podczas wprowadzania danych.");
            }
        }
    }

Dwie uwagi:

  • kod sprawdzający całość danych jest niewygodny dla użytkownika, jeśli poda błędną cenę (np użyje złego separatora dziesiętnego), to musi wprowadzać ponownie wszystkie dane,
  • kod z poprzedniego postu jest błędny - wywołaj dwukrotnie funkcję addProduct.
0

@bogdans
Mam jedną uwagę do tego. Jeśli używam name = scanner.nextLine() otrzymuję komunikat o błędzie (wywołuje instrukcję catch). Jest to jednak tylko komunikat, bo wpisany String zapisuje poprawnie. Natomiast użycie scanner.next() czyta String poprawnie, ale nie rozpoznaje błędu w przypadku wpisania czegoś innego niż String.

1
  1. Nie wierzę, że użycie nextLine generuje wyjątek.
  2. Nie rozumiem, jak Ty rozpoznajesz, że coś nie jest Stringiem? Wszystko co użytkownik wpisze na klawiaturze jest uważane za String, do innych typów musisz konwertować (mniej lub bardziej jawnie).
0

Hm, teraz zwróciłem uwagę, że catch wyskakuje przy pierwszym wpisie. Jak to wyeliminować?
A co do Stringów to chodziło mi o String jako typ prosty. Mam nadzieję, że dobrze tłumaczę.

W załączniku screen.

1
  1. Twój kod różni się od mojego, Ty przekazujesz obiekt typu Scanner do funkcji addProducts, a przedtem go pewnie wykorzystujesz, gdy użytkownik wybiera pozycję z menu. Nie zastanowiłeś się dlaczego w moim kodzie po pobraniu liczby ze Scannera jest "puste" wywołanie nextLine()? Ono jest konieczne po każdym pobraniu liczby.
  2. Ja wiem, że String to typ prosty, ale jeśli użytkownik pytany o nazwę wpisał 123 lub 14.02.2016, to on wpisał String, żadnego błędu nie ma.
0
  1. Heh, faktycznie. Dodanie sc.nextLine() po wyborze opcji menu usunęło wywołanie funkcji catch. Możesz mi łopatologicznie wytłumaczyć, dlaczego po liczbach musi być wywołana funkcja nextLine()? Kojarzę, że chodzi o "skonsumowanie" znaku Enter, czytałem o tym parę razy ale wciąż piszę to bardziej z nawyku, bez zrozumienia.
  2. Zrozumiałem. Ma to sens i chyba nie ma sensu z tym kombinować, żeby w przypadku wpisania int lub double metoda wywalała błąd.
    Jak pisałem w pierwszym poście nie wszystko jest dla mnie jasne, ale dzięki za cierpliwość!
1

Metoda nextLine czyta całą linię i usuwa z bufora klawiatury Enter, który kończył wprowadzanie danych. Metody nextInt, nextDouble, nextFloat czytają tylko fragment linii (do pierwszego białego znaku (spacja, Tab, Enter). Reszta zostaje w buforze klawiatury.

option = sc.nextInt(); //w buforze klawiatury został Enter
...
addProducts(...sc,..);
...
                name = scanner.nextLine(); //odczytuje pusty String i usuwa ten pozostawiony Enter
                if (name.equals("exit"))
                {
                    break;
                }
                quantity = scanner.nextInt();  //ten wiersz generuje błąd, użytkownik sądzi, że wpisuje nazwę, program uważa, że wpisywana jest ilość
0

Mały update.
Ostatnio było mało czasu na Javę. Zmieniłem wybór opcji w głównym menu z int na String. Powoduje to, że w przypadku wyboru czegoś innego niż cyfry 0-5 otrzymujemy komunikat o źle wprowadzonej opcji i możemy całość ponowić. Proste :)

W następnej kolejności dodam usuwanie produktów. Myślę jak to zrobić. Najprościej chyba dodać indeks przed każdym produktem i ewentualne usuwanie przeprowadzać za pomocą wskazania indeksu.

Zaktualizowałem kod w pierwszym poście.

0

Mam problem. Próbuję zadeklarować zmienną index, która będzie trzymać wartość przez cały czas trwania programu. Niestety nie umiem tego zrobić, bo wartość np. przy wyjściu z opcji dodawania produktów resetuje się na zadeklarowaną na początku wartość, a chodzi mi o to, że jak wrócę do dodawania produktów by program kontynuował numerowania produktów. Jakieś wskazówki jak to zrobić?

0

jakie ma zastosowanie petla while w takiej formie while(true) break wychodzi z petli switch a nadal zostaje w nieskonczonej petli while .

0

ja zazwyczaj upraszam klasy do minimum, tworze pakiety i dostepy pakietowe, im mniej klasy o sobie wiedza tym lepiej, poza tym przechowanie w db jest to podstawa

0
Wybitny Samiec napisał(a):

jakie ma zastosowanie petla while w takiej formie while(true) break wychodzi z petli switch a nadal zostaje w nieskonczonej petli while .

No tak, program ma działać dopóki użytkownik nie wybierze opcji zamykającej program.

0

Hejo

Zaktualizowałem troszkę aplikację ale mam pytanie. W poprzednich odpowiedziach pojawiła się rada, żeby przerobić całość na klasy. Co przerobić na klasy? Na moje rozumiem to w ten sposób:
Mam kilka metod, którymi operuję na cenach, np. discount (do obniżania rachunku o jakiś %) czy coupon (do zmniejszania o jakąś wartość). Tworzę sobie klasę o jakiejś adekwatnej nazwie i w niej umieszczam logikę (dobre słowo?) tych metod, a w klasie main po prostu je wywołuję z odpowiednimi parametrami - w moim przypadku Scanner sc i double totalPrice, dla obu metod.
Dobrze to rozumiem?

Do takich stricte matematycznych metod mogę także napisać testy jednostkowe, zgadza się? Do jakich jeszcze?

Liczę na odpowiedzi :)

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