SOLID Java Code

53
SOLID Java Code Omar Bashir https://uk.linkedin.com/in/obprofile @OmarBashir_40 [email protected]

Transcript of SOLID Java Code

SOLID Java Code

Omar Bashir

https://uk.linkedin.com/in/obprofile

@OmarBashir_40

[email protected]

Coding Humanely

● What if our code had feelings ?

● What if we treated people like we treat code ?– e.g., Sir, you will need a foot transplant to be able to

use our latest range of trainers.

Desirable Code Qualities

● Simplicity– Ease of understanding.

● Extensibility– Ease of extending functionality.

● Flexibility– Ease of adapting to the operating environment.

● Testability– Ease of testing from units of code to the entire

systems

Challenges: Size and Complexity

● Problems– Most real world problems requiring automation are

complex.

● Requirements– Increasing demand for comprehensive automation.

● Solutions– Resulting solutions are large and sophisticated.

Modularisation

● Decomposing large systems into smaller and simpler parts.– Functions and procedures.

– Classes and modules.

● Building the parts.● Assembling the parts to complete the system.

● To achieve desirable code qualities parts should be interchangeable.

Keys to Interchangeability

● Coupling– Interdependence between different software modules.

– Low coupling is preferred as it allows flexibility and extensibility.

– Low coupling via simple and stable interfaces.

● Cohesion– Degree to which elements of a module functionally

belong together.

– Higher cohesion reduces complexity of components.

● Higher cohesion and low coupling enables interchangeability.

Spot Issues ?package app;

import calc.Calculator;

public class App {

public static void main(String[] args) {String line = System.console().readLine();String[] parts = line.split(" ");Calculator.operand1 = Double.parseDouble(parts[0]);Calculator.operator = parts[1].charAt(0);Calculator.operand2 = Double.parseDouble(parts[2]);Calculator.calculate();

}

}

Spot Issues ?package app;

import calc.Calculator;

public class App {

public static void main(String[] args) {String line = System.console().readLine();String[] parts = line.split(" ");Calculator.operand1 = Double.parseDouble(parts[0]);Calculator.operator = parts[1].charAt(0);Calculator.operand2 = Double.parseDouble(parts[2]);Calculator.calculate();

}

}

High Coupling

Spot Issues ?package calc;

public class Calculator {public static double operand1;public static double operand2;public static char operator;

public static void calculate() {double result = Double.NaN;switch(operator) {case '+':

result = operand1 + operand2;break;

case '-':result = operand1 - operand2;break;

case '*':result = operand1 * operand2;break;

case '/':result = operand1 / operand2;break;

default:System.out.printf("Unrecognised operator %c\n", operator);

}

System.out.printf("%f %c %f = %f\n", operand1, operator, operand2, result);

}}

Spot Issuespackage calc;

public class Calculator {public static double operand1;public static double operand2;public static char operator;

public static void calculate() {double result = Double.NaN;switch(operator) {case '+':

result = operand1 + operand2;break;

case '-':result = operand1 - operand2;break;

case '*':result = operand1 * operand2;break;

case '/':result = operand1 / operand2;break;

default:System.out.printf("Unrecognised operator %c\n", operator);

}

System.out.printf("%f %c %f = %f\n", operand1, operator, operand2, result);

}}

High Coupling

Low Cohesion

Simplifying with SOLID

● Types of coupling– Content coupling, Common coupling, Stamp coupling,

Control coupling, Data coupling.

● Types of cohesion– Co-incidental cohesion, Logical cohesion, Temporal

cohesion, Procedural cohesion, Communicational cohesion, Sequential cohesion, Functional cohesion.

● SOLID– Five principles of object oriented programming.

– Aims to deliver extensible and maintainable software.

SOLID

● Single Responsibility Principle (SRP)– A class should have a single responsibility.

● Open Close Principle (OCP)– Open for extension but closed for modification.

● Liskov Substitution Principle (LSP)– Substitution of objects by instances of sub-classes.

● Interface Segregation Principle (ISP)– Many client specific interfaces instead of one big one.

● Dependency Inversion Principle (DIP)– Depend on abstractions instead of implementations.

1Single Responsibility Principle

Single Responsibility Principle

