COMP2511

Tutorial 4

 

 

Today

  • Assignment tips
  • Design by Contract
  • Design Principles
  • Streams and lambdas

Assignment Tips

Material from Alvin Cherk

Assignment Tips

Use .equal() instead of == for comparison between objects

public class EqualsExample {
    public static void main(String[] args) {
        String s1 = "Hello"; // string literal
        String s2 = new String("Hello"); // string class
        System.out.println(s1 == s2); // false, as its comparing the addresses of s1 and s2
        System.out.println(s1.equals(s2)); // true, compares the values at the addresses
        //                    ^ do this
    }
}

Correctness + style issue

Assignment Tips

Avoid magic numbers
Assign them to static final variables (readability - style issue)

public class MagicNumbers {
    public void doSomething() {
        if (files.size() == 5) {} // what does 5 mean here?
    }
}
public class MagicNumbers {
    private final static int MAX_FILES = 5; 
    // declare constant with good name ^
    public void doSomething() {
        if (files.size() == MagicNumbers.MAX_FILES) {};
                                         // ^ use constant here
    }
}

Assignment Tips

Use instanceof for type comparison (open/close - design issue)

public abstract class Shape {
    public abstract String getType();
}

public class Rectangle extends Shape {
    public void doSomething(List<Shape> shapes) {
        for (Shape s : shapes) {
            if (s.getClass().equals(Rectangle.class)) { 	// v1: kinda ok
                // do something only on exactly the Rectangle class
            }
            if (s.getType().equals("Rectangle")) { 		    // v2: very bad
               // do something on all rectangles
            }
            if (s instanceof Rectangle) { 					// v3: best choice here
                // do something on all rectangles
            }
        }
    }
}

public class Square extends Rectangle { ... }

Assignment Tips

Polymorphism is preferred over type-checking when performing a specific action (open closed principle - design issue)

// Example: What to do
public abstract class Shape {
    public abstract String getType();
    public abstract double area();
    // ^ declare method in superclass
}

public class Rectangle extends Shape {
    public double area() {
        // calculate the area this way
    }
}

public class Square extends Rectangle {
    public double area() {
        // calculate the area a different way
    }
    public static void main(String[] args) {
        List<Shape> shapes = new ArrayList<>();
        shapes.add(new Rectangle());
        shapes.add(new Square());
        for (Shape s : shapes) {
            s.area(); // no more type checking
        }
    }
}

Try to achieve this, but we know it's not always possible

Design By Contract

Design By Contract

What is it?

A contract must specifiy the following 3 conditions:

  1. Pre-condition - what inputs does it expect?
  2. Post-condition - what outputs does it guarantee?
  3. Invariant - what does not change?

Software design approach that specifies how a software component should interact with other software components

Design By Contract

What does it really mean?

If someone attempts to use my system without following my contracts, then the system doesn't need to ensure correctness. 

