SOLID Principles
Functional Programming
***Acronym for five fundamental principles in Object Oriented programming coined in early 2000s by Michael Feathers and Robert C. Martin (aka Uncle Bob) ***
A class should have only one reason to change.
+++
We have a Blog Post class representing a web post composed by a title, author and a content.
+++
Try to print out its content as a string to the console.
+++
What if we want to print the post's content as a json string to the console?
Hint: Separation of business logic and data presentation is important!
+++
Separate these two responsibilities into separate classes is so important because every responsibility is an axis of change.
+++
If a class has more than one responsibility, then there will be more than one reason for it to change.
+++
If the two responsibilities don't change at different times, then there is no need to separate them.
Separating them would lead to Needless Complexity, indeed.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
+++
We've defined a Rectangle class with its width and height.
+++
Build an application calculating the rectangles' collection total area.
Hint: keep in mind the SRP!
+++
Extend the application calculating circles' area as well.
Hint: abstraction is the key +++
When a single change to a program results in a cascade of changes to dependent modules, that program becomes fragile, rigid, unpredictable and unreusable.
+++
We should strive to write code that doesn't have to be changed every time the requirements change.
+++
You should extend modules' behaviour adding new code, avoiding changes to the old working code.
An object of type T may be substituted with any object of a subtype S
Child classes should never break the parent class type definitions
+++
+++
What if we want to resize a Square too?
+++
Think about the relation between LSP and OCP
No client should be forced to depend on methods it does not use. Instead of one fat interface many small interfaces are prefered based on groups of methods, each one serving one submodule.
+++
Hint: have you ever seen an eating robot?
One should depend upon abstractions, not concretions
+++
import com.tidyjava.weather.api1.WeatherApi1;
import com.tidyjava.weather.api2.WeatherApi2;
public class WeatherAggregator {
private WeatherApi1 weatherApi1 = new WeatherApi1();
private WeatherApi2 weatherApi2 = new WeatherApi2();
public double getTemperature() {
return (weatherApi1.getTemperatureCelcius() + toCelcius(weatherApi2.getTemperatureFahrenheit())) / 2;
}
private double toCelcius(double temperatureFahrenheit) {
return (temperatureFahrenheit - 32) / 1.8f;
}
}
+++
Do you see any problems?
+++
Solving the creation problem
public WeatherAggregator(WeatherApi1 weatherApi1, WeatherApi2 weatherApi2) {
this.weatherApi1 = weatherApi1;
this.weatherApi2 = weatherApi2;
}
+++
Inverting the control... Abstract over concrete implementation
+++
public interface WeatherSource {
float getTemperatureCelcius();
}
+++
public double getTemperature() {
return weatherSources
.stream()
.mapToDouble(WeatherSource::getTemperatureCelcius)
.average()
.getAsDouble();
}
+++
Inversion of control is sometimes facetiously referred to as the "Hollywood Principle: Don't call us, we'll call you".
Dependency inversion is critically important for the construction of code that is resilient to change. And, since the abstractions and details are all isolated from each other, the code is much easier to test and maintain.
We'd like to extract some useful information from gitHub.com about all the repositories belonging to ekmett user.
The goal is to create two different reports:
+++
Plain text report containing the most starred repo's summary.
Json file report containing all the repos belonging to ekmett.
+++
You can query for repos at https://api.gitHub.com/users/ekmett/repos
A response example is available in project's resources folder.
+++ Plain text file
This reports must be in the following form:
{user} owns
${num} repos. His most starred one is $ {repo_name} with${num_stars} stars. Here is a brief description: $ {description}.
+++ Json file report must be in the following format
{
"user": "ekmett",
"repos": [{
"name": "compiler",
"stargazers": 10,
"description": "description",
"commits": ["commit message1","commit message2"]
}]
}
Commits endpoint: https://api.gitHub.com/repos/ekmett/${repo}/commits
+++
Let's starting from WHAT IS NOT...
Programs are composed by statements that "do" something:
Do this and then Do that
+++
int y = 1;
...
public void do(int x) {
y++;
System.out.println("doing something with "+x);
}
...
do(y);
++y;
This imply an initial state, transitions, and an end state.
Functional programs are composed by expressions "evaluating" something
public int do(int x) {
return ++x;
}
int y = do(1);
int z = do(y);
+++
Major difference between imperative and functional programming: No (Observable) Side Effects
- No mutation of variables
- No printing to the console or to any device
- No writing to files, databases, networks, or whatever
- No exception throwing
+++
In other words, programs are composed by functions having no observable effects on the program's execution other than compute results given its inputs.
public static int add(int a, int b) {
while (b > 0) {
a++;
b--;
}
return a;
}
+++
- returns a value and do nothing else.
- don't mutate any element of the outside world
- don’t mutate their arguments
- don’t explode if an error occurs
- can return an exception or anything else
- must return it, not throw it, nor log it, nor print it
+++
Set<Integer> s = new HashSet<>();
public static boolean foo(int x) {
return s.contains(Integer.valueOf(x))
}
s.add(Integer.valueOf(1));
foo(1); //true
s.remove(Integer.valueOf(1))
foo(1) //false
int x = add(1, 0)
int y = add(x, 1)
int z = add(x, y)
int w = 3
assert z == w //true
Expressions can be replaced by theirs results without changing the meaning of the program if Referential Transparency Principle hold.
In practise obviously, you can’t.
In the end, also functional programs have to have an observable effect, such as displaying the result on a screen, or sending it over a network.
+++
This interaction with the outside world won’t occur in the middle of a computation, but only when you finish the computation.
In other words, side effects will be delayed and applied separately.
public static int div(int a, int b) {
return a / b;
}
+++
Hint: wrapping effects is the key...