Trzy style

Tak przy okazji refleksji i rozmyślań filozoficznych, postanowiłem zademonstrować, jak w języku pokroju Javy zaimplementować rozwiązanie problemu obliczania ceny wszystkich produktów w koszyku.

Proceduralnie:


public class Produkt {
    private Integer cena = 0;
    private Integer ilosc = 0;
    private String nazwa = "";

   public Produkty(Integer cena, Integer ilosc, String nazwa);

    /** tu gettery i settery dla wszystkich parametrow parametrow **/

}

public class Koszyk {
    
    private List<Produkt> produkty;
  
    public List<Produkty> getProdukty();
    public void setProdukty(List<Produkty> produkty);

}

public class KoszykUtil {

    public static Integer podliczKoszyk(Koszyk koszyk){
        List<Produkty> produkty = koszyk.getProdukty();
        Integer suma = 0;        

        for(Produkt produkt: produkty){
            suma+= produkt.getCena() * produkt.getIlosc();
        }
        return suma;
    }
}

Produkt p1 = new Produkt(12, 1, "baton z ropą");
Produkt p2 = new Produkt(13, 1, "baton z benzyną");
List<Produkty> produkty = new ArrayList<Produkty>();
produkty.add(p1);
produkty.add(p2);

Koszyk k = new Koszyk();
k.setProdukty(produkty);

Integer suma = KoszykUtil.podliczKoszyk(k);
System.out.println(suma);

Obiektowo:


public class Produkt {

    private final String nazwa;
    private final Integer cena;
    private Integer ilosc;

    public Produkt(String nazwa, Integer cena, Integer ilosc);
    public Integer suma(){
        return cena*ilosc;
   }

}

public class Koszyk {

    private List<Produkt> produkty;
    private Integer wartosc = 0;    

    public dodajProdukt(Produkt produkt){
        produkty.add(produkt);
        wartosc += produkt.suma();
    }

    public Integer wartosc(){
        return wartosc;
    }
}

Koszyk k1 = new Koszyk();
Produkt p1 = new Produkt("baton z benzyna" , 10, 1);
Produkt p2 = new Produkt("baton z ropą", 10, 1);
k1.dodajProdukt(p1);
k1.dodajProdukt(p2);

Integer suma = k1.wartosc();
System.out.println(suma);

Funkcyjne:


public interface Sumator<D> {

     public Integer sumuj(Integer suma, D element); 
}

public class ListUtil {

    public static <T> Integer sumuj(Sumator s, List<T> dane) {
        Integer suma = 0;
        for (T element : dane) {
            suma = s.sumuj(suma, element);
        }
        return suma;

    }
}

public final class Produkt {
    public final Integer cena;
    public final String nazwa;

    public Produkt(String nazwa, Integer cena){
        this.nazwa = nazwa;
        this.cena = cena;
    }
}

Produkt p1 = new Produkt("baton z ropą", 10);
Produkt p2 = new Produkt("baton z benzyna", 10);

List<Produkt> produkty = new ArrayList<Produkt>();
produkty.add(p1);
produkty.add(p2);

produkty = java.util.Collections.unmodifiableList(produkty);
Integer suma = ListUtil.sumuj(new Sumator<Produkt>() {

            @Override
            public Integer sumuj(Integer suma, Produkt element) {
                return suma + element.cena;
            }
        }, produkty);

System.out.println(suma);

Czego można dowiedzieć się z tych prostych przykładów ? Otóż można zauważyć w każdym z nich cechy charakterystyczne danego stylu programowania i tego na co kładzie nacisk.

  1. Proceduralny – stawia na swobodny przepływ danych.
  2. Obiektowy – abstrakcja, enkapsulacja, odwzorowanie modelu domeny
  3. Funkcyjny – niezmienność (immutability), generyczność i funkcja jako pełnoprawny element języka (tutaj emulowana przez anonimową klasę)