i.e. system is allowed to crash

  1. Unlike COMP1511, invalid input no longer needs to be checked if specified in the contracts
  2. You can safely assume labs and assignments will not test for invalid input (even if you don't see any contracts in the code)

Implications for this course

Design By Contract

public class Calculator {
    public static Double add(Double a, Double b) {
        return a + b;
    }

    public static Double divide(Double a, Double b) {
        return a / b;
    }

    public static Double sin(Double angle) {
        return Math.sin(angle);
    }

    public static Double tan(Double angle) {
        return Math.tan(angle);
    }
}

Exercise: specify a contract for every method in the Calculator class

Design By Contract

/**
* @preconditions: angle != null
* @postconditions: sin(angle)
* @invariant: a, b will remain unchanged
*/
public static Double sin(Double angle) {
	return Math.sin(angle);
}
/**
* @preconditions: angle != null, angle != Math.PI / 2
* @postconditions: tan(angle)
* @invariant: a, b will remain unchanged
*/
public static Double tan(Double angle) {
	return Math.tan(angle);
}
/**
* @preconditions: a, b != null
* @postconditions: a + b
* @invariant: a, b will remain unchanged
*/
public static Double add(Double a, Double b) {
	return a + b;
}
/**
* @preconditions: a, b != null, b != 0
* @postconditions: a / b
* @invariant: a, b will remain unchanged
*/
public static Double divide(Double a, Double b) {
	return a / b;
}

add

divide

sin

tan

Law of Demeter

"Principle of least knowledge"

Law of Demeter

What is it?

Design principle that states an object should have minimal knowledge about other objects

It aims to achieve loose coupling

Purpose

  • In a loosely coupled system, objects are less dependent on the internals of other objects (fewer contact points)
  • Implementation of an object can change without needing to modify the caller

Law of Demeter

How to uphold the Law of Demeter?

A method m of an object o should only call methods from:

  1. The object o itself
  2. Objects that are member variables of o
  3. Objects passed in as parameters to m
  4. Objects instantiated within m
a.getB().getC()

*Sometimes it is unavoidable

Avoid situations like below*

Code Review

Law of Demeter

Code Review

In the unsw.training package there is some code for a training system

  • Every employee must attend a whole day training seminar run by a qualified trainer
  • Each trainer is running multiple seminars with no more than 10 attendees per seminar

 

 

The bookTraining method in the TrainingSystem class violates the Law of Demeter. How?

Code Review

  1. How does the bookTraining method violate the Law
    of Demeter?
    • The Seminar class is a "stranger" of the TrainingSystem class
    • getStart() is a method of the Seminar class and it is invoked
      within the TrainingSystem class
  2. What are some other undesirable properties about the
    overall system?
    • The TrainingSystem has low cohesion as it depends on
      mostly external classes
    • The Seminar class is poorly encapsulated. It relies on the TrainingSystem class to enforce its contract (i.e. capacity)

 

/**
 * An in person all day seminar with a maximum of 10 attendees.
 */
public class Seminar {
    private LocalDate start;
    private List<String> attendees;
    public LocalDate getStart() {
        return start;
    }

    /**
     * Try to book this seminar if it occurs on one of the available days and
     * isn't already full
     * @param employee
     * @param availability
     * @return The date of the seminar if booking was successful, null otherwise
     */
    public LocalDate book(String employee, List<LocalDate> availability) {
        for (LocalDate available : availability) {
            if (start.equals(available) &&
                    attendees.size() < 10) {
                attendees.add(employee);
                return available;
            }
        }
        return null;
    }
}
public class TrainingSystem {
    public List<Trainer> trainers;
    /**
     * Try to booking training for an employee, given their availability.
     *
     * @param employee
     * @param availability
     * @return The date of their seminar if booking was successful, null there
     * are no empty slots in seminars on the day they are available.
     */
    public LocalDate bookTraining(String employee, List<LocalDate> availability) {
        for (Trainer trainer : trainers) {
            LocalDate booked = trainer.book(employee, availability);
            if (booked != null)
                return booked;
        }
        return null;
    }
}
/**
 * A trainer that runs in person seminars.
 */
public class Trainer {
    private String name;
    private String room;
    private List<Seminar> seminars;

    public List<Seminar> getSeminars() {
        return seminars;
    }

    /**
     * Try to book one of this trainer's seminars.
     * @param employee
     * @param availability
     * @return The date of the seminar if booking was successful, null if the
     * trainer has no free slots in seminars on the available days.
     */
    public LocalDate book(String employee, List<LocalDate> availability) {
        for (Seminar seminar : seminars) {
            LocalDate booked = seminar.book(employee, availability);
            if (booked != null)
                return booked;
        }
        return null;
    }
}

Liskov Substitution Principle

Liskov Substitution Principle

LSP is violated when either

  • Preconditions are strengthened in the subclass
  • Postconditions are weakened in the subclass 
  • Invariants are not held in the subclass

What is it?

Design principle that states objects of a superclass should be replaceable with objects of its subclasses

Formal definition

 

Let Φ(x) be a provable property of objects x of class T. Then, Φ(y) should be true for objects y of class S where S is a subclass of T

Liskov Substitution Principle

Common signs of potential LSP violations

  1. Subclass throwing an exception in methods inherited from the superclass
  2. Subclass leaving methods inherited from the superclass blank (unimplemented)
  3. When the inheritance hierarchy doesn't appear to make sense

Liskov Substitution Principle

Solve the problem by minimising / removing inheritance

  • Delegation - delegate the functionality to another class
  • Composition - reuse behaviour using one or more classes with composition

Design suggestion: favour composition over inheritance

Violations of LSP are typically caused by inheritance

  • Inheritance achieves the goal of code re-use
  • But potential subclasses are now locked in even if some properties and functions of the superclass don't make sense

Liskov Substitution Principle

/**
 * An online seminar is a video that can be viewed at any time by employees. A
 * record is kept of which employees have watched the seminar.
 */
public class OnlineSeminar extends Seminar {
    private String videoURL;
    private List<String> watched;
}
/**
 * Precondition: each seminar can have a maximum of 10 attendees.
 * Postcondition: an attendee is added to the seminar.
 */
public class Seminar {
    private LocalDate start;
    private List<String> attendees;

    public LocalDate getStart() {
        return start;
    }

    public List<String> getAttendees() {
        return attendees;
    }
}

How does OnlineSeminar violate LSP?

Suppose the capacity precondition is currently true, adding a new attendee to an OnlineSeminar will not satisfy the postcondition because presumably the attendee is added to a watched list as opposed to the attendees list from the superclass

Streams and Lambdas

Streams and Lambdas

What it is?

Java's way of enabling functional programming

Common stream methods:

forEach, filter, map, reduce

 

 

Why bother?

  • Eliminate common bugs (i.e. using forEach makes index out of bounds impossible)
  • Often lead to more compact and readable code

Aside: Optional<type>

If a variable can be null, use Optional<>.

Try to never set variables to be null because dealing with NullPointerExceptions is never fun :(

public class OptionalExample {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
        Optional<String> res = strings.stream().filter(x -> x.equals("1")).findAny();

        if (res.isPresent()) {
            System.out.println(res.get());
        } else {
            // It doesn't exist
            // Handle error?
        }
    }
}

.findAny() actually returns Optional<type>

Material from Alvin Cherk

Code Demo

Streams

Code Demo

Convert the following to use streams

package stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) {
    	// Exercise 1
        List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
        for (String string : strings) {
            System.out.println(string);
        }
	
    	// Exercise 2
        List<Integer> ints = new ArrayList<Integer>();
        for (String string : strings) {
            ints.add(Integer.parseInt(string));
        }
        System.out.println(ints);
    }

}
package stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) {
    	// Exercise 1 Solution
        List<String> strings = new ArrayList<String>(Arrays.asList(new String[] { "1", "2", "3", "4", "5" }));
        strings.stream().forEach(x -> System.out.println(x));
        strings.stream().forEach(x -> {
            System.out.println(x);
        });
		
        // Exercise 2 Solution
        List<Integer> parsedStrings = strings.stream().map(x -> Integer.parseInt(x)).collect(Collectors.toList());
        strings2.stream().map(x -> Integer.parseInt(x)).forEach(x -> System.out.println(x));
    }
}

