Omówienie aplikacji domowej

Czas na tydzień 7, jednak wcześniej musimy omówić zadaną aplikację domową w tygodniu 6. Pracy było trochę, w końcu musieliśmy zrefaktorować część naszego kodu. Nie ma na co czekać, do dzieła!

Color enum

Pierwszym zadaniem było stworzenie typu wyliczeniowego dla koloru, który miał przechowywać w sobie wartości kolorów HEX.

package entity.enums;

public enum Color {
    BLACK("#000000"), WHITE("#FFFFFF"), RED("#FF0000"), GREEN("#008000"), BLUE("#0000FF"), YELLOW("#FFFF00");

    private String hex;

    Color(String hex) {
        this.hex = hex;
    }

    public String getHexColor() {
        return hex;
    }
}

Nie ma zbyt wielkiej filozofii, w konstruktorze przekazujemy wartość HEX w stringu (ponieważ jest hashtag na początku wartości) zapisujemy ją w prywatnym polu oraz umożliwiamy dostanie się do niego poprzez getter.

Material enum

Kolejny jeszcze prostszy typ wyliczeniowy, nie musimy tworzyć nawet konstruktora, ponieważ konstruktor bezparametrowy tworzy się automatycznie choć go nie widać w kodzie. 😉

package entity.enums;

public enum Material {
    LEATHER, FUR, COTTON, WOOL, POLYESTERS;
}

SkinType enum

SkinType enum tworzymy analogicznie jak material enum:

package entity.enums;

public enum  SkinType {
    NATURAL, ARTIFICIAL;
}

ProductSeparators enum

Nadszedł czas na przeniesienie separatorów zapisywanych produktów do typu wyliczeniowego – jest to bardzo dobre i czytelne rozwiązanie. W każdym z nich zapisujemy znak, który odpowiada za separator. Dodatkowo jest getter oraz przeciążona metoda toString, która domyślnie w typie wyliczeniowym zwraca jego nazwę.

public enum ProductSeparators {
    PRODUCT_SEPARATOR("#"), PRODUCT_ID("P"), ClOTH_ID("C"), BOOTS_ID("B");
    
    private String word;

    ProductSeparators(String word) {
        this.word = word;
    }

    @Override
    public String toString() {
        return word;
    }

    public String getWord() {
        return word;
    }
}

Pamiętaj, aby takie stałe również wydzielać z klas do typów wyliczeniowych, kod Twój będzie dużo prostszy do zrozumienia. 😉

Enum – klasy Product, Boots i Cloth

Nadszedł czas na edycję klas Product, Boots, Cloth tak aby korzystały z wcześniej utworzonych typów wyliczeniowych. Nie chcemy w końcu stringów w klasach. 😉

Product

Pole odpowiadające za kolor zmieniamy z:

private String color;

na:

private Color color;

Oczywiście wiąże się to ze zmianą konstruktora na:

public Product(Long id, String productName, Float price, Float weight, Color color, Integer productCount) {
    this.id = id;
    this.productName = productName;
    this.price = price;
    this.weight = weight;
    this.color = color;
    this.productCount = productCount;
}

oraz gettera:

public Color getColor() {
    return color;
}

Dodatkowo metody odpowiedzialne za zapisywanie produktu do Stringa zostały przerefaktorowane tak, aby korzystały z ProductSeparators

    protected String getBasicProductString() {
        return id + ProductSeparators.PRODUCT_SEPARATOR.toString() + productName + ProductSeparators.PRODUCT_SEPARATOR.toString() + price + ProductSeparators.PRODUCT_SEPARATOR.toString() + weight + ProductSeparators.PRODUCT_SEPARATOR.toString() + color + ProductSeparators.PRODUCT_SEPARATOR.toString() + productCount;
    }

    @Override
    public String toString() {
        return ProductSeparators.PRODUCT_ID + ProductSeparators.PRODUCT_SEPARATOR.toString() + getBasicProductString();
    }
}

Boots

Pole typu boolean zmieniamy już na nasz enum SkinType: Czyli zmieniamy to:

private boolean isNaturalSkin;

na:

private SkinType skinType;

Ddatkowo musimy zmienić rownież konstruktor:

public Boots(Long id, String productName, Float price, Float weight, Color color, Integer productCount, Integer size, SkinType skinType) {
    super(id, productName, price, weight, color, productCount);
    this.size = size;
    this.skinType = skinType;
}

Oraz getter, w końcu nie zwracamy już Stringa

public SkinType getSkinType() {
    return skinType;
}

Również zmieniłem separator w metodzie toString korzystając już z typu wyliczeniowego:

