Czyszczenie ifologii

0

Hej! Chciałbym was spytać czy jest jakiś wzorzec który pomógłby wyczyścić taką ifologię jak poniżej? Napisałem interfejs który ma metodę boolean doSomething(SomeObject o) gdzie wartość zwrotna mówi o tym czy warunek został spełniony i akcja się wykonała. Każdego kolejnego ifa zamieniłem na klasę która implementuje wcześniej utworzony interfejs. W metodzie methodABC() tworzę listę instancji tych wszystkich klas, uruchamiam na każdym z nich metodę doSomething(o) i szukam pierwszego który zwrócił mi true. jeśli taki istnieje, robię return.
Czy takie podejście ma rację bytu? Czy już lepiej zostać przy ifologi? Jakie znacie rozwiązanie takiego problemu? A może to nie jest w ogóle problem?

    private void methodABC(SomeObject o){
        
        if(o.getA() != null){
            doSomethingWhenAIsNotNull(o);
            return;
        }

        if(o.getB() != null){
            doSomethingWhenBIsNotNull(o);
            return;
        }
        ...
    }
3

Użyj wzorca strategii:

interface MyOperation {
    boolean apply(SomeObject o);
    void execute(SomeObject o);
}

A później implementujesz interfejs w klasach w zależności od case'u jaki masz wykonać.
Edit:
Później masz listę List<SomeObject> strategies z implementacjami i lecisz czysto foreachem w jednej linijce albo forem.

4
MrMadMatt napisał(a):

Użyj wzorca strategii:

interface MyOperation {
    boolean apply(SomeObject o);
    void execute(SomeObject o);
}

A później implementujesz interfejs w klasach w zależności od case'u jaki masz wykonać.
Edit:
Później masz listę List<SomeObject> strategies z implementacjami i lecisz czysto foreachem w jednej linijce albo forem.

Jaka jest różnica znaczeniowa między apply a execute ?

Ja bym metody nazwał test i accept :

interface PartialConsumer<T> {
    boolean test(T t);
    void accept(T t); 
}

Wtedy jak ktoś lubi sprytny kod można napisać:

interface PartialConsumer<T> extends Predicate<T>, Consumer<T> {
}
0
Kamil Żabiński napisał(a):
MrMadMatt napisał(a):

Użyj wzorca strategii:

interface MyOperation {
    boolean apply(SomeObject o);
    void execute(SomeObject o);
}

A później implementujesz interfejs w klasach w zależności od case'u jaki masz wykonać.
Edit:
Później masz listę List<SomeObject> strategies z implementacjami i lecisz czysto foreachem w jednej linijce albo forem.

Jaka jest różnica znaczeniowa między apply a execute ?

Ja bym metody nazwał test i accept :

interface PartialConsumer<T> {
    boolean test(T t);
    void accept(T t); 
}

Wtedy jak ktoś lubi sprytny kod można napisać:

interface PartialConsumer<T> extends Predicate<T>, Consumer<T> {
}

No różnica jest taka że apply testuje czy dany case może zostać użyty, być może powinienem to nazwać: isApplayable albo test. Kwestia nazewnictwa.

0

Jest trochę sposobów, ale bardzo często można kreatywnie podejść do tego, co @MrMadMatt napisał i np.

public enum MyEnum {
    NAME_STARTS_WITH_START(input -> input.getName().startsWith("START"), input -> System.out.println("Starting: " + input.getId())),
    NAME_ENDS_WITH_END(input -> input.getName().endsWith("END"), input -> System.out.println("Ending: " + input.getId())),
    DEFAULT(input -> true, input -> System.out.println("Proceeding: " + input.getName()))
    ;

    private final Consumer<MyInput> consumer;
    private final Predicate<MyInput> qualifier;

    private Predicate<MyInput> myInput;

    MyEnum(Predicate<MyInput> qualifier, Consumer<MyInput> consumer) {
        this.qualifier = qualifier;
        this.consumer = consumer;
    }

    public static void consume(MyInput input) {
        Stream.of(values())
                .filter(element -> element.qualifier.test(input))
                .findFirst()
                .ifPresent(element -> element.consumer.accept(input));
    }
}

Ewentualnie jeśli często inputem jest String to można użyć mapy typu Map<String, Consumer<String>> itp.
To z takich prostszych, można jeszcze bawić się w Optionale żeby zaoszczędzić sobie ifologii:

public void doSomething(Object a, Object b) {
        // Zamiast: 
        if (a != null) {
            process(a);
        } else if (b != null) {
            process(b);
        }

        // Można:
        Optional.ofNullable(a)
                .or(() -> Optional.ofNullable(b))
                .ifPresent(this::process);
    }

Albo w ogóle nie bawić się w tradycyjną Javę tylko Vavra sobie dociągnąć i wtedy jest jeszcze mniej takich rzeczy.

Ogólnie takich sposobów jest wiele, ale łatwo czasem przedobrzyć. Tj. kończymy z kodem bez if'ów, za to nieczytelnym.

1

Ponieważ te metody nie akceptyją A/B tylko SomeObject oraz zwracają void, można to przerobić na:

    private void methodABC(SomeObject o){
        doSomethingWhenAIsNotNull(o);
        doSomethingWhenBIsNotNull(o);
        ...
    }

void doSomethingWhenAIsNotNull(SomeObject o) {
   if (o.getA() == null) {
     return; // nothing to do
   }
   // ...
}

To, że doSomethingWhenAIsNotNull operuje na wyniku getA to szczegół implementacyjny tej metody i może się w przyszłości zmienić.
Jeśli możesz zmienić interfejs doSomething... to można też tak:

void doSomethingWhenAIsNotNull(AObject a) {
   if (a == null) {
     return; // nothing to do
   }
   // ...
}

Czy akceptowanie null-a i nic nie robienie jest brzydkie?
Gdyby tak było, nie powstałoby Apache Commons StringUtils.

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