Week 6 [Sep 17]
Todo
Admin info to read:
Policy on suggested length for submissions
We don't usually give a strict page limit for documents such as User Guide and the Developer Guide. You need to decide yourself how long the document should be based on the purpose and the intended audience. You can determine the level of details required based on the samples we provide.
Why very narrow project scope?
Defining your own unique project is more fun.
But, wider scope → more diverse projects → harder for us to go deep into your project. The collective know-how we (i.e., students and the teaching team) have built up about SE issues related to the project become shallow and stretched too thinly. It also affects fairness of grading.
That is why a strictly-defined project is more suitable for a first course in SE that focuses on nuts-and-bolts of SE. After learning those fundamentals, in higher level project modules you can focus more on the creative side of software projects without being dragged down by nuts-and-bolts SE issues (because you already know how to deal with them). However, we would like to allow some room for creativity too. That is why we let you build products that are slight variations of a given theme.
Also note: The freedom to do 'anything' is not a necessary condition for creativity. Do not mistake being different for being creative. In fact, the more constrained you are, the more you need creativity to stand out.
Why project requirements are so vague?
"You tell me exactly what to do - I do that - you pay me (in grades)" is a model for contract work, not for learning. Being able to survive in imprecise, uncertain, volatile problem contexts is precisely what we are trying to teach you.
For example, the best way to communicate something often depends on what is being communicated. That is why we don't specify the precise content for project documents. Instead, we aim to refine project documents iteratively. We believe the learning experience will be richer if we let you decide the best way to present your project information rather than just following our instructions blindly. For example, in real-life projects you are rarely told which diagrams to draw; that is a decision you have to make yourself.
Outcomes
OOP
W6.1
Can explain overloading
W6.1a
Can explain method overloading
Paradigms → Object Oriented Programming → Inheritance →
Overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type Signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Method | Type Signature |
---|---|
int add(int X, int Y) |
(int, int) |
void add(int A, int B) |
(int, int) |
void m(int X, double Y) |
(int, double) |
void m(double X, int Y) |
(double, int) |
In the case below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int)
calculate(String): void
calculate(int): void
W6.2
Can implement polymorphism
Method Overriding
W6.2a
Can explain method overriding
Paradigms → Object Oriented Programming → Inheritance →
Overriding
Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.
Consider the following case of EvaluationReport
class inheriting the Report
class:
Report methods |
EvaluationReport methods |
Overrides? |
---|---|---|
print() |
print() |
Yes |
write(String) |
write(String) |
Yes |
read():String |
read(int):String |
No. Reason: the two methods have different signatures; This is a case of |
Paradigms → Object Oriented Programming → Inheritance →
Overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type Signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Method | Type Signature |
---|---|
int add(int X, int Y) |
(int, int) |
void add(int A, int B) |
(int, int) |
void m(int X, double Y) |
(int, double) |
void m(double X, int Y) |
(double, int) |
In the case below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int)
calculate(String): void
calculate(int): void
Which of these methods override another method? A
is the parent class. B
inherits A
.
- a
- b
- c
- d
- e
d
Explanation: Method overriding requires a method in a child class to use the same method name and same parameter sequence used by one of its ancestors
Polymorphism
W6.2b
Can explain OOP polymorphism
Paradigms → Object Oriented Programming → Polymorphism →
What
Polymorphism:
The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple
Polymorphism allows you to write code targeting superclass objects, use that code on subclass objects, and achieve possibly different results based on the actual class of the object.
Assume classes Cat
and Dog
are both subclasses of the Animal
class. You can write code targeting Animal
objects and use that code on Cat
and Dog
objects, achieving possibly different results based on whether it is a Cat
object or a Dog
object. Some examples:
- Declare an array of type
Animal
and still be able to storeDog
andCat
objects in it. - Define a method that takes an
Animal
object as a parameter and yet be able to passDog
andCat
objects to it. - Call a method on a
Dog
or aCat
object as if it is anAnimal
object (i.e., without knowing whether it is aDog
object or aCat
object) and get a different response from it based on its actual class e.g., call theAnimal
class' methodspeak()
on objecta
and get aMeow
as the return value ifa
is aCat
object andWoof
if it is aDog
object.
Polymorphism literally means "ability to take many forms".
Evidence:
Acceptable: Using polymorphism in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Polymorphism]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
W6.2c
Can use polymorphism in Java
C++ to Java → Inheritance →
Polymorphism
Java is a strongly-typed language which means the code works with only the object types that it targets.
The following code PetShelter
keeps a list of Cat
objects and make them speak
. The code will not work with any other type, for example, Dog
objects.
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
}
}
Mittens: Meow
Snowball: Meow
public class Cat {
public Cat(String name) {
super(name);
}
public String speak() {
return name + ": Meow";
}
}
This strong-typing can lead to unnecessary verbosity caused by repetitive similar code that do similar things with different object types.
If the PetShelter
is to keep both cats and dogs, you'll need two arrays and two loops:
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
private static Dog[] dogs = new Dog[]{
new Dog("Spot")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
for(Dog d: dogs){
System.out.println(d.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
public class Dog {
public Dog(String name) {
super(name);
}
public String speak() {
return name + ": Woof";
}
}
A better way is to take advantage of polymorphism to write code that targets a superclass but works with any subclass objects.
The PetShelter2
use one data structure to keep both types of animals and one loop to make them speak. The code targets the Animal
superclass (assuming Cat
and Dog
inherits from the Animal
class) instead of repeating the code for each animal type.
public class PetShelter2 {
private static Animal[] animals = new Animal[]{
new Cat("Mittens"),
new Cat("Snowball"),
new Dog("Spot")};
public static void main(String[] args) {
for (Animal a: animals){
System.out.println(a.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
public class Animal {
protected String name;
public Animal(String name){
this.name = name;
}
public String speak(){
return name;
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public String speak() {
return name + ": Meow";
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public String speak() {
return name + ": Woof";
}
}
Explanation: Because Java supports polymorphism, you can store both Cat
and Dog
objects in an array of Animal
objects. Similarly, you can call the speak
method on any Animal
object (as done in the loop) and yet get different behavior from Cat
objects and Dog
objects.
💡 Suggestion: try to add an Animal
object (e.g., new Animal("Unnamed")
) to the animals
array and see what happens.
Polymorphic code is better in several ways:
- It is shorter.
- It is simpler.
- It is more flexible (in the above example, the
main
method will work even if we add more animal types).
The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
Add the missing variables/methods to the code below so that it produces the output given.
public class Main {
//TODO add your methods here
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
printAreas();
addShape(new Rectangle(4, 4));
printAreas();
}
}
78
12
314
78
12
314
16
Circle
class and Rectangle
class is given below but you'll need to add a parent class Shape
:
public class Circle {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
public int area() {
return (int)(Math.PI * radius * radius);
}
}
public class Rectangle {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
public int area() {
return height * width;
}
}
💡 You may use an array of size 100 to store the shapes.
public class Main {
private static Shape[] shapes = new Shape[100];
private static int shapeCount = 0;
public static void addShape(Shape s){
shapes[shapeCount] = s;
shapeCount++;
}
// ...
}
This exercise continues from the TaskManager Level1
exercise quoted above.
Enhance your TaskManager program in the following ways.
A. Add support for two types of tasks:
- ToDo : a task to do someday
- Deadline: a task to be done by a specific deadline
Both types keeps an internal flag to indicate if the task is done. The flag is initially set to false
.
Here is an example output:
Welcome to TaskManager-Level2!
Your task? todo submit report
Tasks in the list: 1
Your task? deadline write report /by this Friday 4pm
Tasks in the list: 2
Your task? todo read textbook
Tasks in the list: 3
Your task? deadline return textbook /by Sunday
Tasks in the list: 4
Your task? print
Tasks:
[1] description: submit report
is done? No
[2] description: write report
is done? No
do by: this Friday 4pm
[3] description: read textbook
is done? No
[4] description: return textbook
is done? No
do by: Sunday
Your task? exit
Bye!
Changes to the behavior:
add
task description
: adds thetask description
to the task listtodo
task description
: adds to the task list a todo task with the giventask description
deadline
task description /by deadline description
: adds to the task list a deadline task with the giventask description
and with thedeadline description
Suggestion:
- Make the
Todo
class inherit fromTask
class, and makeDeadline
task inherit fromTodo
class. - Use polymorphism to store both types of tasks in an array of
Task
type and use one loop to print both types of tasks.
B. Add support for
Quality Assurance → Testing → Test Automation →
Automated Testing of CLI Apps
A simple way to semi-automate testing of a CLI(Command Line Interface) app is by using input/output re-direction.
- First, we feed the app with a sequence of test inputs that is stored in a file while redirecting the output to another file.
- Next, we compare the actual output file with another file containing the expected output.
Let us assume we are testing a CLI app called AddressBook
. Here are the detailed steps:
-
Store the test input in the text file
input.txt
.add Valid Name p/12345 valid@email.butNoPrefix add Valid Name 12345 e/valid@email.butPhonePrefixMissing
-
Store the output we expect from the SUT in another text file
expected.txt
.Command: || [add Valid Name p/12345 valid@email.butNoPrefix] Invalid command format: add Command: || [add Valid Name 12345 e/valid@email.butPhonePrefixMissing] Invalid command format: add
-
Run the program as given below, which will redirect the text in
input.txt
as the input toAddressBook
and similarly, will redirect the output of AddressBook to a text fileoutput.txt
. Note that this does not require any code changes toAddressBook
.java AddressBook < input.txt > output.txt
-
💡 The way to run a CLI program differs based on the language.
e.g., In Python, assuming the code is inAddressBook.py
file, use the command
python AddressBook.py < input.txt > output.txt
-
💡 If you are using Windows, use a normal command window to run the app, not a Power Shell window.
More on the >
operator and the<
operator. tangentialA CLI program takes input from the keyboard and outputs to the console. That is because those two are default input and output streams, respectively. But you can change that behavior using
<
and>
operators. For example, if you runAddressBook
in a command window, the output will be shown in the console, but if you run it like this,java AddressBook > output.txt
the Operating System then creates a file
output.txt
and stores the output in that file instead of displaying it in the console. No file I/O coding is required. Similarly, adding< input.txt
(or any other filename) makes the OS redirect the contents of the file as input to the program, as if the user typed the content of the file one line at a time.Resources:
-
-
Next, we compare
output.txt
with theexpected.txt
. This can be done using a utility such as WindowsFC
(i.e. File Compare) command, Unixdiff
command, or a GUI tool such as WinMerge.FC output.txt expected.txt
Note that the above technique is only suitable when testing CLI apps, and only if the exact output can be predetermined. If the output varies from one run to the other (e.g. it contains a time stamp), this technique will not work. In those cases we need more sophisticated ways of automating tests.
CLI App: An application that has a Command Line Interface. i.e. user interacts with the app by typing in commands.
import java.util.Scanner;
public class Main {
static Scanner in = new Scanner(System.in);
static Task[] tasks = new Task[100];
static int taskCount = 0;
public static void main(String[] args) {
printWelcome();
String line;
boolean isExit = false;
while (!isExit) {
line = getInput();
String command = line.split(" ")[0];
switch (command) {
case "exit":
case "":
isExit = true;
break;
case "todo":
addTodo(line);
break;
case "deadline":
addDeadline(line);
break;
case "print":
printTasks();
break;
default:
printError();
}
}
exit();
}
private static void addTodo(String line) {
tasks[taskCount] = new Todo(line.substring("todo".length()).trim());
taskCount++;
System.out.println("Tasks in the list: " + taskCount);
}
// ...
private static void printTasks() {
System.out.println("Tasks:");
for (int i = 0; i < taskCount; i++) {
System.out.println("[" + (i + 1) + "] " + tasks[i]);
}
}
}
Abstract Classes
W6.2d
Can implement abstract classes
Paradigms → Object Oriented Programming → Inheritance →
Abstract Classes and Methods
Abstract Class: A class declared as an abstract class cannot be instantiated, but they can be subclassed.
You can use declare a class as abstract when a class is merely a representation of commonalities among its subclasses in which case it does not make sense to instantiate objects of that class.
The Animal
class that exist as a generalization of its subclasses Cat
, Dog
, Horse
, Tiger
etc. can be declared as abstract because
it does not make sense to instantiate an Animal
object.
Abstract Method: An abstract method is a method signature without a method implementation.
The move
method of the Animal
class is likely to be an abstract method as it is not possible to implement a move
method at the Animal
class level
to fit all subclasses because each animal type can move in a different way.
A class that has an abstract method becomes an abstract class because the class definition is incomplete (due to the missing method body) and it is not possible to create objects using an incomplete class definition.
Evan a class that does not have any abstract methods can be declared as an abstract class.
W6.2e
Can interpret abstract classes in class diagrams
Tools → UML → Class Diagrams → Abstract Classes →
What
You can use italics or {abstract}
(preferred) keyword to denote abstract classes/methods.
Example:
W6.2f
Can use abstract classes and methods
C++ to Java → Inheritance →
Abstract Classes and Methods
In Java, an abstract method is declared with the keyword abstract
and given without an implementation. If a class includes abstract methods, then the class itself must be declared abstract.
The speak
method in this Animal
class is abstract
. Note how the method signature ends with a semicolon and there is no method body. This makes sense as the
implementation of the speak
method depends on the type of the animal and it is meaningless to provide a common implementation for all animal types.
public abstract class Animal {
protected String name;
public Animal(String name){
this.name = name;
}
public abstract String speak();
}
As one method of the class is abstract
, the class itself is abstract
.
An abstract class is declared with the keyword abstract
. Abstract classes can be used as reference type but cannot be instantiated.
This Account
class has been declared as abstract although it does not have any abstract methods. Attempting to instantiate Account
objects will result in a compile error.
public abstract class Account {
int number;
void close(){
//...
}
}
Account a;
OK to use as a type
a = new Account();
Compile error!
When an abstract class is subclassed, the subclass should provides implementations for all of the abstract methods in its superclass or else the subclass must also be declared abstract.
The Feline
class below inherits from the abstract class Animal
but it does not provide an implementation for the abstract method speak
. As a result, the Feline
class needs to be abstract too.
public abstract class Feline extends Animal {
public Feline(String name) {
super(name);
}
}
The DomesticCat
class inherits the abstract Feline
class and provides the implementation for the abstract method speak
. As a result, it need not be declared abstract.
public class DomesticCat extends Feline {
public DomesticCat(String name) {
super(name);
}
@Override
public String speak() {
return "Meow";
}
}
Animal a = new Feline("Mittens");
Compile error! Feline
is abstract.
Animal a = new DomesticCat("Mittens");
OK. DomesticCat
can be instantiated and assigned
to a variable of Animal
type (the assignment is allowed by polymorphism).
The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
public class Main {
private static Shape[] shapes = new Shape[100];
private static int shapeCount = 0;
public static void addShape(Shape s){
shapes[shapeCount] = s;
shapeCount++;
}
public static void printAreas(){
for (int i = 0; i < shapeCount; i++){
shapes[i].print();
}
}
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
addShape(new Rectangle(4, 4));
printAreas();
}
}
Circle of area 78
Rectangle of area 12
Circle of area 314
Rectangle of area 16
Circle
class and Rectangle
class is given below:
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Shape
class as an abstract class with two abstract methods.
public abstract class Shape {
public abstract int area();
// ...
}
Choose the correct statements about Java abstract classes and
- a. A concrete class can contain an abstract method.
- b. An abstract class can contain concrete methods.
- c. An abstract class need not contain any concrete methods.
- d. An abstract class cannot be instantiated.
(b)(c)(d)
Explanation: A concrete class cannot contain even a single abstract method.
Evidence:
Acceptable: Using abstract classes in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Abstract]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
Interfaces
W6.2g
Can explain interfaces
Paradigms → Object Oriented Programming → Inheritance →
Interfaces
An interface is a behavior specification i.e. a collection of
There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts. --Oracle Docs on Java
Suppose SalariedStaff
is an interface that contains two methods setSalary(int)
and getSalary()
. AcademicStaff
can declare itself as implementing the SalariedStaff
interface, which means the AcademicStaff
class must implement all the methods specified by the SalariedStaff
interface i.e., setSalary(int)
and getSalary()
.
A class implementing an interface results in an is-a relationship, just like in class inheritance.
In the example above, AcademicStaff
is a SalariedStaff
. An AcademicStaff
object can be used anywhere a SalariedStaff
object is expected
e.g. SalariedStaff ss = new AcademicStaff()
.
W6.2h
Can interpret interfaces in class diagrams
Tools → UML → Class Diagrams → Interfaces →
Interfaces
An interface is shown similar to a class with an additional keyword << interface >>
. When a class implements an interface, it is shown similar to class inheritance except a dashed line is used instead of a solid line.
The AcademicStaff
and the AdminStaff
classes implement the SalariedStaff
interface.
W6.2i
Can use interfaces in Java
C++ to Java → Inheritance →
Interfaces
The text given in this section borrows some explanations and code examples from the -- Java Tutorial.
In Java, an interface is a reference type, similar to a class, mainly containing method signatures. Defining an interface is similar to creating a new class except it uses the keyword interface
in place of class
.
Here is an interface named DrivableVehicle
that defines methods needed to drive a vehicle.
public interface DrivableVehicle {
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Note that the method signatures have no braces and are terminated with a semicolon.
Interfaces cannot be instantiated—they can only be implemented by classes. When an instantiable class implements an interface, indicated by the keyword implements
, it provides a method body for each
of the methods declared in the interface.
Here is how a class CarModelX
can implement the DrivableVehicle
interface.
public class CarModelX implements DrivableVehicle {
@Override
public void turn(Direction direction) {
// implementation
}
// implementation of other methods
}
An interface can be used as a type e.g., DrivableVechile dv = new CarModelX();
.
Interfaces can inherit from other interfaces using the extends
keyword, similar to a class inheriting another.
Here is an interface named SelfDrivableVehicle
that inherits the DrivableVehicle
interface.
public interface SelfDrivableVehicle extends DrivableVehicle {
void goToAutoPilotMode();
}
Note that the method signatures have no braces and are terminated with a semicolon.
Furthermore, Java allows multiple inheritance among interfaces. A Java interface can inherit multiple other interfaces. A Java class can implement multiple interfaces (and inherit from one class).
The design below is allowed by Java. In case you are not familiar with UML notation used: solid lines indicate normal inheritance; dashed lines indicate interface inheritance; the triangle points to the parent.
Staff
interface inherits (note the solid lines) the interfacesTaxPayer
andCitizen
.TA
class implements bothStudent
interface and theStaff
interface.- Because of point 1 above,
TA
class has to implement all methods in the interfacesTaxPayer
andCitizen
. - Because of points 1,2,3, a
TA
is aStaff
, is aTaxPayer
and is aCitizen
.
Interfaces can also contain
C++ to Java → Miscellaneous Topics →
Constants
Java does not directly support constants. The convention is to use a static
final
variable where a constant is needed. The static
modifier causes the variable to be available without
instantiating an object. The final
modifier causes the variable to be unchangeable. Java constants are normally declared in ALL CAPS separated by underscores.
Here is an example of a constant named MAX_BALANCE
which can be accessed as Account.MAX_BALANCE
.
public class Account{
public static final double MAX_BALANCE = 1000000.0;
}
Math.PI
is an example constant that comes with Java.
This example adds a constant MAX_SPEED
and a static method isSpeedAllowed
to the interface DrivableVehicle
.
public interface DrivableVehicle {
int MAX_SPEED = 150;
static boolean isSpeedAllowed(int speed){
return speed <= MAX_SPEED;
}
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Interfaces can contain default method implementations and nested types. They are not covered here.
The Main
class below passes a list of Printable
objects (i.e., objects that implement the Printable
interface) for another method to be printed.
public class Main {
public static void printObjects(Printable[] items) {
for (Printable p : items) {
p.print();
}
}
public static void main(String[] args) {
Printable[] printableItems = new Printable[]{
new Circle(5),
new Rectangle(3, 4),
new Person("James Cook")};
printObjects(printableItems);
}
}
Circle of area 78
Rectangle of area 12
Person of name James Cook
Classes Shape
, Circle
, and Rectangle
are given below:
public abstract class Shape {
public abstract int area();
}
public class Circle extends Shape implements Printable {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape implements Printable {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Printable
interface. Add the missing methods of the Person
class given below.
public class Person implements Printable {
private String name;
// todo: add missing methods
}
public interface Printable {
//...
}
Evidence:
Acceptable: Using interfaces in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Interfaces]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
Design
W6.3
Can interpret an architecture diagram
W6.3a
Can explain what is software design
Design → Introduction →
What
Design in the creative process of transforming the problem into a solution; the solution is also called design. -- 📖 Software Engineering Theory and Practice, Shari Lawrence; Atlee, Joanne M. Pfleeger
Software design has two main aspects:
- Product/external design: designing the external behavior of the product to meet the users' requirements. This is usually done by product designers with the input from business analysts, user experience experts, user representatives, etc.
- Implementation/internal design: designing how the product will be implemented to meet the required external behavior. This is usually done by software architects and software engineers.
W6.3b
Can interpret an architecture diagram
Design → Architecture → Architecture Diagrams →
Reading
Architecture diagrams are free-form diagrams. There is no universally adopted standard notation for architecture diagrams. Any symbol that reasonably describes the architecture may be used.
Some example architecture diagrams:
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
W6.3c
Can explain multi-level design
Design → Introduction →
Multi-Level Design
In a smaller system, design of the entire system can be shown in one place.
This class diagram of se-edu/addressbook-level3 depicts the design of the entire software.
Design of bigger systems needs to be done/shown at multiple levels.
This architecture diagram of se-edu/addressbook-level4 depicts the high-level design of the software.
Here are examples of lower level designs of some components of the same software:
Evidence:
Covered by the deliverable below (i.e. to be able to enhance an component of AddressBook-Level4, you need to be able to understand its multi-level design):
v1.1 Product
- Each member should try to add some enhancements that are in line with the vision for v2.0. Preferably, these should be
global-impact enhancements, touching as many other components as possible. Refer to the AddressBook-Level4 Developer Guide has some guidance on how to implement a new feature end-to-end.
W6.4
Can explain architectural styles
W6.4a
Can explain architectural styles
Design → Architecture → Styles
What
Software architectures follow various high-level styles (aka architectural patterns), just like
n-tier style, client-server style, event-driven style, transaction processing style, service-oriented style, pipes-and-filters style, message-driven style, broker style, ...
source: https://inspectapedia.com
W6.4b
Can identify n-tier architectural style
Design → Architecture → Styles → n-Tier Style
What
In the n-tier style, higher layers make use of services provided by lower layers. Lower layers are independent of higher layers. Other names: multi-layered, layered.
Operating systems and network communication software often use n-tier style.
W6.4c
Can identify the client-server architectural style
Design → Architecture → Styles → Client-Server Style
What
The client-server style has at least one component playing the role of a server and at least one client component accessing the services of the server. This is an architectural style used often in distributed applications.
The online game and the Web application below uses the client-server style.
W6.4d
Can identify event-driven architectural style
Design → Architecture → Styles → Event-Driven Style
What
Event-driven style controls the flow of the application by detecting
When the ‘button clicked’ event occurs in a GUI, that event can be transmitted to components that are interested in reacting to that event. Similarly, events detected at a Printer port can be transmitted to components related to operating the Printer. The same event can be sent to multiple consumers too.
Evidence:
Explain how the AddressBook-Level4 uses Event-Driven style (refer to the Developer Guide for more info).
W6.4e
Can identify transaction processing architectural style
Design → Architecture → Styles → Transaction Processing Style
What
The transaction processing style divides the workload of the system down to a number of transactions which are then given to a dispatcher that controls the execution of each transaction. Task queuing, ordering, undo etc. are handled by the dispatcher.
In this example from a Banking system, transactions are generated by the terminals used by
W6.4f
Can identify service-oriented architectural style
Design → Architecture → Styles → Service-Oriented Style
What
The service-oriented architecture (SOA) style builds applications by combining functionalities packaged as programmatically accessible services. SOA aims to achieve interoperability between distributed services, which may not even be implemented using the same programming language. A common way to implement SOA is through the use of XML web services where the web is used as the medium for the services to interact, and XML is used as the language of communication between service providers and service users.
Suppose that Amazon.com provides a web service for customers to browse and buy merchandise, while HSBC provides a web service for merchants to charge HSBC credit cards. Using these web services, an ‘eBookShop’ web application can be developed that allows HSBC customers to buy merchandise from Amazon and pay for them using HSBC credit cards. Because both Amazon and HSBC services follow the SOA architecture, their web services can be reused by the web application, even if all three systems use different programming platforms.
W6.4g
Can name several other architecture styles
Design → Architecture → Styles
More Styles
Other well-known architectural styles include the pipes-and-filters architectures, the broker architectures, the peer-to-peer architectures, and the message-oriented architectures.
-
Pipes and Filters pattern -- an article from Microsoft about the pipes and filters architectural style
-
Broker pattern -- Wikipedia article on the broker architectural style
-
Peer-to-peer pattern -- Wikipedia article on the P2P architectural style
-
Message-driven processing -- a post by Margaret Rouse
W6.4h
Can explain how architectural styles are combined
Design → Architecture → Styles
Using Styles
Most applications use a mix of these architectural styles.
An application can use a client-server architecture where the server component comprises several layers, i.e. it uses the n-Tier architecture.
Assume you are designing a multiplayer version of the Minesweeper game where any number of players can play the same Minefield. Players use their own PCs to play the game. A player scores by deducing a cell correctly before any of the other players do. Once a cell is correctly deduced, it appears as either marked or cleared for all players.
Comment on how each of the following architectural styles could be potentially useful when designing the architecture for this game.
- Client-server
- Transaction-processing
- SOA (Service Oriented Architecture)
- multi-layer (n-tier)
- Client-server – Clients can be the game UI running on player PCs. The server can be the game logic running on one machine.
- Transaction-processing – Each player action can be packaged as transactions (by the client component running on the player PC) and sent to the server. Server processes them in the order they are received.
- SOA – The game can access a remote web services for things such as getting new puzzles, validating puzzles, charging players subscription fees, etc.
- Multi-layer – The server component can have two layers: logic layer and the storage layer.
Evidence:
Explain architectural styles currently used in AddressBook-Level4. Speculate under what circumstances other architectural styles can be used in it e.g. what kind of additional features can make a certain style applicable
W6.5
Can use intermediate-level class diagrams
W6.5a
Can use UML notes
Tools → UML →
Notes
UML notes can augment UML diagrams with additional information. These notes can be shown connected to a particular element in the diagram or can be shown without a connection. The diagram below shows examples of both.
Example:
W6.5b
Can specify constraints in UML diagrams
Tools → UML →
Constraints
A constraint can be given inside a note, within curly braces. Natural language or a formal notation such as OCL (Object Constraint Language) may be used to specify constraints.
Example:
W6.5c
Can show an association as an attribute
Tools → UML → Class Diagrams →
Associations as Attributes
An association can be shown as an attribute instead of a line.
Association multiplicities and the default value too can be shown as part of the attribute using the following notation. Both are optional.
name: type [multiplicity] = default value
The diagram below depicts a multi-player Square Game being played on a board comprising of 100 squares. Each of the squares may be occupied with any number of pieces, each belonging to a certain player.
A Piece
may or may not be on a Square
. Note how that association can be replaced by an isOn
attribute of the Piece
class. The isOn
attribute can either be null
or hold a reference to a Square
object, matching the 0..1
multiplicity of the association it replaces. The default value is null
.
The association that a Board
has 100 Sqaure
s can be shown in either of these two ways:
Coding best practices
W6.6
Can use good naming
W6.6a
Can explain the need for good names in code
Implementation → Code Quality → Naming →
Introduction
Proper naming improves the readability. It also reduces bugs caused by ambiguities regarding the intent of a variable or a method.
There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton
W6.6b
Can improve code quality using technique: use nouns for things and verbs
for actions
Implementation → Code Quality → Naming → Basic →
Use Nouns for Things and Verbs for Actions
Use nouns for classes/variables and verbs for methods/functions.
Examples:
Name for a | Bad | Good |
---|---|---|
Class | CheckLimit |
LimitChecker |
method | result() |
calculate() |
Distinguish clearly between single-valued and multivalued variables.
Examples:
Good
Person student;
ArrayList<Person> students;
Good
student = Person('Jim')
students = [Person('Jim'), Person('Alice')]
W6.6c
Can improve code quality using technique: use standard words
Implementation → Code Quality → Naming → Basic →
Use Standard Words
Use correct spelling in names. Avoid 'texting-style' spelling. Avoid foreign language words, slang, and names that are only meaningful within specific contexts/times e.g. terms from private jokes, a TV show currently popular in your country
W6.6d
Can improve code quality using technique: use name to explain
Implementation → Code Quality → Naming → Intermediate →
Use Name to Explain
A name is not just for differentiation; it should explain the named entity to the reader accurately and at a sufficient level of detail.
Examples:
Bad | Good |
---|---|
processInput() (what 'process'?) |
removeWhiteSpaceFromInput() |
flag |
isValidInput |
temp |
If the name has multiple words, they should be in a sensible order.
Examples:
Bad | Good |
---|---|
bySizeOrder() |
orderBySize() |
Imagine going to the doctor's and saying "My eye1 is swollen"! Don’t use numbers or case to distinguish names.
Examples:
Bad | Bad | Good |
---|---|---|
value1 , value2 |
value , Value |
originalValue , finalValue |
W6.6e
Can improve code quality using technique: not too long, not too short
Implementation → Code Quality → Naming → Intermediate →
Not Too Long, Not Too Short
While it is preferable not to have lengthy names, names that are 'too short' are even worse. If you must abbreviate or use acronyms, do it consistently. Explain their full meaning at an obvious location.
W6.6f
Can improve code quality using technique: avoid misleading names
Implementation → Code Quality → Naming → Intermediate →
Avoid Misleading Names
Related things should be named similarly, while unrelated things should NOT.
Example: Consider these variables
colorBlack
: hex value for color blackcolorWhite
: hex value for color whitecolorBlue
: number of times blue is usedhexForRed
: : hex value for color red
This is misleading because colorBlue
is named similar to colorWhite
and colorBlack
but has a different purpose while hexForRed
is named differently but has very similar purpose
to the first two variables. The following is better:
hexForBlack
hexForWhite
hexForRed
blueColorCount
Avoid misleading or ambiguous names (e.g. those with multiple meanings), similar sounding names, hard-to-pronounce ones (e.g. avoid ambiguities like "is that a lowercase L, capital I or number 1?", or "is that number 0 or letter O?"), almost similar names.
Examples:
Bad | Good | Reason |
---|---|---|
phase0 |
phaseZero |
Is that zero or letter O? |
rwrLgtDirn |
rowerLegitDirection |
Hard to pronounce |
right left wrong |
rightDirection leftDirection wrongResponse |
right is for 'correct' or 'opposite of 'left'? |
redBooks readBooks |
redColorBooks booksRead |
red and read (past tense) sounds the same |
FiletMignon |
egg |
If the requirement is just a name of a food, egg is a much easier to type/say choice than FiletMignon |
W6.7
Can avoid unsafe coding practices
W6.7a
Can explain the need for avoiding error-prone shortcuts
Implementation → Code Quality → Error-Prone Practices →
Introduction
It is safer to use language constructs in the way they are meant to be used, even if the language allows shortcuts. Some such coding practices are common sources of bugs. Know them and avoid them.
W6.7b
Can improve code quality using technique: use the default branch
Implementation → Code Quality → Error-Prone Practices → Basic →
Use the Default Branch
Always include a default branch in case
statements.
Furthermore, use it for the intended default action and not just to execute the last option. If there is no default action, you can use the 'default' branch to detect errors (i.e. if execution reached the default
branch,
throw an exception). This also applies to the final else
of an if-else
construct. That is, the final else
should mean 'everything else', and not the final option. Do not use else
when an if
condition can be explicitly specified, unless there is absolutely no other possibility.
Bad
if (red) print "red";
else print "blue";
Good
if (red) print "red";
else if (blue) print "blue";
else error("incorrect input");
W6.7c
Can improve code quality using technique: don't recycle variables or parameters
Implementation → Code Quality → Error-Prone Practices → Basic →
Don't Recycle Variables or Parameters
- Use one variable for one purpose. Do not reuse a variable for a different purpose other than its intended one, just because the data type is the same.
- Do not reuse formal parameters as local variables inside the method.
Bad
double computeRectangleArea(double length, double width) {
length = length * width;
return length;
}
Good
double computeRectangleArea(double length, double width) {
double area;
area = length * width;
return area;
}
W6.7d
Can improve code quality using technique: avoid empty catch blocks
Implementation → Code Quality → Error-Prone Practices → Basic →
Avoid Empty Catch Blocks
Never write an empty catch
statement. At least give a comment to explain why the catch
block is left empty.
W6.7e
Can improve code quality using technique: delete dead code
Implementation → Code Quality → Error-Prone Practices → Basic →
Delete Dead Code
We all feel reluctant to delete code we have painstakingly written, even if we have no use for that code any more ("I spent a lot of time writing that code; what if we need it again?"). Consider all code as baggage you have to carry; get rid of unused code the moment it becomes redundant. If you need that code again, simply recover it from the revision control tool you are using. Deleting code you wrote previously is a sign that you are improving.
W6.7f
Can improve code quality using technique: minimise scope of variables
Implementation → Code Quality → Error-Prone Practices → Intermediate →
Minimise Scope of Variables
Minimize global variables. Global variables may be the most convenient way to pass information around, but they do create implicit links between code segments that use the global variable. Avoid them as much as possible.
Define variables in the least possible scope. For example, if the variable is used only within the if
block of the conditional statement, it should be declared inside that if
block.
The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used. -- Effective Java, by Joshua Bloch
Resources:
W6.7g
Can improve code quality using technique: minimise code duplication
Implementation → Code Quality → Error-Prone Practices → Intermediate →
Minimise Code Duplication
Code duplication, especially when you copy-paste-modify code, often indicates a poor quality implementation. While it may not be possible to have zero duplication, always think twice before duplicating code; most often there is a better alternative.
This guideline is closely related to the
Supplmentary → Principles →
DRY Principle
DRY (Don't Repeat Yourself) Principle: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system The Pragmatic Programmer, by Andy Hunt and Dave Thomas
This principle guards against duplication of information.
The functionality implemented twice is a violation of the DRY principle even if the two implementations are different.
The value a system-wide timeout being defined in multiple places is a violation of DRY.
W6.8
Can write good code comments
W6.8a
Can explain the need for commenting minimally but sufficiently
Implementation → Code Quality → Comments →
Introduction
Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer. --Steve McConnell, Author of Clean Code
Some think commenting heavily increases the 'code quality'. This is not so. Avoid writing comments to explain bad code. Improve the code to make it self-explanatory.
W6.8b
Can improve code quality using technique: do not repeat the obvious
Implementation → Code Quality → Comments → Basic →
Do Not Repeat the Obvious
If the code is self-explanatory, refrain from repeating the description in a comment just for the sake of 'good documentation'.
Bad
// increment x
x++;
//trim the input
trimInput();
W6.8c
Can improve code quality using technique: write to the reader
Implementation → Code Quality → Comments → Basic →
Write to the Reader
Do not write comments as if they are private notes to self. Instead, write them well enough to be understood by another programmer. One type of comments that is almost always useful is the header comment that you write for a class or an operation to explain its purpose.
Examples:
Bad Reason: this comment will only make sense to the person who wrote it
// a quick trim function used to fix bug I detected overnight
void trimInput(){
....
}
Good
/** Trims the input of leading and trailing spaces */
void trimInput(){
....
}
Bad Reason: this comment will only make sense to the person who wrote it
# a quick trim function used to fix bug I detected overnight
def trim_input():
...
Good
def trim_input():
"""Trim the input of leading and trailing spaces"""
...
W6.8d
Can improve code quality using technique: explain what and why, not how
Implementation → Code Quality → Comments → Intermediate →
Explain WHAT and WHY, not HOW
Comments should explain what and why aspect of the code, rather than the how aspect.
👍 What : The specification of what the code supposed to do. The reader can compare such comments to the implementation to verify if the implementation is correct
Example: This method is possibly buggy because the implementation does not seem to match the comment. In this case the comment could help the reader to detect the bug.
/** Removes all spaces from the {@code input} */
void compact(String input){
input.trim();
}
👍 Why : The rationale for the current implementation.
Example: Without this comment, the reader will not know the reason for calling this method.
// Remove spaces to comply with IE23.5 formatting rules
compact(input);
👎 How : The explanation for how the code works. This should already be apparent from the code, if the code is self-explanatory. Adding comments to explain the same thing is redundant.
Example:
Bad Reason: Comment explains how the code works.
// return true if both left end and right end are correct or the size has not incremented
return (left && right) || (input.size() == size);
Good Reason: Code refactored to be self-explanatory. Comment no longer needed.
boolean isSameSize = (input.size() == size) ;
return (isLeftEndCorrect && isRightEndCorrect) || isSameSize;
Project Management
W6.9
Can explain continuous integration and continuous deployment
W6.9a
Can explain integration
Implementation → Integration → Introduction →
What
Combining parts of a software product to form a whole is called integration. It is also one of the most troublesome tasks and it rarely goes smoothly.
W6.9b
Can explain build automation tools
Implementation → Integration → Build Automation →
What
Build automation tools automate the steps of the build process, usually by means of build scripts.
In a non-trivial project, building a product from source code can be a complex multi-step process. For example, it can include steps such as to pull code from the revision control system, compile, link, run automated tests, automatically update release documents (e.g. build number), package into a distributable, push to repo, deploy to a server, delete temporary files created during building/testing, email developers of the new build, and so on. Furthermore, this build process can be done ‘on demand’, it can be scheduled (e.g. every day at midnight) or it can be triggered by various events (e.g. triggered by a code push to the revision control system).
Some of these build steps such as to compile, link and package are already automated in most modern IDEs. For example, several steps happen automatically when the ‘build’ button of the IDE is clicked. Some IDEs even allow customization to this build process to some extent.
However, most big projects use specialized build tools to automate complex build processes.
Some popular build tools relevant to Java developers:
Some other build tools : Grunt (JavaScript), Rake (Ruby)
Some build tools also serve as dependency management tools. Modern software projects often depend on third party libraries that evolve constantly. That means developers need to download the correct version of the required libraries and update them regularly. Therefore, dependency management is an important part of build automation. Dependency Management tools can automate that aspect of a project.
Maven and Gradle, in addition to managing the build process, can play the role of dependency management tools too.
- Getting Started with Gradle -- Documentation from Gradle
- Gradle Tutorial -- from tutorialspoint.com
Gradle_is used used for,
- a. better revision control
- b. build automation
- c. UML diagramming
- d. project collaboration
(b)
Evidence:
- Explain what Gradle is and how it helps in the AddressBook project.
- Run various project tasks using Gradle as described in [AddressBook - Level 4: Using Gradle] e.g. run tests in headless mode
W6.9c
Can explain continuous integration and continuous deployment
Implementation → Integration → Build Automation →
Continuous Integration and Continuous Deployment
An extreme application of build automation is called continuous integration (CI) in which integration, building, and testing happens automatically after each code change.
A natural extension of CI is Continuous Deployment (CD) where the changes are not only integrated continuously, but also deployed to end-users at the same time.
Some examples of CI/CD tools:
- Travis CI for Beginners -- Documentation from Travis
Evidence:
- Explain what Travis is and how it helps in the AddressBook project.
- One member: Set up Travis for your team repo by following [AddressBook - Level 4: Using Travis]
🅿️ Project
W6.10
Can contribute to project documentation
Covered by:
Tutorial 6
Suggested tutorial activity
- Draw the architecture diagram for your product and explain to your tutor/other team
W6.2b
Can explain OOP polymorphism
Paradigms → Object Oriented Programming → Polymorphism →
What
Polymorphism:
The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple
Polymorphism allows you to write code targeting superclass objects, use that code on subclass objects, and achieve possibly different results based on the actual class of the object.
Assume classes Cat
and Dog
are both subclasses of the Animal
class. You can write code targeting Animal
objects and use that code on Cat
and Dog
objects, achieving possibly different results based on whether it is a Cat
object or a Dog
object. Some examples:
- Declare an array of type
Animal
and still be able to storeDog
andCat
objects in it. - Define a method that takes an
Animal
object as a parameter and yet be able to passDog
andCat
objects to it. - Call a method on a
Dog
or aCat
object as if it is anAnimal
object (i.e., without knowing whether it is aDog
object or aCat
object) and get a different response from it based on its actual class e.g., call theAnimal
class' methodspeak()
on objecta
and get aMeow
as the return value ifa
is aCat
object andWoof
if it is aDog
object.
Polymorphism literally means "ability to take many forms".
Evidence:
Acceptable: Using polymorphism in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Polymorphism]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
W6.2f
Can use abstract classes and methods
C++ to Java → Inheritance →
Abstract Classes and Methods
In Java, an abstract method is declared with the keyword abstract
and given without an implementation. If a class includes abstract methods, then the class itself must be declared abstract.
The speak
method in this Animal
class is abstract
. Note how the method signature ends with a semicolon and there is no method body. This makes sense as
the implementation of the speak
method depends on the type of the animal and it is meaningless to provide a common implementation for all animal types.
public abstract class Animal {
protected String name;
public Animal(String name){
this.name = name;
}
public abstract String speak();
}
As one method of the class is abstract
, the class itself is abstract
.
An abstract class is declared with the keyword abstract
. Abstract classes can be used as reference type but cannot be instantiated.
This Account
class has been declared as abstract although it does not have any abstract methods. Attempting to instantiate Account
objects will result in a compile error.
public abstract class Account {
int number;
void close(){
//...
}
}
Account a;
OK to use as a type
a = new Account();
Compile error!
When an abstract class is subclassed, the subclass should provides implementations for all of the abstract methods in its superclass or else the subclass must also be declared abstract.
The Feline
class below inherits from the abstract class Animal
but it does not provide an implementation for the abstract method speak
. As a result, the
Feline
class needs to be abstract too.
public abstract class Feline extends Animal {
public Feline(String name) {
super(name);
}
}
The DomesticCat
class inherits the abstract Feline
class and provides the implementation for the abstract method speak
. As a result, it need not be declared abstract.
public class DomesticCat extends Feline {
public DomesticCat(String name) {
super(name);
}
@Override
public String speak() {
return "Meow";
}
}
Animal a = new Feline("Mittens");
Compile error! Feline
is abstract.
Animal a = new DomesticCat("Mittens");
OK. DomesticCat
can be instantiated and assigned
to a variable of Animal
type (the assignment is allowed by polymorphism).
The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
public class Main {
private static Shape[] shapes = new Shape[100];
private static int shapeCount = 0;
public static void addShape(Shape s){
shapes[shapeCount] = s;
shapeCount++;
}
public static void printAreas(){
for (int i = 0; i < shapeCount; i++){
shapes[i].print();
}
}
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
addShape(new Rectangle(4, 4));
printAreas();
}
}
Circle of area 78
Rectangle of area 12
Circle of area 314
Rectangle of area 16
Circle
class and Rectangle
class is given below:
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Shape
class as an abstract class with two abstract methods.
public abstract class Shape {
public abstract int area();
// ...
}
Choose the correct statements about Java abstract classes and
- a. A concrete class can contain an abstract method.
- b. An abstract class can contain concrete methods.
- c. An abstract class need not contain any concrete methods.
- d. An abstract class cannot be instantiated.
(b)(c)(d)
Explanation: A concrete class cannot contain even a single abstract method.
Evidence:
Acceptable: Using abstract classes in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Abstract]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
W6.2i
Can use interfaces in Java
C++ to Java → Inheritance →
Interfaces
The text given in this section borrows some explanations and code examples from the -- Java Tutorial.
In Java, an interface is a reference type, similar to a class, mainly containing method signatures. Defining an interface is similar to creating a new class except it uses the keyword interface
in place of class
.
Here is an interface named DrivableVehicle
that defines methods needed to drive a vehicle.
public interface DrivableVehicle {
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Note that the method signatures have no braces and are terminated with a semicolon.
Interfaces cannot be instantiated—they can only be implemented by classes. When an instantiable class implements an interface, indicated by the keyword implements
, it provides a method body for each
of the methods declared in the interface.
Here is how a class CarModelX
can implement the DrivableVehicle
interface.
public class CarModelX implements DrivableVehicle {
@Override
public void turn(Direction direction) {
// implementation
}
// implementation of other methods
}
An interface can be used as a type e.g., DrivableVechile dv = new CarModelX();
.
Interfaces can inherit from other interfaces using the extends
keyword, similar to a class inheriting another.
Here is an interface named SelfDrivableVehicle
that inherits the DrivableVehicle
interface.
public interface SelfDrivableVehicle extends DrivableVehicle {
void goToAutoPilotMode();
}
Note that the method signatures have no braces and are terminated with a semicolon.
Furthermore, Java allows multiple inheritance among interfaces. A Java interface can inherit multiple other interfaces. A Java class can implement multiple interfaces (and inherit from one class).
The design below is allowed by Java. In case you are not familiar with UML notation used: solid lines indicate normal inheritance; dashed lines indicate interface inheritance; the triangle points to the parent.
Staff
interface inherits (note the solid lines) the interfacesTaxPayer
andCitizen
.TA
class implements bothStudent
interface and theStaff
interface.- Because of point 1 above,
TA
class has to implement all methods in the interfacesTaxPayer
andCitizen
. - Because of points 1,2,3, a
TA
is aStaff
, is aTaxPayer
and is aCitizen
.
Interfaces can also contain
C++ to Java → Miscellaneous Topics →
Constants
Java does not directly support constants. The convention is to use a static
final
variable where a constant is needed. The static
modifier causes the variable to be available without
instantiating an object. The final
modifier causes the variable to be unchangeable. Java constants are normally declared in ALL CAPS separated by underscores.
Here is an example of a constant named MAX_BALANCE
which can be accessed as Account.MAX_BALANCE
.
public class Account{
public static final double MAX_BALANCE = 1000000.0;
}
Math.PI
is an example constant that comes with Java.
This example adds a constant MAX_SPEED
and a static method isSpeedAllowed
to the interface DrivableVehicle
.
public interface DrivableVehicle {
int MAX_SPEED = 150;
static boolean isSpeedAllowed(int speed){
return speed <= MAX_SPEED;
}
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Interfaces can contain default method implementations and nested types. They are not covered here.
The Main
class below passes a list of Printable
objects (i.e., objects that implement the Printable
interface) for another method to be printed.
public class Main {
public static void printObjects(Printable[] items) {
for (Printable p : items) {
p.print();
}
}
public static void main(String[] args) {
Printable[] printableItems = new Printable[]{
new Circle(5),
new Rectangle(3, 4),
new Person("James Cook")};
printObjects(printableItems);
}
}
Circle of area 78
Rectangle of area 12
Person of name James Cook
Classes Shape
, Circle
, and Rectangle
are given below:
public abstract class Shape {
public abstract int area();
}
public class Circle extends Shape implements Printable {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape implements Printable {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Printable
interface. Add the missing methods of the Person
class given below.
public class Person implements Printable {
private String name;
// todo: add missing methods
}
public interface Printable {
//...
}
Evidence:
Acceptable: Using interfaces in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-Interfaces]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
W6.3c
Can explain multi-level design
Design → Introduction →
Multi-Level Design
In a smaller system, design of the entire system can be shown in one place.
This class diagram of se-edu/addressbook-level3 depicts the design of the entire software.
Design of bigger systems needs to be done/shown at multiple levels.
This architecture diagram of se-edu/addressbook-level4 depicts the high-level design of the software.
Here are examples of lower level designs of some components of the same software:
Evidence:
Covered by the deliverable below (i.e. to be able to enhance an component of AddressBook-Level4, you need to be able to understand its multi-level design):
v1.1 Product
- Each member should try to add some enhancements that are in line with the vision for v2.0. Preferably, these should be
global-impact enhancements, touching as many other components as possible. Refer to the AddressBook-Level4 Developer Guide has some guidance on how to implement a new feature end-to-end.
W6.4d
Can identify event-driven architectural
style
Design → Architecture → Styles → Event-Driven Style
What
Event-driven style controls the flow of the application by detecting
When the ‘button clicked’ event occurs in a GUI, that event can be transmitted to components that are interested in reacting to that event. Similarly, events detected at a Printer port can be transmitted to components related to operating the Printer. The same event can be sent to multiple consumers too.
Evidence:
Explain how the AddressBook-Level4 uses Event-Driven style (refer to the Developer Guide for more info).
W6.4h
Can explain how architectural styles are
combined
Design → Architecture → Styles
Using Styles
Most applications use a mix of these architectural styles.
An application can use a client-server architecture where the server component comprises several layers, i.e. it uses the n-Tier architecture.
Assume you are designing a multiplayer version of the Minesweeper game where any number of players can play the same Minefield. Players use their own PCs to play the game. A player scores by deducing a cell correctly before any of the other players do. Once a cell is correctly deduced, it appears as either marked or cleared for all players.
Comment on how each of the following architectural styles could be potentially useful when designing the architecture for this game.
- Client-server
- Transaction-processing
- SOA (Service Oriented Architecture)
- multi-layer (n-tier)
- Client-server – Clients can be the game UI running on player PCs. The server can be the game logic running on one machine.
- Transaction-processing – Each player action can be packaged as transactions (by the client component running on the player PC) and sent to the server. Server processes them in the order they are received.
- SOA – The game can access a remote web services for things such as getting new puzzles, validating puzzles, charging players subscription fees, etc.
- Multi-layer – The server component can have two layers: logic layer and the storage layer.
Evidence:
Explain architectural styles currently used in AddressBook-Level4. Speculate under what circumstances other architectural styles can be used in it e.g. what kind of additional features can make a certain style applicable
W6.9b
Can explain build automation tools
Implementation → Integration → Build Automation →
What
Build automation tools automate the steps of the build process, usually by means of build scripts.
In a non-trivial project, building a product from source code can be a complex multi-step process. For example, it can include steps such as to pull code from the revision control system, compile, link, run automated tests, automatically update release documents (e.g. build number), package into a distributable, push to repo, deploy to a server, delete temporary files created during building/testing, email developers of the new build, and so on. Furthermore, this build process can be done ‘on demand’, it can be scheduled (e.g. every day at midnight) or it can be triggered by various events (e.g. triggered by a code push to the revision control system).
Some of these build steps such as to compile, link and package are already automated in most modern IDEs. For example, several steps happen automatically when the ‘build’ button of the IDE is clicked. Some IDEs even allow customization to this build process to some extent.
However, most big projects use specialized build tools to automate complex build processes.
Some popular build tools relevant to Java developers:
Some other build tools : Grunt (JavaScript), Rake (Ruby)
Some build tools also serve as dependency management tools. Modern software projects often depend on third party libraries that evolve constantly. That means developers need to download the correct version of the required libraries and update them regularly. Therefore, dependency management is an important part of build automation. Dependency Management tools can automate that aspect of a project.
Maven and Gradle, in addition to managing the build process, can play the role of dependency management tools too.
- Getting Started with Gradle -- Documentation from Gradle
- Gradle Tutorial -- from tutorialspoint.com
Gradle_is used used for,
- a. better revision control
- b. build automation
- c. UML diagramming
- d. project collaboration
(b)
Evidence:
- Explain what Gradle is and how it helps in the AddressBook project.
- Run various project tasks using Gradle as described in [AddressBook - Level 4: Using Gradle] e.g. run tests in headless mode
W6.9c
Can explain continuous integration and continuous
deployment
Implementation → Integration → Build Automation →
Continuous Integration and Continuous Deployment
An extreme application of build automation is called continuous integration (CI) in which integration, building, and testing happens automatically after each code change.
A natural extension of CI is Continuous Deployment (CD) where the changes are not only integrated continuously, but also deployed to end-users at the same time.
Some examples of CI/CD tools:
- Travis CI for Beginners -- Documentation from Travis
Evidence:
- Explain what Travis is and how it helps in the AddressBook project.
- One member: Set up Travis for your team repo by following [AddressBook - Level 4: Using Travis]
W6.10
Can contribute to project documentation
Covered by:
Lecture 6
Slides: Uploaded on IVLE.