Code Demo

package stream;

public abstract class GenericSatellite {
    private double linearSpeed; // in KM/minute
    private String name; // name of satellite

    public GenericSatellite(double linearSpeed, String name) {
        this.linearSpeed = linearSpeed;
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public double getLinearSpeed() {
        return this.linearSpeed;
    }
}
package stream;

public class FastSatellite extends GenericSatellite {
    private static final double speed = 100.0;

    public FastSatellite(String name) {
        super(speed, name);
    }
}
package stream;

public class SlowSatellite extends GenericSatellite {
    private static final double speed = 50.0;

    public SlowSatellite(String name) {
        super(SlowSatellite.speed, name);
    }
}
package stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Controller {
    private List<GenericSatellite> satellites = new ArrayList<>();

    public void addSatellite(GenericSatellite satelliteToAdd) {
        this.satellites.add(satelliteToAdd);
    }

    public List<GenericSatellite> getSatelliteList() {
        return this.satellites;
    }
    
    public static void main(String[] args) { ... }
}

Further Streams Example

public static void main(String[] args) {
    Controller c = new Controller();
    c.addSatellite(new FastSatellite("Fast Satellite 1"));
    c.addSatellite(new FastSatellite("Fast Satellite 2"));
    c.addSatellite(new SlowSatellite("Slow Satellite 1"));

    List<GenericSatellite> allSatellites = c.getSatelliteList();

    // What if I just want FastSatellites only?
    // Method 1, normal for-in/for-each
    List<GenericSatellite> fast1 = new ArrayList<>();
    for (GenericSatellite x : allSatellites) {
        if (x instanceof FastSatellite) {
            fast1.add(x);
        }
    }

    // Method 2, streams
    List<GenericSatellite> fast2 = allSatellites
    	.stream()
        .filter(x -> x instanceof FastSatellite)
        .collect(Collectors.toList());

    // Same, but I typecast at the same time
    List<FastSatellite> fast3 = allSatellites
    	.stream()
        .filter(x -> x instanceof FastSatellite)
        .map(x -> (FastSatellite) x)
        .collect(Collectors.toList());
}

Further Streams Example

public static void main(String[] args) {
    Controller c = new Controller();
    List<GenericSatellite> allSatellites = c.getSatelliteList();

    // What if im trying to look for a satellite with name == "Fast Satellite 2"?
    // Method 1, normal for-in/for-each
    GenericSatellite g1 = null;
    for (GenericSatellite x : allSatellites) {
        if (x.getName().equals("Fast Satellite 2")) {
            g1 = x;
            break;
        }
    }

    // Method 2, streams
    Optional<GenericSatellite> g2 = allSatellites
      .stream()
      .filter(x -> x.getName().equals("Fast Satellite 2"))
      .findAny();
}

Further Streams Example

The End

Reminder to talk about code smell

COMP2511 Tutorial 4

By Matthew Liu

COMP2511 Tutorial 4

  • 172