● A class should have one and only one reason to change.– A class should have only one responsibility.

● Promotes high cohesion and low coupling.– High cohesion because of focused classes.

– Low coupling because of dependency on other cohesive classes.

Examplepublic class MeanCalculator {

public double calculate(final String data) {double sum = 0.0;final String[] parsedData = data.split(",");for (String item : parsedData) {

sum += Double.parseDouble(item);}

return sum/parsedData.length;}

}

Examplepublic class MeanCalculator {

public double calculate(final String data) {double sum = 0.0;final String[] parsedData = data.split(",");for (String item : parsedData) {

sum += Double.parseDouble(item);}

return sum/parsedData.length;}

}

Examplepublic class MeanCalculator {

public double calculate(final String data) {double sum = 0.0;final String[] parsedData = data.split(",");for (String item : parsedData) {

sum += Double.parseDouble(item);}

return sum/parsedData.length;}

}

Two reasons to change1. Parsing2. Computation

Example

Functional (Java 8) Implementation

public class MeanCalculator {public double calculate(final List<Double> data) {

double sum = 0.0;for (Double item : data) {

sum += item;}

return sum/data.size();}

}

public class MeanCalculator {public double calculate(final List<Double> data) {

return data.stream().collect(Collectors.

averagingDouble(item -> item));}

}

2Open Close Principle

Open/Close Principle

● Software entities (Classes, methods, modules etc.) should be open for extension but closed for modification.– Changing functionality need not require modifying

existing code.

● Preferred approaches,– Inheritance,

– Composition of abstractions (plugins).

Examplepublic class Parser {

List<Double> parse(final String data) {final String[] parsedCsv = data.split(",");return toDoubleList(parsedCsv);

}

private List<Double> toDoubleList(final String[] data) {final List<Double> list = new ArrayList<>();for(String item: data) {

list.add(Double.parseDouble(item));}

return list;}

}

Exa

mpl

e: E

x ten

ding

a

Clo

sed

Par

ser

public class Parser {public List<Double> parse(final String data, final Format format) {

String[] parsedData = null;switch(format){case CSV:

parsedData = data.split(",");break;

case XML:try {

parsedData = parseXml(data);} catch (Exception exp) {

throw new IllegalArgumentException(exp);}

break;default:

throw new IllegalArgumentException(String.format("Unknown format %s", format));}

return toDoubleList(parsedData);}

/** * Format: <DataList><Item>3</Item><Item>4.5</Item><Item>7.5</Item></DataList> */

private String[] parseXml(final String xmlString) throws Exception {final Document xmlDoc = DocumentBuilderFactory.

newInstance().newDocumentBuilder().

parse(new ByteArrayInputStream(xmlString.getBytes()));

final NodeList nodes = xmlDoc.getElementsByTagName("Item");final String[] textList = new String[nodes.getLength()];for (int i = 0; i < nodes.getLength(); i++) {

textList[i] = nodes.item(i).getTextContent().trim();}

return textList;}

private List<Double> toDoubleList(final String[] data) {final List<Double> list = new ArrayList<>();

for(String item: data) {list.add(Double.parseDouble(item));

}

return list;}

}

public List<Double> parse(final String data, final Format format) {

String[] parsedData = null;switch(format){case CSV:

parsedData = data.split(",");break;

case XML:try {

parsedData = parseXml(data);} catch (Exception exp) {

throw new IllegalArgumentException(exp);}

break;default:

throw new IllegalArgumentException(String.format("Unknown format %s", format));

}

Example: Opening With Template Method

public abstract class Parser {public List<Double> parse(final String data) {

final String[] parsedData = split(data);return toDoubleList(parsedData);

}

protected abstract String[] split(final String data);

private List<Double> toDoubleList(final String[] data) {final List<Double> list = new ArrayList<>();for(String item: data) {

list.add(Double.parseDouble(item));}

return list;}

}

Example: Parser Using Streams

public abstract class Parser {public List<Double> parse(final String data) {

final String[] parsedData = split(data);return Arrays.asList(parsedData).stream().

map(item -> Double.parseDouble(item)).collect(Collectors.toList());

}

protected abstract String[] split(final String data);}

Example: Opening With Template Method

public class CsvParser extends Parser {

@Overrideprotected String[] split(String data) {

return data.split(",");}

}

public class XmlParser extends Parser {

/** * Format: * <DataList><Item>3</Item><Item>4.5</Item><Item>7.5</Item></DataList>

*/@Overrideprotected String[] split(String data) {

String[] textList = null;try {

final Document xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(data.getBytes()));

final NodeList nodes = xmlDoc.getElementsByTagName("Item");textList = new String[nodes.getLength()];for (int i = 0; i < nodes.getLength(); i++) {

textList[i] = nodes.item(i).getTextContent().trim();}

} catch (Exception exp) {throw new IllegalArgumentException(exp);

}

return textList;}

}

