JavaTalks: OOD principles

2,358
-1

Published on

Presentation from JavaTalks webinar. Describes OOD Principles: Don't Repeat Yourself Principle (DRY), Single Responsibility Principle (SRP), Open Closed Principle (OCP), Interface Segregation Principle (ISP), Liskov's Substitution Principle (LSP), Inversion of Control Principle (ICP).
Wbinar video is here(ru): http://javatalks.ru/ftopic18502.php

Published in: Technology, Spiritual
1 Comment
1 Like
Statistics
Notes
No Downloads
Views
Total Views
2,358
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
51
Comments
1
Likes
1
Embeds 0
No embeds

No notes for slide

JavaTalks: OOD principles

  1. 1. Башкирцев (Старовер) Станислав ctapobep@javatalks.ru JavaTalks OOD Principles
  2. 2. 2 План презентации • Intro • Don’t Repeat Yourself • Open Closed • Single Responsibility • Interface Segregation • Inversion of Control • Liskov’s Substitution • Q/A
  3. 3. 3 OOD Principles. What’s that? • Recipes, best practices how to write • Clean, easy to understand code • Maintainable (flexible, extendable) code
  4. 4. DRY Don’t Repeat Yourself • Don’t duplicate code
  5. 5. 5 Without DRY st.executeQuery("select user.name, user.password from user where id=?");
  6. 6. If something changes? st.executeQuery("select user.username, user.password from user where id=?");
  7. 7. With DRY st.executeQuery("select user.name, user.password from user where id=?"); public User getUser(Long id) {…}
  8. 8. If something changes? st.executeQuery("select user.username, user.password from user where id=?"); public User getUser(Long id) {…}
  9. 9. DRY Don’t Repeat Yourself • Don’t duplicate code • Names should be clear
  10. 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. 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
  12. 12. DRY Don’t Repeat Yourself • Don’t duplicate code • Names should be clear • Location should be clear
  13. 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. 14. DRY. Pros & Cons Pros: • Changes impact local area • Once written is not repeated • No ”new” error prone solutions Cons: • Amount of classes grows
  15. 15. OCP Open Closed Principle - code should be closed for modification, but open for extension.
  16. 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. 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. 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. 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. 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. 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. 22. OCP. Moral This example shows that changing code that already works is always bad idea. Follow OCP to escape this!
  23. 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; } }
  24. 24. OCP. Example Great! You job satisfies me! But now I want WNBA to be added 
  25. 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. 26. OCP. How it works OCP uses: • Delegation • Inheritance
  27. 27. OCP. Pros & Cons Pros: • Adding new functionality without legacy code being changed Cons: • May complicate system because of amount of classes being grown
  28. 28. SRP Single Responsibility Principle: • Single class should have a single responsibility • Class should have only one reason to change
  29. 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. 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. 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. 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. 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
  34. 34. ISP Interface Segregation Principle – client code shouldn’t be obligated to depend on interfaces it doesn’t use.
  35. 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. 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.
  37. 37. ISP. What these methods do?!
  38. 38. ISP. Fat/Polluted Interfaces interface Person { void goToWork(); void withdrawSalary(); void eat(); } It’s fat It’s POLLUTED
  39. 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. 40. ISP. No extra methods public class PersonImpl implements Person { public void eat() { //some real implementation } } Now we have only needed methods.
  41. 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. 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. 43. ISP. Pros & Cons Pros: • No need to implement unnecessary methods • Client code sees only what it should see Cons: • Adding additional interfaces
  44. 44. IoCP Invertion of Control Principle says: • Code to abstraction, not to implementation • Objects that use other objects, shouldn’t create latter ones
  45. 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. 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. 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. 48. IoCP. Again doesn’t work Your parser doesn’t work with HTML that isn’t a valid XML!
  49. 49. IoCP. New Implementation HtmlParser DomBasedHtmlParser EnhancedHtmlParser
  50. 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. 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. 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. 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. 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. 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. 56. LSP Liskov’s Substitution Principle – derived types must be completely substitutable for their base types. LSP declares how to use inheritance correctly.
  57. 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. 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. 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. 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. 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; ... } }
  62. 62. LSP. Again bugs! Your system always throws errors! It’s a chaos!!
  63. 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. 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. 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. 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. 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. 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
  69. 69. The End Q/A
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×