OCP Review 2.3 - Polymorphism and Design
Polymorphism
- we can “see” only the part of the object we’re interested in
- at runtime, if we call a method, the true one is the method from the instance class, not the reference one
Casting
- no need to cast if upcasting (from a specialzed version to a general one)
- need to cast if downcasting
- use
instanceof
- can only cast inheritance-related classes
String s = "";
Object o = s;
String s1 = (String) o;
Design Principles
a design principle is an established idea or best practice that facilitates software development
Encapsulation
- almost always related to information hiding
- invariants about internal data: property or truth that is maintained after data is modified
Invariants
- For a Person:
- all people have names
- all living people has age between 0…150
class Person {
String name;
int age;
}
JavaBeans
Inheritance vs Composition
- Inheritance: IS-A
- instanceof operator
- Composition: HAS-A
- look at properties
Stupid inheritance
class Person {
String name;
int age;
}
class Car extends Person {
String color;
}
Object composition: Delegation
Design patterns
- creational patterns
- 4 showed in the book
- only Singleton & Inmutable Object in the exam
Singleton
- allow only one instance of that class in memory
class PrintJobsManager {
private int remainingJobs;
private static PrintJobsManager sharedInstance = new PrintJobsManager();
public static synchronized PrintJobsManager sharedPrintJobsManager() {
return sharedInstance;
}
private PrintJobsManager() {}
public synchronized void addPrintJob(String s) {
remainingJobs = remainingJobs + 1;
}
public synchronized int getRemainingJobs() {
return remainingJobs;
}
}
Singleton is effectively final
- we don’t have any constructor now!
class A extends PrintJobsManager {
A() {
super(); // FAILS
}
}
Double-Checked Locking
class PrintJobsManager {
private int remainingJobs;
private static volatile PrintJobsManager sharedInstance;
// Double-Checked Locking
public static PrintJobsManager sharedPrintJobsManager() {
if (sharedInstance == null) {
synchronized (PrintJobsManager.class) {
sharedInstance = new PrintJobsManager();
}
}
return sharedInstance;
}
private PrintJobsManager() {}
public synchronized void addPrintJob(String s) {
remainingJobs = remainingJobs + 1;
}
public synchronized int getRemainingJobs() {
return remainingJobs;
}
}
Volatile?
- Nice discussion here: http://stackoverflow.com/questions/7855700/why-is-volatile-used-in-this-example-of-double-checked-locking
public final class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton(); // gets initialized once, before all threads start
}
}
Inmutable Objects
- use constructor to set all properties
- mark all instance variables as
private
andfinal
- only use getters, not setters
- don’t allow referenced mutable objects to be modified or accessed directly
- prevent methods from being overridden
class City {
private String name;
public City(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Inmutable Objects
- all instance vars must be private
- if not: we can change it
City seville = new City("Seville");
seville.population = 100;
System.out.println(seville.getName());
class City {
private String name;
int population; // should be private to avoid direct access!
public City(String name) {
this(name, 0);
}
public City(String name, int population) {
this.name = name;
this.population = population;
}
public String getName() {
return name;
}
}
Inmutable Objects
- better encapsulated version
class City {
private final String name;
private final int population;
public City(String name) {
this(name, 0); // chained constructors
}
public City(String name, int population) {
this.name = name;
this.population = population;
}
public String getName() {
return name;
}
}
Inmutable Objects: adding referenced objects
List<String> streets = new LinkedList<>();
streets.add("Torricelli");
streets.add("Sierpes");
streets.add("Constitución");
City seville = new City("Seville", 800_000, streets);
seville.getStreets().forEach(System.out::println);
seville.getStreets().add("Wall Street"); // we can mutate the list!
seville.getStreets().forEach(System.out::println);
streets.clear(); // even worse, we have stored the reference, so we can change from outside
seville.getStreets().forEach(System.out::println);
class City {
private final String name;
private final int population;
private final List<String> streets;
public City(String name) {
this(name, 0); // chained constructors
}
public City(String name, int population) {
this(name, population, null);
}
public City(String name, int population, List<String> streets) {
this.name = name;
this.population = population;
this.streets = streets;
}
public String getName() {
return name;
}
public int getPopulation() {
return population;
}
public List<String> getStreets() {
return streets;
}
}
Solution
class City {
private final String name;
private final int population;
private final List<String> streets;
public City(String name) {
this(name, 0); // chained constructors
}
public City(String name, int population) {
this(name, population, null);
}
public City(String name, int population, List<String> streets) {
this.name = name;
this.population = population;
this.streets = Collections.unmodifiableList(streets);
}
public String getName() {
return name;
}
public int getPopulation() {
return population;
}
public List<String> getStreets() {
return streets;
}
}
Builder pattern
class Person {
private String name;
private String address;
private int age;
public static class Builder {
private String name;
private String address;
private int age;
public Person build() {
return new Person(name, address, age);
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
}
private Person(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public String getName() { return name; }
public String getAddress() { return address; }
public int getAge() { return age; }
}