3Liskov Substitution Principle

Liskov Substitution Principle

● Named after MIT professor Barbara Liskov.● Functions that use references to base classes

must be able to use objects of the derived class without knowing it. – Derived classes must be substitutable for base class.

● Caution,– Should not alter the behaviour of the application.

– Inheritance hierarchies should not violate domain concepts.

● E.g., a square is a rectangle instead of both are shapes.

public class AvergerApp {public static void main(String[] args) {

final List<String> params = Arrays.asList(args);if ((params.size() != 2) ||

(params.stream().filter(i -> i.contains("=")).count() != 2)) {System.err.println("Parameters: f=<CSV/XML> m=<message>");

} else {final String format = params.stream().

filter(str -> str.contains("f")).findFirst().get().split("=")[1];final String message = params.stream().

filter(str -> str.contains("m")).findFirst().get().split("=")[1];final Parser parser = getParser(format);final List<Double> data = parser.parse(message);final double avg = new MeanCalculator().calculate(data);System.out.printf("Average(%s) = %f", data, avg);

}}

private static Parser getParser(final String format) {Parser parser = null;switch (format) {case "CSV":

parser = new CsvParser();break;

case "XML":parser = new XmlParser();break;

default:throw new IllegalArgumentException("Unknown format " + format));

}return parser;

}}

public class AvergerApp {public static void main(String[] args) {

final List<String> params = Arrays.asList(args);if ((params.size() != 2) ||

(params.stream().filter(i -> i.contains("=")).count() != 2)) {System.err.println("Parameters: f=<CSV/XML> m=<message>");

} else {final String format = params.stream().

filter(str -> str.contains("f")).findFirst().get().split("=")[1];final String message = params.stream().

filter(str -> str.contains("m")).findFirst().get().split("=")[1];final Parser parser = getParser(format);final List<Double> data = parser.parse(message);final double avg = new MeanCalculator().calculate(data);System.out.printf("Average(%s) = %f", data, avg);

}}

private static Parser getParser(final String format) {Parser parser = null;switch (format) {case "CSV":

parser = new CsvParser();break;

case "XML":parser = new XmlParser();break;

default:throw new IllegalArgumentException("Unknown format " + format));

}return parser;

}}

4Interface Segregation Principle

Interface Segragation Principle

● Multiple fine grained client or domain specific interfaces are better than few coarse grained interfaces.

● Consequences– Classes only implement interfaces that are

requirement specific.

– Client classes are exposed only to the functionality that they need.

Examplepublic interface AuthorisationService {

boolean isAuthorised(String userId, Resource resource, Action action);

List<Entitlement> entitlementReport(String userId);List<Group> membershipReport(String userId);boolean entitle(Group group, Entitlement entitlement);boolean entitle(String userId, Entitlement entitlement);boolean add(String userId, Group group);boolean remove(String userId, Group group);boolean remove(String userId, Entitlement entitlement);boolean remove(Group group, Entitlement entitlement);

}

Examplepublic interface AuthorisationService {

boolean isAuthorised(String userId, Resource resource, Action action);

List<Entitlement> entitlementReport(String userId);List<Group> membershipReport(String userId);boolean entitle(Group group, Entitlement entitlement);boolean entitle(String userId, Entitlement entitlement);boolean add(String userId, Group group);boolean remove(String userId, Group group);boolean remove(String userId, Entitlement entitlement);boolean remove(Group group, Entitlement entitlement);

}