Mam nadzieje, że powyższe przykłady były pouczające.Jestem bardzo ciekawy, które z powyższych rozwiązań przypadło Wam do gustu i dlaczego, oraz jaki styl preferujecie.

  1. Theq pisze:

    Każde z tych podejść ma swoje wady i zalety, więc stosuje je w zależności od potrzeb. Moim zdaniem nie ma co wybierać „najlepszego” stylu i zamykać się w jakiejś doktrynie. Jakbym miał wybierać to bym wybrał styl proceduralny. W końcu całe to programowanie to tylko przetwarzanie danych :)

  2. Masz racje, że to przetwarzanie danych, ale z drugiej strony, zarówno podejście obiektowe jak i funkcyjne zapewnia lepszą testowalność i przewidywalność kodu, przez co jest łatwiejsze w rozbudowie. Ja osobiście bardzo lubię podejście obiektowe od strony modelowania, natomiast funkcyjne od strony implementacji samych algorytmów.

  3. Śpiechu pisze:

    Mnie najbardziej spodobał się przykład 3 – najbardziej powykręcany. Z kolei najczytelniejszy jest oczywiście 2. Podejście obiektowe jest chyba najbardziej naturalne dla nas humanoidów.
    Zastanawiam się czy należało zrzucać liczenie sumy na sam produkt, czy to nie koszyk powinien brać na siebie.

    W pierwszym przykładzie można było zastosować diamond operator?
    List produkty = new ArrayList();

  4. Śpiechu pisze:

    @up pozjadało wszystkie tagi :-/

  5. Theq pisze:

    Może i podejscie obiektowe jest bardziej intuicyjne dla nas, ale już nie jest dla komputera. Jeżeli wydajność jest naszym priorytetem to OOP już nie jest takie fajne. Nie jestem też pewien co do łatwiejszej testowalności, przewidywalności (wtf?) i łatwości rozbudowy kodu. Niektórzy tak potrafią namieszać, że jest wręcz przeciwnie.

    btw. ?

    public Integer wartosc(){
    return wartosc();
    }

  6. @Theq -> tam był błąd poprawiłem.
    @Śpiechu -> jeszcze się nie bawiłem Javą 7

    Co do intuicyjności to chyba najbardziej jest jednak proceduralne, bo najwięcej takiego kodu widzę generalnie wszędzie, oczywiście ci którzy go piszą, twierdzą, że jest obiektowy :)

  7. thek pisze:

    Preferowany: obiektowy. Logiczniejsze jest moim zdaniem widzenie tych zależności miedzyobiektowych niż tworzenie bytów trochę „znikąd”. Najlepiej to widać chyba w polu wartość. Proceduralnie czy funkcyjnie są to osobne twory, obiektowo jest to składowa samego Koszyka.

    A co do funkcyjnego podejścia to nie jest ono zakręcone dla kogoś kto „babrał się” w C++ z przeciążaniem operatorów :) Bardzo podobne i tu odsyłam do wpisu na Wiki: http://pl.wikibooks.org/wiki/C++/Przeci%C4%85%C5%BCanie_operator%C3%B3w
    Kto chce niech sobie porówna, z tym, że tutaj użyto z tego co widzę klas szablonowych (też w C++ istnieją), czego w przykładach na Wiki nie ma, albo mi umknęło przy pobieżnym zerknięciu.

  8. Podejście funkcyjne nie jest zakręcone, kwestia tylko taka, że implementacja w Javie jest upierdliwa, dlatego też zrobiono Scalę ;).

    Co do zależności między obiektami, to tu właśnie wchodzi pojęcie abstrakcji i enkapsulacji – czyli nie interesuj się, co się dzieje pod maską :>

  9. pojawia się problem rabatów łączonych a te maja różne źródła:
    – rabat ilościowy dla danego produktu
    – rabat wartościowy dla koszyka
    – rabat lojalnościowy dla klienta

    moim zdaniem dobry projekt rozłoży te rabaty w różnych obiektach stosownie do kompetencji, mały kłopot: czy produkt zna swoją historię?

    wiec np.
    – magazyn wie jakie są rabaty ilościowe
    – koszyk zna rabaty kwotowe (od wielkości pojedynczego zamówienia)
    – CRM wie jakie rabaty maja klienci

    niby rozwalone po klasach ale zwracam uwagę, że wszystkie trzy rabaty maja zupełnie inne źródła i innych „właścicieli”… podejście obiektowe/DDD (w tym projektowanie poprzez odpowiedzialność klas) wydaje się najskuteczniejsze…

  10. @Jarek Żeliński – jeszcze wszystko jest kwestią tego, w którym momencie liczony jest ewentualny rabat…

  11. Zakładam, że wysokość rabatu nie powinna zależeć od momentu jego naliczenia, nie licząc oczywiście aktualnego stanu umowy, sumy obrotów itp….

  12. Artur Świerc pisze:

    Także chciałem poruszyć temat rabatów w koszyku, jest to o tyle ważny temat, ponieważ powoduje wiele zawiłości. Do tej pory mam do czynienia z tworami mającymi po 1000 linii ifów.

    Czytałem trochę o politykach w DDD jednakże nie znalazłem żadnych sensownych przykładów, wszędzie tylko ogólniki.

  13. wzorzec strategii oraz chyba ważniejsze: sensowne rozłożenie wiedzy o rabatach jak pisałem…

  14. Widzę, że niewinny eksperyment intelektualny przeradza się w jakąś głębszą dyskusje :> co do wzorca strategii to zgadzam się, że to dobre rozwiązanie.

  15. wzorca strategii używam często, standardowym zastosowaniem jest następujące podejście: przypadek użycia to serwis (usługa) systemu dla użytkownika, niech się nazywa „zarządzanie użytkownikiem”, realizuje go jedna klasa sterująca (przechwytuje każde wywołanie tej usługi przez użytkownika), świadcząca ten serwis, alternatywne scenariusze tego UC to strategie klasy sterującej… wracając do koszyka i rabatów: jeżeli tak (jak opisałem) podzielimy naliczanie rabatów to łatwo będzie ustalić kto do czego (do ustalania którego rabatu) ma prawa, próba ustalenia różnych praw do różnych składników rabatu metodą „jeden wielki if dla systemu rabatowego” czyli funkcyjne podejście, może być nie lada wyzwaniem.

  16. Theq pisze:

    A co z „rabatem” milionowego klienta, promocji czasowej, promocji 2 za 1, promocji typu kup tv dostaniesz bluray gratis, ustalanie ceny w zaleznosci od wyposazenia produktu, kodow promocyjnych, zmian kursu walut?? To tak jakbyście mieli za mało rozkminiania w pracy/na studiach 😛

  17. to kolejne scenariusze na naliczenie (udzielenie) rabatu. Rabaty mają to do siebie są zależne o niezależne, widzę tu jeszcze inny problem: są rabaty bez żadnego sensu biznesowego a brak logiki w biznesie jest także brakiem tej logiki w systemie. Nie raz spotykam się z tym, ze klient sam nie potrafi wykazać jakiejś logiki w tym co robi więc jak ma to zrobić system? Osobiście nie widzę żadnego problemu w systemach rabatowych, ważne jest by je po protu zrozumieć, bo jeżeli człowiek potrafi taki rabat w powtarzalny sposób naliczyć to system tym bardziej… jeżeli …

    Wskazane 2 za 1 czy blue ray gratis itp. to problem tworzenia i zarządzania regułami biznesowymi a nie rabatami (takie rabaty to reguły biznesowe).

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.