@Override
public String toString() {
    return ProductSeparators.BOOTS_ID + ProductSeparators.PRODUCT_SEPARATOR.toString() + getBasicProductString() + ProductSeparators.PRODUCT_SEPARATOR.toString() + size + ProductSeparators.PRODUCT_SEPARATOR.toString() + skinType;
}

Cloth

W klasie Cloth również musimy skorzystać z typu wyliczeniowego, czyli musimy zmienić typ tego pola:

private String material;

na:

private Material material;

Oczywiście jeszcze zmiana konstruktora:

public Cloth(Long id, String productName, Float price, Float weight, Color color, Integer productCount, String size, Material material) {
    super(id, productName, price, weight, color, productCount);
    this.size = size;
    this.material = material;
}

No i wiadomo, że getter również wymaga zmiany:

public Material getMaterial() {
    return material;
}

I również skorzystałem z separatorów z typu wyliczeniowego ProductSeparators

@Override
public String toString() {
    return ProductSeparators.ClOTH_ID + ProductSeparators.PRODUCT_SEPARATOR.toString() + getBasicProductString() + ProductSeparators.PRODUCT_SEPARATOR.toString() + size + ProductSeparators.PRODUCT_SEPARATOR.toString() + material;
}

Refaktoryzacja ProductParser

Ze względu, że dodaliśmy dużo typów wyliczeniowych m.in zamiast Stringów to musimy zmienić działanie ProductParsera.

Na początku w ProductSeparators dodałem metodę, która zwraca odpowiedni typ produktu na podstawie wczytanego znaku z całego Stringa:

public static ProductSeparators getIdByChar(String word) {
    for(ProductSeparators id: ProductSeparators.values()) {
        if (id.getWord().equals(word)) {
            return id;
        }
    }
    return null;
}

A jego wywołanie wygląda tak – wraz z blokiem switch case:

public static Product stringToProduct(String productStr) {
    final ProductSeparators productType = ProductSeparators.getIdByChar(productStr.substring(0,1));

    switch (productType) {

        case PRODUCT_ID:
            return convertToProduct(productStr);

        case ClOTH_ID:
            return convertToCloth(productStr);

        case BOOTS_ID:
            return convertToBoots(productStr);
    }

    return null;
}

I teraz kwestia zmiany metod co do konwertowania Stringa na odpowiednie typy wyliczeniowego – w tym celu stworzyłem osobne klasy:

ColorParser

package entity.parser;

import entity.enums.Color;

public class ColorParser {

    public static Color parseStrToColor(String str) {
        String color = str.toUpperCase();

        if (color.equals("RED")) {
            return Color.RED;
        } else if (color.equals("BLUE")) {
            return Color.BLUE;
        } else if (color.equals("BLACK")) {
            return Color.BLACK;
        } else if (color.equals("YELLOW")) {
            return Color.YELLOW;
        } else if (color.equals("GREEN")) {
            return Color.GREEN;
        } else if (color.equals("WHITE")) {
            return Color.WHITE;
        }

        return Color.WHITE;
    }

}

MaterialParser

package entity.parser;

import entity.enums.Material;

public class MaterialParser {
    public static Material parseStrToMaterial(String str) {
        String material = str.toUpperCase();

        if (material.equals("LEATHER")) {
            return Material.LEATHER;
        } else if (material.equals("FUR")) {
            return Material.FUR;
        } else if (material.equals("COTTON")) {
            return Material.COTTON;
        } else if (material.equals("WOOL")) {
            return Material.WOOL;
        } else if (material.equals("POLYESTERS")) {
            return Material.POLYESTERS;
        }

        return Material.POLYESTERS;
    }
}

SkinParser

package entity.parser;

import entity.enums.SkinType;

public class SkinParser {

    public static SkinType parseStrToSkinType(String str) {
        String type = str.toUpperCase();

        if (type.equals("NATURAL")) {
            return SkinType.NATURAL;
        } else if(type.equals("ARTIFICIAL")) {
            return SkinType.ARTIFICIAL;
        }

        return SkinType.ARTIFICIAL;
    }
}

I korzystając z tych wszystkich metod statycznych, mogę z łatwością konwertować Stringa na odpowiedni typ wyliczeniowy i tworzyć dzięki niemu konkretny obiekt.

Spójrz – tak wyglądała wcześniej implementacja convertToProduct

private static Product convertToProduct(String productStr) {
       String [] productInformations = productStr.split(Product.PRODUCT_SEPARATOR);

       Long id = Long.parseLong(productInformations[1]);
       String productName = productInformations[2];
       Float price = Float.parseFloat(productInformations[3]);
       Float weight = Float.parseFloat(productInformations[4]);
       String color = productInformations[5];
       Integer productCount = Integer.parseInt(productInformations[6]);

       return new Product(id, productName, price, weight, color, productCount);
   }

