10. Not clear names
public class Utils {
public static Connection createConnection(String... params){...}
public static Object[] getSortedArray(Object[] array){...}
}
1. No one would guess to look sorting method in this class.
2. Newbie always would write his own implementation.
11. Correct DRY
public class ArrayUtils {
public static Object[] getSortedArray(Object[] array) {…}
}
public class DatabaseUtils {
public static Connection createConnection(String... params) {...}
}
clear, well-defined class names
13. Not clear location
package ru.jt.transformer.csv;
public class ArrayUtils {
public static Object[] getSortedArray(Object[] array) {...}
}
No one would look for array utilities in such package
14. DRY. Pros & Cons
Pros:
• Changes impact local area
• Once written is not repeated
• No ”new” error prone solutions
Cons:
• Amount of classes grows
16. OCP. Example
I want my clients to be able to
see results of past games. Let’s
keep up with NHL & NBA games..
1. American customer
2. His thoughts
3. His ill imagination
17. OCP. Example
public class SportInfoParser implements SportInfoParser {
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfo sportInfo = null;
if ("nba".equals(sportInfoArray[0])) {
NbaSportInfo nbaSportInfo = new NbaSportInfo();
Map<Long, Integer> scores = new HashMap<Long, Integer>();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
nbaSportInfo.setScores(scores);
sportInfo = nbaSportInfo;
} else if ("nhl".equals(sportInfoArray[0])) {
NhlSportInfo nhlSportInfo = new NhlSportInfo();
nhlSportInfo.setSlapShotCount(1);
}
return sportInfo;
}
}
Base class
Creates specific
objects according to...
18. OCP. Example
Great! A lot of new clients, a lot of
money from ads. But.. Would be
great if my cliends would be able
to get info about MLB games too!
19. OCP. Example
public class SportInfoParser implements SportInfoParser {
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfo sportInfo = null;
if ("nba".equalsIgnoreCase(sportInfoArray[0])) {
NbaSportInfo nbaSportInfo = new NbaSportInfo();
Map<Long, Integer> scores = new HashMap<Long, Integer>();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
nbaSportInfo.setScores(scores);
sportInfo = nbaSportInfo;
} else if ("nhl".equalsIgnoreCase(sportInfoArray[0])) {
NhlSportInfo nhlSportInfo = new NhlSportInfo();
nhlSportInfo.setSlapShotCount(1);
} else if(sportInfoArray[0].equalsIgnoreCase("mlb")){
MlbSportInfo mlbSportInfo = new MlbSportInfo();
mlbSportInfo.setHits(Integer.parseInt(sportInfoArray[1]));
mlbSportInfo.setRuns(Integer.parseInt(sportInfoArray[2]));
}
return sportInfo;
}
}
New league
was added
We are changing
already working class!
20. OCP. Example
Why my clients see errors
on every page?!! I pay you
for work, not for errors! I
loose my clients, make the
program work right!
21. OCP. Example
public class SportInfoParser implements SportInfoParser {
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfo sportInfo = null;
if ("nba".equalsIgnoreCase(sportInfoArray[0])) {
NbaSportInfo nbaSportInfo = new NbaSportInfo();
Map<Long, Integer> scores = new HashMap<Long, Integer>();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
nbaSportInfo.setScores(scores);
sportInfo = nbaSportInfo;
} else if ("nhl".equalsIgnoreCase(sportInfoArray[0])) {
NhlSportInfo nhlSportInfo = new NhlSportInfo();
nhlSportInfo.setSlapShotCount(1);
} else if(sportInfoArray[0].equalsIgnoreCase("mlb")){
MlbSportInfo mlbSportInfo = new MlbSportInfo();
mlbSportInfo.setHits(Integer.parseInt(sportInfoArray[1]));
mlbSportInfo.setRuns(Integer.parseInt(sportInfoArray[2]));
}
return sportInfo;
}
}
Element of array is
compared with league name
– NPE is possible!
22. OCP. Moral
This example shows that changing code that
already works is always bad idea.
Follow OCP to escape this!
23. OCP. Example
public class SportInfoParser implements SportInfoParser {
private Map<String, SportInfoBuilder> builders;
public SportInfoParserEnhanced(Map<String, SportInfoBuilder> builders) {
this.builders = builders;
}
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfoBuilder builder = builders.get(sportInfoArray[0]);
if(builder != null){
return builder.build(sportInfoArray);
}
return null;
}
}
MlbSportInfoBuilder
NbaSportInfoBuilder NhlSportInfoBuilder
public class NbaSportInfoBuilder implements SportInfoBuilder {
public SportInfo build(String[] sportInfoArray) {
NbaSportInfo nbaSportInfo = new NbaSportInfo();
Map<Long, Integer> scores = new HashMap<Long, Integer>();
scores.put(…, …);
nbaSportInfo.setScores(scores);
return nbaSportInfo;
}
}
25. OCP. Example
public class SportInfoParser implements SportInfoParser {
private Map<String, SportInfoBuilder> builders;
public SportInfoParserEnhanced(Map<String, SportInfoBuilder> builders) {
this.builders = builders;
}
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfoBuilder builder = builders.get(sportInfoArray[0]);
if(builder != null){
return builder.build(sportInfoArray);
}
return null;
}
}
MlbSportInfoBuilder
NbaSportInfoBuilder NhlSportInfoBuilder
WnbaSportInfoBuilder
New league was added without
changing single line of code!
26. OCP. How it works
OCP uses:
• Delegation
• Inheritance
27. OCP. Pros & Cons
Pros:
• Adding new functionality without legacy code
being changed
Cons:
• May complicate system because of amount of
classes being grown
29. SRP. Random thoughts
public class CurrencyConverter {
public BigDecimal convert(Currency from, Currency to, BigDecimal amount) {
// gets connection to some online service and asks it to convert currency
// parses the answer and returns results
}
public BigDecimal getInflationIndex(Currency currency, Date from, Date to) {
// gets connection to some online service to get data about
// currency inflation for specified period
}
}
Hm.. Strange that inflation is
counted in CurrencyConverter..
Hm.. What if format of
currency service changes?
What if the format of inflation
service changes?
We’ll have to change this
class in both cases!
It’s not intuitive!
It’s overloaded!
We have to do something!
30. SRP. Separate Responsibilities
public class CurrencyConverter {
public BigDecimal convert(Currency from, Currency to, BigDecimal amount) {
// gets connection to some online service and asks it to convert currency
// parses the answer and returns results
}
}
public class InflationIndexCounter {
public BigDecimal getInflationIndex(Currency currency, Date from, Date to) {
// gets connection to some online service to get data about
// currency inflation for specified period
}
}
Hm.. What if format of
currency service changes? We
change CurrencyConverter!
Hm.. What if format of
inflation service changes? We
change InflationIndexCounter!
31. SRP & DRY
Again two responsibilities:
Authentication & getting user
from database
public class UserAuthenticator {
public boolean authenticate(String username, String password){
User user = getUser(username);
return user.getPassword().equals(password);
}
private User getUser(String username){
st.executeQuery("select user.name, user.password from user where id=?");
// something's here
return user;
}
}
DRY violation!
32. SRP. Delegating responsibilities
public class UserAuthenticator {
private UserDetailsService userDetailsService;
public UserAuthenticator(UserDetailsService service) {
userDetailsService = service;
}
public boolean authenticate(String username, String password){
User user = userDetailsService.getUser(username);
return user.getPassword().equals(password);
}
}
Now we don’t work
directly with database!
If we would want to use
ORM, UserAuthenticator
won’t change!
33. SRP. Pros & Cons
Pros:
• Helps to follow DRY
• Lowers chances to change single class
• Class names correspond to what they do
Cons:
• May complecate system by adding too much
new classes
35. ISP. Example
interface Person {
void goToWork();
void withdrawSalary();
void eat();
}
Base interface of
the person.
All these methods are
useful for current
implementation of
person.
36. ISP. Extra methods
interface Person {
void goToWork();
void withdrawSalary();
void eat();
}
But we’re writting new
module that considers a
person only as a human
being. So we need only
one method eat()
public class PersonImpl implements Person {
public void goToWork() {
throw new UnsupportedOperationException();
}
public void withdrawSalary() {
throw new UnsupportedOperationException();
}
public void eat() {
//some real implementation
}
}
So our new
implementation has two
extra methods.
39. ISP. Interface Separating
public interface Person {
void eat();
}
public interface Worker {
void goToWork();
void withdrawSalary();
}
We separated Person
into Person & Worker.
Two conceptually
different interfaces.
40. ISP. No extra methods
public class PersonImpl implements Person {
public void eat() {
//some real implementation
}
}
Now we have only
needed methods.
41. ISP. Legacy code
What if we have ready
implementation, but we
don’t want to use fat
interface?
public class FatPersonImpl implements FatPerson {
public void goToWork() {
//some real implementation
}
public void withdrawSalary() {
//some real implementation
}
public void eat() {
//some real implementation
}
}
42. ISP. Use Adapters
public class PersonAdapter implements Person {
private FatPerson fatPerson;
public PersonAdapter(FatPerson fatPerson) {
this.fatPerson = fatPerson;
}
public void eat() {
fatPerson.eat();
}
}
Thin interface
Fat interface
43. ISP. Pros & Cons
Pros:
• No need to implement unnecessary methods
• Client code sees only what it should see
Cons:
• Adding additional interfaces
44. IoCP
Invertion of Control Principle says:
• Code to abstraction, not to implementation
• Objects that use other objects, shouldn’t
create latter ones
45. IoCP. Coding to implementation
public interface HtmlParser {
HtmlDocument parseUrl(String url);
}
public class Crawler {
public void saveHtmlDocument() {
DomBasedHtmlParser parser = new DomBasedHtmlParser();
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void save(HtmlDocument htmlDocument, String pageName) {
// logic of saving
}
}
public class DomBasedHtmlParser implements HtmlParser {
public HtmlDocument parseUrl(String url) {
// getting html page as stream
//parsing it with DOM parser
//creating HtmlDocument
return htmlDocuments;
}
}
46. IoCP. How to test Crawler?
public class Crawler {
public void saveHtmlDocument() {
DomBasedHtmlParser parser = new DomBasedHtmlParser();
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void save(HtmlDocument htmlDocument, String pageName) {
// logic of saving
}
}
It’s impossible to write
unit test for Crawler,
because you cannot
mock parser.
47. IoCP. Let’s inject
public class Crawler {
private DomBasedHtmlParser parser;
public Crawler(DomBasedHtmlParser parser) {
this.parser = parser;
}
public void saveHtmlDocument() {
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void save(HtmlDocument htmlDocument, String pageName) {
// logic of saving
}
}
Crawler crawler = new Crawler(someMockParser);
Now you can specify
parser through
constructor. You can
inject dummy object
while testing.
48. IoCP. Again doesn’t work
Your parser doesn’t
work with HTML that
isn’t a valid XML!
50. IoCP. But how do we replace?
public class Crawler {
private DomBasedHtmlParser parser;
public Crawler(DomBasedHtmlParser parser) {
this.parser = parser;
}
public void saveHtmlDocument() {
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void save(HtmlDocument htmlDocument, String pageName) {
// logic of saving
}
}We cannot specify
another implementaion!
51. IoCP. Let’s code to interface
public class Crawler {
private HtmlParser parser;
public Crawler(HtmlParser parser) {
this.parser = parser;
}
public void saveHtmlDocument() {
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void save(HtmlDocument htmlDocument, String pageName) {
// logic of saving
}
}
Now we use interface, so
we can specify enhanced
implementation of
parser.
52. IoCP. Another look
How do we inject objects if we work on
framework/library? We cannot use IoC
Containers, our clients don’t allow us extra
dependencies, they don’t want to depend on
Spring or Guice. We need leightweight
decision.
53. IoCP. Let’s use Factories
public class Crawler{
private HtmlParser parser = ParserFactory.getHtmlParser();
public void saveHtmlDocument() {
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
}
Let’s use Factories! Hm.. But we cannot
write unit tests again!
54. IoCP. Let’s mix
public class Crawler{
private HtmlParser parser = ParserFactory.getHtmlParser();
public void saveHtmlDocument() {
HtmlDocument document = parser.parseUrl("http://javatalks.ru");
save(document, "jt-index");
}
public void setParser(HtmlParser parser) {
this.parser = parser;
}
}
We have both: setter and
factory in the same class.
Now we have default
implementation and
possibility to change
default behavior.
55. IoCP. Pros & Cons
Pros:
• Classes don’t depend on concrete
implementation
• Allows easily change implementation
• Allows write good unit tests
Cons:
• Creating additional interfaces
• Creating Factories/depending on IoC
containers
56. LSP
Liskov’s Substitution Principle – derived types
must be completely substitutable for their
base types.
LSP declares how to use inheritance correctly.
57. LSP. java.util.List
List list = new ArrayList(); List list = new LinkedList();
list.get(1);
Would be strange if these
implementation would do
different things here
58. LSP. Emu doesn’t fly!
class Bird extends Animal {
@Override //walk is overriden from Animal
public void walk() {...}
@Override //makeOffspring() is overriden from Animal
public void makeOffspring() {...};
//was added
public void fly() {...}
}
class Emu extend Bird {
public void makeOffspring() {...}
}
But emu doesn’t fly!
59. LSP. Emu indeed doesn’t fly
class Bird extends Animal {
@Override //walk is overriden from Animal
public void walk() {...}
@Override //makeOffspring() is overriden from Animal
public void makeOffspring() {...}
}
class FlyingBird extends Bird {
public void fly() {...}
}
class Emu extends Bird {
@Override
public void makeOffspring(){..}
}
Simply birds.
Flying birds.
Emu is simply a bird.
It doesn’t have extra
methods.
60. LSP. Array example
interface ArraySorter {
Object[] parse(Object []args);
}
class DefaultArraySorter implements ArraySorter {
public Object[] sort(Object []array){
Object[] result = array.clone();
...
}
}
Default implementation.
It’s a temporal class that
uses unefficient approach.
But it does it’s work
correctly.
61. LSP. Array example
interface ArraySorter {
Object[] parse(Object []args);
}
At last we wrote enhanced
implementation that’s
really efficient.
class QuickArraySorter implements ArraySorter {
public Object[] sort(Object []array){
Object[] result = array;
...
}
}
63. LSP. Array example
class QuickArraySorter implements ArraySorter {
public Object[] sort(Object []array){
Object[] result = array;
...
}
}
It sorts the original array!
We have problems with
synchronization.
Implementation does its
work, but it has side
effects. It doesn’t satisfy
interface contract!
We cannot simply replace
one implementation with
another one, because they
differ!
We should correct its
behaviour to copy original
array and work with its copy.
64. LSP & equals()
public class Point {
private int x;
private int y;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
if (x != point.x) return false;
if (y != point.y) return false;
return true;
}
}
public class ColoredPoint extends Point {
private int color;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ColoredPoint)) return false;
if (!super.equals(o)) return false;
ColoredPoint that = (ColoredPoint) o;
if (color != that.color) return false;
return true;
}
}
Colored point extends
simple point and adds new
field – color.
But it works only with
ColoredPoint!
65. LSP & equals
Point point = new Point(1, 1);
ColoredPoint coloredPoint = new ColoredPoint(1, 1, 1);
System.out.println(point.equals(coloredPoint));
System.out.println(coloredPoint.equals(point));
This will print:
true
false
This violates equals()
contract!
There is no correct
dicision in this situation!
66. LSP & equals()
public class ColoredPoint {
private Point point;
private int color;
}
The only correct way here
is to use delegation
instead of inheritance.
67. LSP & exceptions
List list = new OurSuperPuperImplementation();
list.iterator().next();
What if this method in
our implementation
threw IOException?
How would we know
about that? We work
with interface List, not
with implementation!
That’s why Java
doesn’t allow us to
throw any checked
exception that are not
declared in base class.
68. LSP. Pros & Cons
Pros:
• Allows not to think about concrete
implementation, but code to abstraction
• Unambiguously defines contract all
implementers should follow
• Allows to interchange implementation
correctly, without side effects