Examplepublic interface AuthorisationService {

boolean isAuthorised(String userId, Resource resource, Action action);

List<Entitlement> entitlementReport(String userId);List<Group> membershipReport(String userId);boolean entitle(Group group, Entitlement entitlement);boolean entitle(String userId, Entitlement entitlement);boolean add(String userId, Group group);boolean remove(String userId, Group group);boolean remove(String userId, Entitlement entitlement);boolean remove(Group group, Entitlement entitlement);

}

Example: Segregating Interfaces

public interface EntitlementsModifier {boolean entitle(Group group, Entitlement entitlement);boolean entitle(String userId, Entitlement entitlement);boolean add(String userId, Group group);boolean remove(String userId, Group group);boolean remove(String userId, Entitlement entitlement);boolean remove(Group group, Entitlement entitlement);

}

public interface EntitlementsChecker {boolean isAuthorised(String userId,

Resource resource, Action action);

List<Entitlement> entitlementReport(String userId);List<Group> membershipReport(String userId);

}

5Dependency Inversion Principle

Dependency Inversion Principle

● High-level modules should not depend on low-level modules. – Both should depend on abstractions.

● Abstractions should not depend on details.– Details should depend on abstractions.

● Dependencies are a risk,– Details bind dependants to concrete implementations.

● This limits flexibility and extensibility.

– Abstractions provides independence to dependants.● Dependants are able to select most suitable

implementations.

Example: Averaging Calculator

● A class that uses – An averaging algorithm to process input.

– A parser to parse text input to a collection of doubles.

– Input and output classes.

● Input formats can change.– Binding the calculator to a concrete parser and IO

makes it inflexible.

– Averaging calculator references a parser, an input and an output interface.

– The application specifies the parser and IO needed.

Example: Parserpublic interface IParser {

List<Double> parse(final String data);}

public abstract class Parser implements IParser {public List<Double> parse(final String data) {

final String[] parsedData = split(data);return Arrays.asList(parsedData).stream().

map(item -> Double.parseDouble(item)).collect(Collectors.toList());

}

protected abstract String[] split(final String data);}

Example: Input

public interface IInput {String read();

}

public class FixedInput implements IInput {private final String data;

public FixedInput(final String data) {this.data = data;

}

@Overridepublic String read() {

return data;}

}

Example: Output

public interface IOutput {void write(final List<Double> data,

final double average);}

public class ConsoleOutput implements IOutput {

@Overridepublic void write(final List<Double> data,

final double average) {System.out.printf("Average%s = %f\n",

data, average);}

}

Example: Mean Processorpublic interface IProcessor {

double process(List<Double> data);}

public class MeanProcessor implements IProcessor {public double process(final List<Double> data) {

return data.stream().collect(Collectors.

averagingDouble(item -> item));}

}

Example: Averaging Calculatorpublic class AveragingCalculator {

final private IInput input;final private IOutput output;final private IProcessor algo;final private IParser parser;

public AveragingCalculator(final IProcessor algo, final IParser parser, final IInput input, final IOutput output) {

this.algo = algo;this.parser = parser;this.input = input;this.output = output;

}

public void run() {final String inputData = input.read();final List<Double> data = parser.parse(inputData);final double average = algo.process(data);output.write(data, average);

}}

Example: CSV Averaging Apppublic class CsvAveragingApp {

public static void main(String[] args) {final IInput input = new FixedInput(args[0]);final IOutput output = new ConsoleOutput();final IParser parser = new CsvParser();final IProcessor algo = new MeanProcessor();

final AveragingCalculator calc = new AveragingCalculator(algo,

parser, input, Output);

calc.run();}

}

Example: XML Averaging Apppublic class XmlAveragingApp {

public static void main(String[] args) {final IInput input = new FixedInput(args[0]);final IOutput output = new ConsoleOutput();final IParser parser = new XmlParser();final IProcessor algo = new MeanProcessor();

final AveragingCalculator calc = new AveragingCalculator(algo,

parser, input, output);

calc.run();}

}

Opposite of SOLIDSingle Responsibility Principle

Open Close Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Opposite of SOLIDSingle Responsibility Principle

Open Close Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

STUPID

Opposite of SOLIDSingle Responsibility Principle

Open Close Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Singletons

Tight Coupling

Untestability

Premature Optimisation

Indescriptive Naming

Duplication