I tym razem zmienił się typ color, który zostaje tworzony na podstawie Stringa przy użyciu metody parseStrToColor z klasy ColorParser. Czyli tak:

Color color = ColorParser.parseStrToColor(productInformations[5]);

I cała metoda convertToProduct:

private static Product convertToProduct(String productStr) {
    String [] productInformations = productStr.split(ProductSeparators.PRODUCT_SEPARATOR.toString());

    Long id = Long.parseLong(productInformations[1]);
    String productName = productInformations[2];
    Float price = Float.parseFloat(productInformations[3]);
    Float weight = Float.parseFloat(productInformations[4]);
    Color color = ColorParser.parseStrToColor(productInformations[5]);
    Integer productCount = Integer.parseInt(productInformations[6]);

    return new Product(id, productName, price, weight, color, productCount);
}

ConverToCloth

private static Cloth convertToCloth(String productStr) {
    String [] productInformations = productStr.split(ProductSeparators.PRODUCT_SEPARATOR.toString());

    Long id = Long.parseLong(productInformations[1]);
    String productName = productInformations[2];
    Float price = Float.parseFloat(productInformations[3]);
    Float weight = Float.parseFloat(productInformations[4]);
    Color color = ColorParser.parseStrToColor(productInformations[5]);
    Integer productCount = Integer.parseInt(productInformations[6]);
    String size = productInformations[7];
    Material material = MaterialParser.parseStrToMaterial(productInformations[8]);

    return new Cloth(id, productName, price, weight, color, productCount, size, material);
}

I jeszcze convertToBoots

private static Boots convertToBoots(String productStr) {
    String [] productInformations = productStr.split(ProductSeparators.PRODUCT_SEPARATOR.toString());

    Long id = Long.parseLong(productInformations[1]);
    String productName = productInformations[2];
    Float price = Float.parseFloat(productInformations[3]);
    Float weight = Float.parseFloat(productInformations[4]);
    Color color = ColorParser.parseStrToColor(productInformations[5]);
    Integer productCount = Integer.parseInt(productInformations[6]);
    Integer size = Integer.parseInt(productInformations[7]);
    SkinType skinType = SkinParser.parseStrToSkinType(productInformations[8]);

    return new Boots(id, productName, price, weight, color, productCount, size, skinType);
}

Refaktoryzacja klasy Main

Zmieniło się dużo w produktach, dlatego musieliśmy zmienić formę wczytywania danych od klienta podczas tworzenia nowych produktów.

Podczas wczytywania m.in. koloru, materiału, typu skóry wypisałem wszystkie możliwości do wpisania:

System.out.println("Choose color: RED, BLUE, GREEN, BLACK, WHITE, YELLOW ");
color = ColorParser.parseStrToColor(scanner.next());

I na podstawie metod parseStrTo* tworzyłem odpowiednie typy wyliczeniowe.

Analogicznie trzeba zrobić to dla Material:

System.out.println("Choose material: LEATHER, FUR, COTTON, WOOL, POLYESTERS.");
material = MaterialParser.parseStrToMaterial(scanner.next());

Oraz SkinType:

System.out.println("Choose skin type: NATURAL, ARTIFICIAL ");
skinType = SkinParser.parseStrToSkinType(scanner.next());

Testy

Testów nie pisałem zbyt skomplikowanych, po prostu przetestowałem wszystkie możliwe case, jakie występują podczas parsowania Stringa do typu wyliczeniowego.

Przykładowy test wygląda tak:

@Test
public void testParseColorBlue() {
    String strColor = "BluE";

    Color color = ColorParser.parseStrToColor(strColor);

    Assert.assertEquals(Color.BLUE, color);
}

Podaję Stringa, wrzucam go do parsera i oczekuję konkretnego koloru – i tak z każdym przypadkiem i każdym parserem. 😉

Podsumowanie

I w sumie to tylko w tej aplikacji domowej, wydaję mi się, że ostatnie dwa tygodnie bardziej dały nam w kość podczas robienia aplikacji domowej. Można powiedzieć, że ta była bardzo przyjemna.

Jeśli zas było coś nie zrozumiałe to napisz w komentarzu lub na grupie Facebookowe – jeśli jest inaczej to czas ruszać na tydzień 7!

 

 

Kamil Klimek

Od 2016 jestem programistą Java. Przez pierwsze 4 lata pracowałem jako Full Stack Java Developer. Później postanowiłem postawić nacisk na Javę, żeby jeszcze lepiej ją poznać.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x