25T3 Week 4
Tuesday 10am-1pm (T10A)
Start 10:05am
Use .equal() for String (or any Class) comparison instead of ==
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
Avoid the use of magic numbers, assign them to final variables (readability - style issue)
public class MagicNumbers {
List<String> files = new ArrayList<>();
public void doSomething() {
if (files.size() == 5) { // 5 here is a magic number
// What does it mean?
}
}
}
public class MagicNumbers {
List<String> files = new ArrayList<>();
private final static int MAX_FILES = 5;
// declare constant with good name ^
public void doSomething() {
if (files.size() == MagicNumbers.MAX_FILES) {};
double angle = 750; // ^ use constant here
angle = angle % 360; // This is more OK, since theres context
}
}
Don't use super.x to set attributes in the super class in the subclass's constructor. Pass variable into super constructor as argument (single responsibility - design issue)
// Example: What not to do
public class Shape {
private String name;
private double length;
public Shape(String name) {
this.name = name;
// length is currently unset
}
public void setLength(double length) {
this.length = length;
}
}
public class Rectangle extends Shape {
public Rectangle(String name, double length) {
super(name);
this.length(length); // bad
this.length = length; // also bad if not private
}
}Don't use super.x to set attributes in the super class in the subclass's constructor. Pass variable into super constructor as argument (single responsibility - design issue)
// Example: What to do
public class Shape {
private String name;
private double length;
public Shape(String name, double length) {
this.name = name;
this.length = length;
this.setLength(length); // this is ok as well
}
}
public class Rectangle extends Shape {
private String somethingElse;
public Rectangle(String name, double length, String somethingElse) {
super(name, length);
this.somethingElse = somethingElse;
}
}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) {
// v1: kinda ok but overly verbose, does not include subclass
if (s.getClass().equals(Rectangle.class)) {
// do something only on exactly the Rectangle class
}
// v2: very bad, relies on string which can be incorrectly handled
if (s.getType().equals("Rectangle")) {
// do something on all rectangles
}
// v3: good, supports polymorphism + subclasses
if (s instanceof Rectangle) {
// do something on all rectangles
}
}
}
}
public class Square extends Rectangle {}Polymorphism is preferred over typechecking to perform a specific action (open/closed - design issue explored next week)
// Example: What not to do
public abstract class Shape {
public abstract String getType();
}
public class Rectangle extends Shape {}
public class Square extends Rectangle {
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Rectangle());
shapes.add(new Square());
for (Shape s : shapes) {
if (s.getType().equals("Rectangle")) {
// calculate the area this way
} else if (s.getType().equals("Square")) {
// calculate the area a different way
}
}
}
}Polymorphism is preferred over typechecking to perform a specific action (open closed - design issue explored next week)
// 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
}
}
}"Principle of least knowledge"
What is it?
Law of Demeter (aka principle of least knowledge) is a design guideline that says that an object should assume as little as possible knowledge about the structures or properties of other objects.
It aims to achieve loose coupling in code.
Cohesion: how closely related individual functions and responsibilities of a class/module are
Coupling: how much a class is reliant on other classes
We want to aim for high cohesion & low coupling
What does it actually mean?
A method in an object should only invoke methods of:
E.g., don't do this
o.get(name).get(thing).remove(node)*Caveat is that sometimes this is unavoidable
Law of Demeter
In the unsw.training package there is some skeleton code for a training system.
In the TrainingSystem class there is a method to book a seminar for an employee given the dates on which they are available. This method violates the principle of least knowledge (Law of Demeter).
In the unsw.training package there is some skeleton code for a training system.
In the TrainingSystem class there is a method to book a seminar for an employee given the dates on which they are available. This method violates the principle of least knowledge (Law of Demeter).
/**
* 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;
}
/**
* 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;
}
public List<String> getAttendees() {
return attendees;
}
}
public class TrainingSystem {
private List<Trainer> trainers;
public LocalDate bookTraining(String employee, List<LocalDate> availability) {
for (Trainer trainer : trainers) {
for (Seminar seminar : trainer.getSeminars()) {
for (LocalDate available : availability) {
if (seminar.getStart().equals(available) &&
seminar.getAttendees().size() < 10) {
seminar.getAttendees().add(employee);
return available;
}
}
}
}
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;
}
}
How and why does it violate this principle?
What other properties of this design are not desirable?
/**
* 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;
}
/**
* 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;
}
}TrainingSystem no longer has knowledge of SeminarWhat is it?
Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
*inheritance arrows are the other way around
Solve the problem without inheritance
Design principle: Favour composition over inheritance
If you favour composition over inheritance, your software will be more flexible, easier to maintain, extend.
That being said, inheritance can have its place!
/**
* 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;
}
/**
* 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;
}
public List<String> getAttendees() {
return attendees;
}
}
Where does OnlineSeminar violate LSP?
OnlineSeminar doesn't require a list of attendees
Technically, they could work out, but you probably shouldn't.
What type of design pattern is composite?
Structural
Structural design pattern are patterns that ease the design by identifying a simple way to realize relationships among entities.
They explain how to assemble objects and classes into large structures, while keeping structures flexible and efficient
Composite pattern is useful for aggregating different objects/data. The aim is to be able to manipulate a single instance of an object just as you would manipulate a group of them.
Tree like structure of objects
Calculator.java - Composite pattern
Inside src/calculator, use the Composite Pattern to write a simple calculator that evaluates an expression. Your calculator should be able to:
{1 + [2 * (4 + 3)]}
Expression 1
Expression 2
Expression 3
What type of design pattern?
Creational
Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.
Factory method provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
Reduces coupling and shotgun surgery, as all classes are created using the same method.
There are a few variants of the factory pattern:
Let's get to know them!
interface Vehicle {
void drive();
}
class Car implements Vehicle {
public void drive() { System.out.println("Driving a car 🚗"); }
}
class Bike implements Vehicle {
public void drive() { System.out.println("Riding a bike 🚲"); }
}
class VehicleFactory {
static Vehicle createVehicle(String type) {
return switch (type.toLowerCase()) {
case "car" -> new Car();
case "bike" -> new Bike();
};
}
}
public class FactoryExample {
public static void main(String[] args) {
Vehicle vehicle = VehicleFactory.createVehicle("car");
vehicle.drive(); // Output: Driving a car 🚗
}
}
public class StaticFactory {
public static A createA() {
}
public static B createB() {
}
public static C createC() {
}
}// Abstract Products
interface Button { void render(); }
class LightButton implements Button { public void render() { System.out.println("🟡 Light Button"); } }
class DarkButton implements Button { public void render() { System.out.println("⚫ Dark Button"); } }
// Abstract Factory
interface GUIFactory { Button createButton(); }
class LightFactory implements GUIFactory { public Button createButton() { return new LightButton(); } }
class DarkFactory implements GUIFactory { public Button createButton() { return new DarkButton(); } }
// Client Code
public class AbstractFactoryExample {
public static void main(String[] args) {
GUIFactory factory = new DarkFactory(); // Switch to LightFactory for light mode
factory.createButton().render(); // ⚫ Dark Button
}
}Thrones.java - Factory Pattern
Inside src/thrones, there is some code to model a simple chess-like game. In this game different types of characters move around on a grid fighting each other. When one character moves into the square occupied by another they attack that character and inflict damage based on random chance. There are four types of characters:
We want to refactor the code so that when the characters are created, they are put in a random location in a grid of length 5.
main method of Game.java to use these factories.