SOLID mit Java 8

Preview:

Citation preview

SOLID mit Java 8

Java Forum Stuttgart 2016

Roland MastSybit GmbHAgiler Software-ArchitektScrum Masterroland.mast@sybit.de

Roland MastSybit GmbHAgiler Software-Architektroland.mast@sybit.de

5 Principles

• Single responsibility

• Open Closed

• Liskov Substitution

• Interface Segregation

• Dependency Inversion

SOLID und Uncle Bob

Single ResponsibilityPrinciple?Single ResponsibilityPrinciple?

A class should have one, and only one, reason to change.

A class should have one, and only one, reason to change.

// Strings in einem Array nach deren Länge sortieren

Arrays.sort(strings, new Comparator<String>() {public int compare(String a, String b) {

return Integer.compare(a.length(), b.length());}

});

Arrays.sort(strings, (a, b) -> Integer.compare(a.length(), b.length()));

// Strings alphabetisch sortieren

Arrays.sort(strings, String::compareToIgnoreCase);

Lambdas

List<Person> persons = Arrays.asList(// name, age, sizenew Person("Max", 18, 1.9),new Person("Peter", 23, 1.8),new Person("Pamela", 23, 1.6),new Person("David", 12, 1.5));

Double averageSize = persons.stream().filter(p -> p.age >= 18).collect(Collectors.averagingDouble(p -> p.size));

Lambdas in Streams

Iteration über die Daten

Filtern anstattif (age >= 18) {

keep();

}Collectors berechnet den Durchschnitt

Single Responsibility

public interface BinaryOperation {long identity();long binaryOperator(long a, long b);

}

public class Sum implements BinaryOperation {@Overridepublic long identity() { return 0L; }

@Overridepublic long binaryOperator(long a, long b) {

return a + b;}

}

Single Responsibility

private long[] data;public long solve() {

int threadCount = Runtime.getRuntime().availableProcessors();ForkJoinPool pool = new ForkJoinPool(threadCount);pool.invoke(this);return res;

}protected void compute() {

if (data.length == 1) {res=operation.binaryOperator(operation.identity(), data[0]);

} else {int midpoint = data.length / 2;long[] l1 = Arrays.copyOfRange(data, 0, midpoint);long[] l2 = Arrays.copyOfRange(data, midpoint, data.length);SolverJ7 s1 = new SolverJ7(l1, operation);SolverJ7 s2 = new SolverJ7(l2, operation);ForkJoinTask.invokeAll(s1, s2);res = operation.binaryOperator(s1.res, s2.res);

}}

Daten müssen selbst aufgeteilt und zusammengeführt werden

Interface und Klasse definieren Operation

Parallele SummenberechnungThreadpool muss selbst erzeugt werden

private long[] data;

public long sumUp() {return LongStream.of(data)

.parallel()

.reduce(0, (a, b) -> a + b);}

Parallel ReduceSingle Responsibility

Single Responsibility Principle

Lambdas + Streams

Kapseln Verantwortlichkeiten

Open/ClosedPrinciple?Open/ClosedPrinciple?

You should be able to extend a classes behavior, without modifying it.

You should be able to extend a classes behavior, without modifying it.

public interface Iterable<T> {Iterator<T> iterator();

}

Iterable Interface

public interface Iterable<T> {Iterator<T> iterator();

void forEach(Consumer<? super T> action);Spliterator<T> spliterator();

}

Open/Closed

public interface Iterable<T> {Iterator<T> iterator();

default void forEach(Consumer<? super T> action) {for (T t : this) { action.accept(t); }

}

default Spliterator<T> spliterator() {return Spliterators.spliteratorUnknownSize(iterator(), 0);

}}

Default-ImplementationOpen/Closed

Open/Closed Principle

Default-Implementierungen in Interfaces

Flexibel Frameworks erweitern

Liskov's Substitution Principle?Liskov's Substitution Principle?

“No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the supertype.”

Derived classes must be substitutable for their base classes.

Derived classes must be substitutable for their base classes.

/*** Find the first occurrence of a text in files

* given by a list of file names.*/

public Optional<String> findFirst(String text, List<String> fileNames) {

return fileNames.stream().map(name -> Paths.get(name))

.flatMap(path -> Files.lines(path))

.filter(s -> s.contains(text))

.findFirst();}

Checked Exception in StreamsLiskov‘s Substitution

public final class Files {

public static Stream<String> lines(Path path)throws IOException {

BufferedReader br =Files.newBufferedReader(path);

try {return br.lines()

.onClose(asUncheckedRunnable(br));} catch (Error|RuntimeException e) {

….. 8< ……………………br.close();

….. 8< ……………………}

}

IOException

/*** Find the first occurrence of a text in files

* given by a list of file names.*/

public Optional<String> findFirst(String text, List<String> fileNames) {

return fileNames.stream().map(name -> Paths.get(name))

.flatMap(path -> mylines(path))

.filter(s -> s.contains(text))

.findFirst();}

Handle IOExceptionLiskov‘s Substitution

private Stream<String> mylines(Path path){try (BufferedReader br =

Files.newBufferedReader(path)) {

return br.lines().onClose(asUncheckedRunnable(br));

} catch (IOException e) {throw new UncheckedIOException(e);

}}

private static Runnable asUncheckedRunnable(Closeable c) {return () -> {

try {c.close();

} catch (IOException e) {

throw new UncheckedIOException(e);

}};

}

Close StreamLiskov‘s Substitution

java.io.BufferedReader::lines + java.nio.file.Files::find, lines, list, walkwerfen UncheckedIOException beim Zugriff innerhalb des Streams

Liskov´s Substitution Principle

Files + BufferedReader

UncheckedIOException nur bei neuen Methoden

Interface Segregation Principle?Interface Segregation Principle?

Make fine grained interfaces that are client specific.

Make fine grained interfaces that are client specific.

public interface ParserContext {Reader getReader();void handleLine(String line);void handleException(Exception e);

}

public void parse(final ParserContext context) {try (BufferedReader br = new BufferedReader(context.getReader())) {

String line;do {

line = br.readLine();if (line != null) { context.handleLine(line); }

} while (line != null)} catch (IOException e) { context.handleException(e); }

}

InterfacesInterface Segregation

1 Interface mit3 Methoden

<T> T withLinesOf(Supplier<Reader> reader,Function<Stream<String>, T> handleLines,Function<Exception, RuntimeException> transformException) {

try (BufferedReader br = new BufferedReader(reader.get())) {return handleLines.apply(br.lines());

} catch (IOException e) {throw transformException.apply(e);

}}

Functional InterfacesInterface Segregation

3 separate Interfaces

withLinesOf(() -> reader,

lines -> lines.filter(line -> line.contains("ERROR")).map(line -> line.split(":")[1]).collect(toList()),

LogAnalyseException::new);

Functional InterfacesInterface Segregation

LocalTimeInterface Segregation

Interface Segregation Principle

Functional Interfaces

Ermutigen zum Auftrennen von Interfaces

Dependency Inversion Principle?Dependency Inversion Principle?

Depend on abstractions, not on concretions.Depend on abstractions, not on concretions.

Warning: JDK internal APIs are unsupported andprivate to JDK implementation that are

subject to be removed or changed incompatibly and could break your application.

Please modify your code to eliminate dependency on any JDK internal APIs.

For the most recent update on JDK internal API replacements, please check:

https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

jdeps -jdkinternalsDependency Inversion

jdeps -jdkinternals classes

classes -> C:\Program Files\Java\jdk1.8.0_74\jre\lib\rt.jar

de.sybit.warranty.facade.impl.DefaultWarrantyFacade (classes)

-> com.sun.xml.internal.fastinfoset.stax.events.UtilJDK internal API (rt.jar)

jdepsDependency Inversion

return (Util.isEmptyString(param) || "*".equals(param));

public List<String> filterError(Reader reader) {try (BufferedReader br =new BufferedReader(reader)){

return br.lines().filter(line -> line.contains("ERROR")).map(line -> line.split(":")[1]).collect(toList());

} catch (IOException e) {throw new LogAnalyseException(e);

}}

LambdasDependency Inversion

Filterimplementierung ist abhängig von konkreten Implementierungen

private Parser parser = new Parser();

public List<String> filterError(Reader reader) {return parser.withLinesOf(

reader,lines -> lines

.filter(line -> line.contains("ERROR"))

.map(line -> line.split(":")[1])

.collect(toList()),LogAnalyseException::new);

}

LambdasDependency Inversion

filterError() ist nur noch abhängig von Abstraktionen

public class Parser {<T> T withLinesOf(Reader reader,

Function<Stream<String>, T> handleLines,Function<Exception, RuntimeException> onError) {

try (BufferedReader br = new BufferedReader(reader)) {return handleLines.apply(br.lines());

} catch (IOException e) {throw onError.apply(e);

}}

}

LambdasDependency Inversion

withLinesOf() kapselt die Abhängigkeiten

Dependency Inversion Principle

Lambdas

Sind starke Abstraktionen

module MyClient {

requires MyService;

}

module MyService {

exports de.sybit.myservice;

}

SOLID mit Java 9 - Jigsaw

“The principles of software design still apply, regardless of your programming style. The fact that you've decided to use a [functional] language that doesn't have an assignment operator does not mean that you can ignore the Single Responsibility Principle; or that the Open Closed Principle is somehow automatic.”

Robert C. Martin: OO vs FP (24 November 2014)http://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html

SOLID mit Java – OO vs FP

Roland Mast, Sybit GmbHAgiler Software-Architekt

roland.mast@sybit.de

Recommended