Week 5 [Sep 10]
Todo
Admin info to read:
Policy on project work distribution
As most of the work is graded individually, it is OK to do less or more than equal share in your project team.
Related: [Admin: Project: Scope]
Policy on email response time
Normally, the prof will respond within 24 hours if it was an email sent to the prof or a forum post directed at the prof. If you don't get a response within that time, please feel free to remind the prof. It is likely that the prof did not notice your post or the email got stuck somewhere.
Similarly we expect you to check email regularly and respond to emails written to you personally (not mass email) promptly.
Not responding to a personal email is a major breach of professional etiquette (and general civility). Imagine how pissed off you would be if you met the prof along the corridor, said 'Hi prof, good morning' and the prof walked away without saying anything back. Not responding to a personal email is just as bad. Always take a few seconds to at least acknowledge such emails. It doesn't take long to type "Noted. Thanks" and hit 'send'.
The promptness of a reply is even more important when the email is requesting you for something that you cannot provide. Imagine you wrote to the prof requesting a reference letter and the prof did not respond at all because he/she did not want to give you one; You'll be quite frustrated because you wouldn't know whether to look for another prof or wait longer for a response. Saying 'No' is fine and in fact a necessary part of professional life; but saying nothing is not acceptable. If you didn't reply, the sender will not even know whether you received the email.
Why so much bean counting?
Sometimes, small things matter in big ways. e.g., all other things being equal, a job may be offered to the candidate who has the neater looking CV although both have the same qualifications. This may be unfair, but that's how the world works. Students forget this harsh reality when they are in the protected environment of the school and tend to get sloppy with their work habits. That is why we reward all positive behavior, even small ones (e.g., following precise submission instructions, arriving on time etc.).
But unlike the real world, we are forgiving. That is why you can still earn full 10 marks of the participation marks even if you miss a few things here and there.
Related article: This Is The Personality Trait That Most Often Predicts Success (this is why we reward things like punctuality).
Outcomes
Requirements
W5.1
Can apply basic product design guidelines
No textbook section. To be covered during the lecture.
Evidence:
Apply the discussed guidelines when designing the product (covered by v1.1 deliverables).
Design
W5.2
Can explain models
W5.2a
Can explain models
Design → Modelling → Introduction →
A model is a representation of something else.
A
A class diagram is a diagram drawn using the UML modelling notation.
An example class diagram:
A model provides a simpler view of a complex entity because a model captures only a selected aspect. This omission of some aspects implies models are
Design → Design Fundamentals → Abstraction →
Abstraction is a technique for dealing with complexity. It works by establishing a level of complexity we are interested in, and suppressing the more complex details below that level.
The guiding principle of abstraction is that only details that are relevant to the current perspective or the task at hand needs to be considered. As most programs are written to solve complex problems involving large amounts of intricate details, it is impossible to deal with all these details at the same time. That is where abstraction can help.
Ignoring lower level data items and thinking in terms of bigger entities is called data abstraction.
Within a certain software component, we might deal with a user data type, while ignoring the details contained in the user data item such as name, and date of birth. These details have been ‘abstracted away’ as they do not affect the task of that software component.
Control abstraction abstracts away details of the actual control flow to focus on tasks at a simplified level.
print(“Hello”)
is an abstraction of the actual output mechanism within the computer.
Abstraction can be applied repeatedly to obtain progressively higher levels of abstractions.
An example of different levels of data abstraction: a File
is a data item that is at a higher level than an array and an array is at a higher level than a bit.
An example of different levels of control abstraction: execute(Game)
is at a higher level than print(Char)
which is at a higher than an Assembly language instruction MOV
.
Abstraction is a general concept that is not limited to just data or control abstractions.
Some more general examples of abstraction:
- An OOP class is an abstraction over related data and behaviors.
- An architecture is a higher-level abstraction of the design of a software.
- Models (e.g., UML models) are abstractions of some aspect of reality.
A class diagram captures the structure of the software design but not the behavior.
Multiple models of the same entity may be needed to capture it fully.
In addition to a class diagram (or even multiple class diagrams), a number of other diagrams may be needed to capture various interesting aspects of the software.
W5.2b
Can explain how models are used
Design → Modelling → Introduction →
In software development, models are useful in several ways:
a) To analyze a complex entity related to software development.
Some examples of using models for analysis:
- Models of the problem domain (i.e. the environment in which the software is expected to solve a problem) can be built to aid the understanding of the problem to be solved.
- When planning a software solution, models can be created to figure out how the solution is to be built. An architecture diagram is such a model.
b) To communicate information among stakeholders. Models can be used as a visual aid in discussions and documentations.
Some examples of using models to communicate:
- We can use an
architecture diagram to explain the high-level design of the software to developers. - A business analyst can use a use case diagram to explain to the customer the functionality of the system.
- A class diagram can be reverse-engineered from code so as to help explain the design of a component to a new developer.
An architecture diagram depicts the high-level design of a software.
Some example architecture diagrams:
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
c) As a blueprint for creating software. Models can be used as instructions for building software.
Some examples of using models to as blueprints:
- A senior developer draws a class diagram to propose a design for an OOP software and passes it to a junior programmer to implement.
- A software tool allows users to draw UML models using its interface and the tool automatically generates the code based on the model.
Model-driven development (MDD), also called Model-driven engineering, is an approach to software development that strives to exploit models as blueprints. MDD uses models as primary engineering artifacts when developing software. That is, the system is first created in the form of models. After that, the models are converted to code using code-generation techniques (usually, automated or semi-automated, but can even involve manual translation from model to code). MDD requires the use of a very expressive modeling notation (graphical or otherwise), often specific to a given problem domain. It also requires sophisticated tools to generate code from models and maintain the link between models and the code. One advantage of MDD is that the same model can be used to create software for different platforms and different languages. MDD has a lot of promise, but it is still an emerging technology
Further reading:
- Martin Fowler's view on MDD - TLDR: he is sceptical
- 5 types of Model Driven Software Development - A more optimistic view, although an old article
Choose the correct statements about models.
- a. Models are abstractions.
- b. Models can be used for communication.
- c. Models can be used for analysis of a problem.
- d. Generating models from code is useless.
- e. Models can be used as blueprints for generating code.
(a) (b) (c) (e)
Explanation: Models generated from code can be used for understanding, analysing, and communicating about the code.
Explain how models (e.g. UML diagrams) can be used in a class project.
Can models be useful in evaluating the design quality of a software written by students?
Evidence:
Explain how models (e.g. UML diagrams) can be used in a class project.
Can models be useful in evaluating the design quality of a software written by students?
W5.3
Can explain basic object/class structures
W5.3a
Can explain structure modelling of OO solutions
Design → Modelling → Modelling Structure
An OO solution is basically a network of objects interacting with each other. Therefore, it is useful to be able to model how the relevant objects are 'networked' together inside a software i.e. how the objects are connected together.
Given below is an illustration of some objects and how they are connected together. Note: the diagram uses an ad-hoc notation.
Note that these object structures within the same software can change over time.
Given below is how the object structure in the previous example could have looked like at a different time.
However, object structures do not change at random; they change based on a set of rules, as was decided by the designer of that software. Those rules that object structures need to follow can be illustrated as a class structure i.e. a structure that exists among the relevant classes.
Here is a class structure (drawn using an ad-hoc notation) that matches the object structures given in the previous two examples. For example, note how this class structure does not allow any connection between Genre
objects and Author
objects, a rule followed by the two object structures above.
UML Object Diagrams are used to model object structures and UML Class Diagrams are used to model class structures of an OO solution.
Here is an object diagram for the above example:
And here is the class diagram for it:
W5.3b
Can use basic-level class diagrams
Design → Modelling → Modelling Structure
Classes form the basis of class diagrams.
Associations are the main connections among the classes in a class diagram.
The most basic class diagram is a bunch of classes with some solid lines among them to represent associations, such as this one.
An example class diagram showing associations between classes.
In addition, associations can show additional decorations such as association labels, association roles, multiplicity and navigability to add more information to a class diagram.
Here is the same class diagram shown earlier but with some additional information included:
Which association notatations are shown in this diagram?
- a. association labels
- b. association roles
- c. association multiplicity
- d. class names
(a) (b) (c) (d)
Explanation: '1’ is a multiplicity, ‘mentored by’ is a label, and ‘mentor’ is a role.
Explain the associations, navigabilities, and multiplicities in the class diagram below:
Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities in the class diagram below:
class Box {
private Item[] parts = new Item[10];
private Item spareItem;
private Lid lid; // lid of this box
private Box outerBox;
public void open(){
//...
}
}
class Item {
public static int totalItems;
}
class Lid {
Box box; // the box for which this is the lid
}
Evidence:
Explain the associations, navigabilities, and multiplicities in the class diagram below:
W5.3c
Can use basic object diagrams
Design → Modelling → Modelling Structure
Object diagrams can be used to complement class diagrams. For example, you can use object diagrams to model different object structures that can result from a design represented by a given class diagram.
This question is based on the following question from another topic:
Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities in the class diagram below:
class Box {
private Item[] parts = new Item[10];
private Item spareItem;
private Lid lid; // lid of this box
private Box outerBox;
public void open(){
//...
}
}
class Item {
public static int totalItems;
}
class Lid {
Box box; // the box for which this is the lid
}
Draw an object diagram to match the code. Include objects of all three classes in your object diagram.
Evidence:
Suppose we wrote a program to follow the class structure given in this class diagram:
Draw object diagrams to represent the object structures after each of these steps below. Assume that we are trying to minimize the number of total objects.
i.e. apply step 1 → [diagram 1] → apply step 2 on diagram 1 → [diagram 2] and so on.
-
There are no persons.
-
Alfred
is the Guardian ofBruce
. -
Bruce
's contact number is the same asAlfred
's. -
Alfred
is also the guardian of another person. That person listsAlfred
s home address as his home address as well as office address. -
Alfred
has a an office address atWayne Industries
building which is different from his home address (i.e.Bat Cave
).
After step 2, the diagram should be like this:
W5.3d
Can distinguish between class diagrams and object diagrams
Tools → UML →
Compared to the notation for a class diagrams, object diagrams differ in the following ways:
- Shows objects instead of classes:
- Instance name may be shown
- There is a
:
before the class name - Instance and class names are underlined
- Methods are omitted
- Multiplicities are omitted
Furthermore, multiple object diagrams can correspond to a single class diagram.
Both object diagrams are derived from the same class diagram shown earlier. In other words, each of these object diagrams shows ‘an instance of’ the same class diagram.
Which of these class diagrams match the given object diagram?
- a
- b
(a) (b)
Explanation: Both class diagrams allow one Unit
object to be linked to one Item
object.
Implementation
W5.4
Can implement polymorphism
Method Overriding
W5.4a
Can explain method overloading :
Paradigms → Object Oriented Programming → Inheritance →
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
W5.4b
Can explain method overriding :
Paradigms → Object Oriented Programming → Inheritance →
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 →
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
W5.4c
Can explain OOP polymorphism :
Paradigms → Object Oriented Programming → Polymorphism →
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".
W5.4d
Can use polymorphism in Java :
C++ to Java → Inheritance →
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 →
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
W5.4e
Can implement abstract classes :
Paradigms → Object Oriented Programming → Inheritance →
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.
Even a class that does not have any abstract methods can be declared as an abstract class.
W5.4f
Can use abstract classes and methods :
C++ to Java → Inheritance →
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.
Interfaces
W5.4g
Can explain interfaces :
Paradigms → Object Oriented Programming → Inheritance →
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()
.
W5.4h
Can use interfaces in Java :
C++ to Java → Inheritance →
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 →
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 {
//...
}
Project Management
W5.5
Can use GitHub PRs in a workflow
W5.5a
Can use Git to resolve merge conflicts
Tools → Git and GitHub →
1. Start a branch named fix1
in a local repo. Create a commit that adds a line with some text to one of the files.
2. Switch back to master
branch. Create a commit with a conflicting change i.e. it adds a line with some different text in the exact location the previous line was added.
3. Try to merge the fix1
branch onto the master
branch. Git will pause mid-way during the merge and report a merge conflict. If you open the conflicted file, you will see something like this:
COLORS
------
blue
<<<<<<< HEAD
black
=======
green
>>>>>>> fix1
red
white
4. Observe how the conflicted part is marked between a line starting with <<<<<<<
and a line starting with >>>>>>>
, separated by another line starting with =======
.
This is the conflicting part that is coming from the master
branch:
<<<<<<< HEAD
black
=======
This is the conflicting part that is coming from the fix1
branch:
=======
green
>>>>>>> fix1
5. Resolve the conflict by editing the file. Let us assume you want to keep both lines in the merged version. You can modify the file to be like this:
COLORS
------
blue
black
green
red
white
6. Stage the changes, and commit.
Evidence:
Acceptable: Merge conflicts resolved in any repo.
Suggested: Evidence of following the steps in the LO.
Submission: Show your merge commit during the tutorial.
W5.5b
Can review and merge PRs on GitHub
Tools → Git and GitHub →
1. Go to GitHub page of your fork and review the add-intro
PR you created previously in [
1. Fork the samplerepo-pr-practice onto your GitHub account. Clone it onto your computer.
2. Create a branch named add-intro
in your clone. Add a couple of commits which adds/modifies an Introduction section to the README.md
. Example:
# Introduction
Creating Pull Requsts (PRs) is needed when using RCS in a multi-person projects.
This repo can be used to practice creating PRs.
3. Push the add-intro
branch to your fork.
git push origin add-intro
4. Create a Pull Request from the add-intro
branch in your fork to the master
branch of the same fork (i.e. your-user-name/samplerepo-pr-practice
, not se-edu/samplerepo-pr-practice
), as described below.
4a. Go to the GitHub page of your fork (i.e. https://github.com/{your_username}/samplerepo-pr-practice
), click on the Pull Requests
tab, and then click on New Pull Request
button.
4b. Select base fork
and head fork
as follows:
base fork
: your own fork (i.e.{your user name}/samplerepo-pr-practice
, NOTse-edu/samplerepo-pr-practice
)head fork
: your own fork.
The base fork is where changes should be applied. The head fork contains the changes you would like to be applied.
4c. (1) Set the base branch to master
and head branch to add-intro
, (2) confirm the diff contains the changes you propose to merge in this PR (i.e. confirm that you did not accidentally include extra commits in the branch), and (3) click the Create pull request
button.
4d. (1) Set PR name, (2) set PR description, and (3) Click the Create pull request
button.
A common newbie mistake when creating branch-based PRs is to mix commits of one PR with another. To learn how to avoid that mistake, you are encouraged to continue and create another PR as explained below.
5. In your local repo, create a new branch add-summary
off the master
branch.
When creating the new branch, it is very important that you switch back to the master
branch first. If not, the new branch will be created off the current branch add-intro
. And that is how you end up having commits of the first PR in the second PR as well.
6. Add a commit in the add-summary
branch that adds a Summary section to the README.md
, in exactly the same place you added the Introduction section earlier.
7. Push the add-summary
to your fork and create a new PR similar to before.
1a. Go to the respective PR page and click on the Files changed
tab. Hover over the line you want to comment on and click on the icon that appears on the left margin. That should create a text box for you to enter your comment.
1b. Enter some dummy comment and click on Start a review
button.
1c. Add a few more comments in other places of the code.
1d. Click on the Review Changes
button, enter an overall comment, and click on the Submit review
button.
2. Update the PR to simulate revising the code based on reviewer comments. Add some more commits to the add-intro
branch and push the new commits to the fork. Observe how the PR is updated automatically to reflect the new code.
3. Merge the PR. Go to the GitHub page of the respective PR, scroll to the bottom of the Conversation
tab, and click on the Merge pull request
button, followed by the Confirm merge
button. You should see a Pull request successfully merged and closed
message after the PR is merged.
4. Sync the local repo with the remote repo. Because of the merge you did on the GitHub, the master
branch of your fork is now ahead of your local repo by one commit. To sync the local repo with the remote repo, pull the master
branch to the local repo.
git checkout master
git pull origin master
Observe how the add-intro
branch is now merged to the master
branch in your local repo as well.
5. De-conflict the add-summary
PR
add-summary
PR is now showing a conflict (when you scroll to the bottom of that page, you should see a message This branch has conflicts that must be resolved
). You can resolve it locally and update the PR accordingly, as explained below.
5a. Switch to the add-summary
branch. To make that branch up-to-date with the master
branch, merge the master
branch to it, which will surface the merge conflict. Resolve it and complete the merge.
5b. Push the updated add-summary
branch to the fork. That will remove the 'merge conflicts' warning in the GitHub page of the PR.
6. Merge the add-summary
PR using the GitHub interface, similar to how you merged the previous PR.
Note that you could have merged the add-summary
branch to the master
branch locally before pushing it to GitHub. In that case, the PR will be merged on GitHub automatically to reflect that the branch has been merged already.
Evidence:
Acceptable: PRs you merged in any repo.
Suggested: Evidence of following the steps in the LO.
Submission: Show your merged PRs during the tutorial.
W5.6
Can follow Forking Workflow
W5.6a
Can explain forking workflow
Project Management → Revision Control →
In the forking workflow, the 'official' version of the software is kept in a remote repo designated as the 'main repo'. All team members fork the main repo create pull requests from their fork to the main repo.
To illustrate how the workflow goes, let’s assume Jean wants to fix a bug in the code. Here are the steps:
- Jean creates a separate branch in her local repo and fixes the bug in that branch.
- Jean pushes the branch to her fork.
- Jean creates a pull request from that branch in her fork to the main repo.
- Other members review Jean’s pull request.
- If reviewers suggested any changes, Jean updates the PR accordingly.
- When reviewers are satisfied with the PR, one of the members (usually the team lead or a designated 'maintainer' of the main repo) merges the PR, which brings Jean’s code to the main repo.
- Other members, realizing there is new code in the upstream repo, sync their forks with the new upstream repo (i.e. the main repo). This is done by pulling the new code to their own local repo and pushing the updated code to their own fork.
- A detailed explanation of the Forking Workflow - From Atlassian
W5.6b
Can follow Forking Workflow
Tools → Git and GitHub →
This activity is best done as a team. If you are learning this alone, you can simulate a team by using two different browsers to log into GitHub using two different accounts.
-
One member: set up the team org and the team repo.
- Create a GitHub organization for your team. The org name is up to you. We'll refer to this organization as team org from now on.
- Add a team called
developers
to your team org. - Add your team members to the
developers
team. - Fork se-edu/samplerepo-workflow-practice to your team org. We'll refer to this as the team repo.
- Add the forked repo to the
developers
team. Give write access.
-
Each team member: create PRs via own fork
- Fork that repo from your team org to your own GitHub account.
- Create a PR to add a file
yourName.md
(e.g.jonhDoe.md
) containing a brief resume of yourself (branch → commit → push → create PR)
-
For each PR: review, update, and merge.
- A team member (not the PR author): Review the PR by adding comments (can be just dummy comments).
- PR author: Update the PR by pushing more commits to it, to simulate updating the PR based on review comments.
- Another team member: Merge the PR using the GitHub interface.
- All members: Sync your local repo (and your fork) with upstream repo. In this case, your upstream repo is the repo in your team org.
-
Create conflicting PRs.
- Each team member: Create a PR to add yourself under the
Team Members
section in theREADME.md
. - One member: in the
master
branch, remove John Doe and Jane Doe from theREADME.md
, commit, and push to the main repo.
- Each team member: Create a PR to add yourself under the
-
Merge conflicting PRs one at a time. Before merging a PR, you’ll have to resolve conflicts. Steps:
- [Optional] A member can inform the PR author (by posting a comment) that there is a conflict in the PR.
- PR author: Pull the
master
branch from the repo in your team org. Merge the pulledmaster
branch to your PR branch. Resolve the merge conflict that crops up during the merge. Push the updated PR branch to your fork. - Another member or the PR author: When GitHub does not indicate a conflict anymore, you can go ahead and merge the PR.
Evidence:
Acceptable: Evidence of following the forking workflow with the current team members using any repo.
Suggested: Evidence of following the steps in the LO with current team members.
Submission: Show during the tutorial.
W5.6c
Can explain DRCS vs CRCS
Project Management → Revision Control →
RCS can be done in two ways: the centralized way and the distributed way.
Centralized RCS (CRCS for short)uses a central remote repo that is shared by the team. Team members download (‘pull’) and upload (‘push’) changes between their own local repositories and the central repository. Older RCS tools such as CVS and SVN support only this model. Note that these older RCS do not support the notion of a local repo either. Instead, they force users to do all the versioning with the remote repo.
The centralized RCS approach without any local repos (e.g., CVS, SVN)
Distributed RCS (DRCS for short, also known as Decentralized RCS) allows multiple remote repos and pulling and pushing can be done among them in arbitrary ways. The workflow can vary differently from team to team. For example, every team member can have his/her own remote repository in addition to their own local repository, as shown in the diagram below. Git and Mercurial are some prominent RCS tools that support the distributed approach.
The decentralized RCS approach
W5.6d
Can explain feature branch flow
Project Management → Revision Control →
Feature branch workflow is similar to forking workflow except there are no forks. Everyone is pushing/pulling from the same remote repo. The phrase feature branch is used because each new feature (or bug fix, or any other modification) is done in a separate branch and merged to master
branch when ready.
- A detailed explanation of the Feature Branch Workflow - From Atlassian
W5.6e
Can explain centralized flow
Project Management → Revision Control →
The centralized workflow is similar to the feature branch workflow except all changes are done in the master
branch.
- A detailed explanation of the Centralized Workflow - From Atlassian
🅿️ Project
W5.7
Can work with a 2KLoC code base
This LO can earn you 3 participation marks, 2 mark for the individual component and 1 bonus mark for the team component. You can omit either one of them.
💡 When working with existing code, a safe approach is to change the code in very small steps, each resulting in a verifiable change without breaking the app. For example, when adding a new sort
command, the first few steps can be,
- Teach the app to accept a
sort
command but ignore it. - Next, teach the app to direct the
sort
command to an existing command e.g.sort
command simply invokes thelist
command internally. - Add a
SortCommand
class but make it simply a copy of the the existingListCommand
. Direct thesort
command to the newSortCommand
. - ...
💡 Note that you can reuse the code you write here in your final project, if applicable.
Individual component:
Requirements: Do an enhancement to [AddressBook - Level2] e.g. add a new command. It can be the same enhancement you did to AddressBook Level1 (at the 1KLoC milestone in week 3). The size of the enhancement does not matter but try to limit to one enhancement (rather than mix many enhancements). In addition,
- update the User Guide
- update existing tests and add new tests if necessary, for both JUnit tests and I/O tests
- follow the coding standard
- follow the OOP style
Optional but encouraged:
- Update the Developer Guide
Submission: Create a PR against Addressbook-Level2. Try to make a clean PR (i.e. free of unrelated code modifications).
Team component:
The team component is to be done by all members, including those who didn't do the individual component.
-
Review PRs created by team members in the Individual Component above i.e. add review comments in the PR created against module repo. You can either give suggestions to improve, or ask questions to understand, the code written by the team member.
-
Requirements: Try to ensure that each PR reviewed by at least one team member and each team member's PR is reviewed by at least one other team member.
-
Submission: Just update PR created in the individual component by adding comments/commits to it.
W5.8
Can conceptualize a product
Covered by:
Tutorial 5
Suggested question to discuss:
Explain the associations, navigabilities, and multiplicities in the class diagram below:
Suppose we wrote a program to follow the class structure given in this class diagram:
Draw object diagrams to represent the object structures after each of these steps below. Assume that we are trying to minimize the number of total objects.
i.e. apply step 1 → [diagram 1] → apply step 2 on diagram 1 → [diagram 2] and so on.
-
There are no persons.
-
Alfred
is the Guardian ofBruce
. -
Bruce
's contact number is the same asAlfred
's. -
Alfred
is also the guardian of another person. That person listsAlfred
s home address as his home address as well as office address. -
Alfred
has a an office address atWayne Industries
building which is different from his home address (i.e.Bat Cave
).
After step 2, the diagram should be like this:
W5.1
Can apply basic product design guidelines
No textbook section. To be covered during the lecture.
Evidence:
Apply the discussed guidelines when designing the product (covered by v1.1 deliverables).
W5.2b
Can explain how models are used
Design → Modelling → Introduction →
In software development, models are useful in several ways:
a) To analyze a complex entity related to software development.
Some examples of using models for analysis:
- Models of the problem domain (i.e. the environment in which the software is expected to solve a problem) can be built to aid the understanding of the problem to be solved.
- When planning a software solution, models can be created to figure out how the solution is to be built. An architecture diagram is such a model.
b) To communicate information among stakeholders. Models can be used as a visual aid in discussions and documentations.
Some examples of using models to communicate:
- We can use an
architecture diagram to explain the high-level design of the software to developers. - A business analyst can use a use case diagram to explain to the customer the functionality of the system.
- A class diagram can be reverse-engineered from code so as to help explain the design of a component to a new developer.
An architecture diagram depicts the high-level design of a software.
Some example architecture diagrams:
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
source: https://commons.wikimedia.org
c) As a blueprint for creating software. Models can be used as instructions for building software.
Some examples of using models to as blueprints:
- A senior developer draws a class diagram to propose a design for an OOP software and passes it to a junior programmer to implement.
- A software tool allows users to draw UML models using its interface and the tool automatically generates the code based on the model.
Model-driven development (MDD), also called Model-driven engineering, is an approach to software development that strives to exploit models as blueprints. MDD uses models as primary engineering artifacts when developing software. That is, the system is first created in the form of models. After that, the models are converted to code using code-generation techniques (usually, automated or semi-automated, but can even involve manual translation from model to code). MDD requires the use of a very expressive modeling notation (graphical or otherwise), often specific to a given problem domain. It also requires sophisticated tools to generate code from models and maintain the link between models and the code. One advantage of MDD is that the same model can be used to create software for different platforms and different languages. MDD has a lot of promise, but it is still an emerging technology
Further reading:
- Martin Fowler's view on MDD - TLDR: he is sceptical
- 5 types of Model Driven Software Development - A more optimistic view, although an old article
Choose the correct statements about models.
- a. Models are abstractions.
- b. Models can be used for communication.
- c. Models can be used for analysis of a problem.
- d. Generating models from code is useless.
- e. Models can be used as blueprints for generating code.
(a) (b) (c) (e)
Explanation: Models generated from code can be used for understanding, analysing, and communicating about the code.
Explain how models (e.g. UML diagrams) can be used in a class project.
Can models be useful in evaluating the design quality of a software written by students?
Evidence:
Explain how models (e.g. UML diagrams) can be used in a class project.
Can models be useful in evaluating the design quality of a software written by students?
W5.3b
Can use basic-level class diagrams
Design → Modelling → Modelling Structure
Classes form the basis of class diagrams.
Associations are the main connections among the classes in a class diagram.
The most basic class diagram is a bunch of classes with some solid lines among them to represent associations, such as this one.
An example class diagram showing associations between classes.
In addition, associations can show additional decorations such as association labels, association roles, multiplicity and navigability to add more information to a class diagram.
Here is the same class diagram shown earlier but with some additional information included:
Which association notatations are shown in this diagram?
- a. association labels
- b. association roles
- c. association multiplicity
- d. class names
(a) (b) (c) (d)
Explanation: '1’ is a multiplicity, ‘mentored by’ is a label, and ‘mentor’ is a role.
Explain the associations, navigabilities, and multiplicities in the class diagram below:
Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities in the class diagram below:
class Box {
private Item[] parts = new Item[10];
private Item spareItem;
private Lid lid; // lid of this box
private Box outerBox;
public void open(){
//...
}
}
class Item {
public static int totalItems;
}
class Lid {
Box box; // the box for which this is the lid
}
Evidence:
Explain the associations, navigabilities, and multiplicities in the class diagram below:
W5.3c
Can use basic object diagrams
Design → Modelling → Modelling Structure
Object diagrams can be used to complement class diagrams. For example, you can use object diagrams to model different object structures that can result from a design represented by a given class diagram.
This question is based on the following question from another topic:
Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities in the class diagram below:
class Box {
private Item[] parts = new Item[10];
private Item spareItem;
private Lid lid; // lid of this box
private Box outerBox;
public void open(){
//...
}
}
class Item {
public static int totalItems;
}
class Lid {
Box box; // the box for which this is the lid
}
Draw an object diagram to match the code. Include objects of all three classes in your object diagram.
Evidence:
Suppose we wrote a program to follow the class structure given in this class diagram:
Draw object diagrams to represent the object structures after each of these steps below. Assume that we are trying to minimize the number of total objects.
i.e. apply step 1 → [diagram 1] → apply step 2 on diagram 1 → [diagram 2] and so on.
-
There are no persons.
-
Alfred
is the Guardian ofBruce
. -
Bruce
's contact number is the same asAlfred
's. -
Alfred
is also the guardian of another person. That person listsAlfred
s home address as his home address as well as office address. -
Alfred
has a an office address atWayne Industries
building which is different from his home address (i.e.Bat Cave
).
After step 2, the diagram should be like this:
W5.5a
Can use Git to resolve merge conflicts
Tools → Git and GitHub →
1. Start a branch named fix1
in a local repo. Create a commit that adds a line with some text to one of the files.
2. Switch back to master
branch. Create a commit with a conflicting change i.e. it adds a line with some different text in the exact location the previous line was added.
3. Try to merge the fix1
branch onto the master
branch. Git will pause mid-way during the merge and report a merge conflict. If you open the conflicted file, you will see something like this:
COLORS
------
blue
<<<<<<< HEAD
black
=======
green
>>>>>>> fix1
red
white
4. Observe how the conflicted part is marked between a line starting with <<<<<<<
and a line starting with >>>>>>>
, separated by another line starting with =======
.
This is the conflicting part that is coming from the master
branch:
<<<<<<< HEAD
black
=======
This is the conflicting part that is coming from the fix1
branch:
=======
green
>>>>>>> fix1
5. Resolve the conflict by editing the file. Let us assume you want to keep both lines in the merged version. You can modify the file to be like this:
COLORS
------
blue
black
green
red
white
6. Stage the changes, and commit.
Evidence:
Acceptable: Merge conflicts resolved in any repo.
Suggested: Evidence of following the steps in the LO.
Submission: Show your merge commit during the tutorial.
W5.5b
Can review and merge PRs on GitHub
Tools → Git and GitHub →
1. Go to GitHub page of your fork and review the add-intro
PR you created previously in [
1. Fork the samplerepo-pr-practice onto your GitHub account. Clone it onto your computer.
2. Create a branch named add-intro
in your clone. Add a couple of commits which adds/modifies an Introduction section to the README.md
. Example:
# Introduction
Creating Pull Requsts (PRs) is needed when using RCS in a multi-person projects.
This repo can be used to practice creating PRs.
3. Push the add-intro
branch to your fork.
git push origin add-intro
4. Create a Pull Request from the add-intro
branch in your fork to the master
branch of the same fork (i.e. your-user-name/samplerepo-pr-practice
, not se-edu/samplerepo-pr-practice
), as described below.
4a. Go to the GitHub page of your fork (i.e. https://github.com/{your_username}/samplerepo-pr-practice
), click on the Pull Requests
tab, and then click on New Pull Request
button.
4b. Select base fork
and head fork
as follows:
base fork
: your own fork (i.e.{your user name}/samplerepo-pr-practice
, NOTse-edu/samplerepo-pr-practice
)head fork
: your own fork.
The base fork is where changes should be applied. The head fork contains the changes you would like to be applied.
4c. (1) Set the base branch to master
and head branch to add-intro
, (2) confirm the diff contains the changes you propose to merge in this PR (i.e. confirm that you did not accidentally include extra commits in the branch), and (3) click the Create pull request
button.
4d. (1) Set PR name, (2) set PR description, and (3) Click the Create pull request
button.
A common newbie mistake when creating branch-based PRs is to mix commits of one PR with another. To learn how to avoid that mistake, you are encouraged to continue and create another PR as explained below.
5. In your local repo, create a new branch add-summary
off the master
branch.
When creating the new branch, it is very important that you switch back to the master
branch first. If not, the new branch will be created off the current branch add-intro
. And that is how you end up having commits of the first PR in the second PR as well.
6. Add a commit in the add-summary
branch that adds a Summary section to the README.md
, in exactly the same place you added the Introduction section earlier.
7. Push the add-summary
to your fork and create a new PR similar to before.
1a. Go to the respective PR page and click on the Files changed
tab. Hover over the line you want to comment on and click on the icon that appears on the left margin. That should create a text box for you to enter your comment.
1b. Enter some dummy comment and click on Start a review
button.
1c. Add a few more comments in other places of the code.
1d. Click on the Review Changes
button, enter an overall comment, and click on the Submit review
button.
2. Update the PR to simulate revising the code based on reviewer comments. Add some more commits to the add-intro
branch and push the new commits to the fork. Observe how the PR is updated automatically to reflect the new code.
3. Merge the PR. Go to the GitHub page of the respective PR, scroll to the bottom of the Conversation
tab, and click on the Merge pull request
button, followed by the Confirm merge
button. You should see a Pull request successfully merged and closed
message after the PR is merged.
4. Sync the local repo with the remote repo. Because of the merge you did on the GitHub, the master
branch of your fork is now ahead of your local repo by one commit. To sync the local repo with the remote repo, pull the master
branch to the local repo.
git checkout master
git pull origin master
Observe how the add-intro
branch is now merged to the master
branch in your local repo as well.
5. De-conflict the add-summary
PR
add-summary
PR is now showing a conflict (when you scroll to the bottom of that page, you should see a message This branch has conflicts that must be resolved
). You can resolve it locally and update the PR accordingly, as explained below.
5a. Switch to the add-summary
branch. To make that branch up-to-date with the master
branch, merge the master
branch to it, which will surface the merge conflict. Resolve it and complete the merge.
5b. Push the updated add-summary
branch to the fork. That will remove the 'merge conflicts' warning in the GitHub page of the PR.
6. Merge the add-summary
PR using the GitHub interface, similar to how you merged the previous PR.
Note that you could have merged the add-summary
branch to the master
branch locally before pushing it to GitHub. In that case, the PR will be merged on GitHub automatically to reflect that the branch has been merged already.
Evidence:
Acceptable: PRs you merged in any repo.
Suggested: Evidence of following the steps in the LO.
Submission: Show your merged PRs during the tutorial.
W5.6b
Can follow Forking Workflow
Tools → Git and GitHub →
This activity is best done as a team. If you are learning this alone, you can simulate a team by using two different browsers to log into GitHub using two different accounts.
-
One member: set up the team org and the team repo.
- Create a GitHub organization for your team. The org name is up to you. We'll refer to this organization as team org from now on.
- Add a team called
developers
to your team org. - Add your team members to the
developers
team. - Fork se-edu/samplerepo-workflow-practice to your team org. We'll refer to this as the team repo.
- Add the forked repo to the
developers
team. Give write access.
-
Each team member: create PRs via own fork
- Fork that repo from your team org to your own GitHub account.
- Create a PR to add a file
yourName.md
(e.g.jonhDoe.md
) containing a brief resume of yourself (branch → commit → push → create PR)
-
For each PR: review, update, and merge.
- A team member (not the PR author): Review the PR by adding comments (can be just dummy comments).
- PR author: Update the PR by pushing more commits to it, to simulate updating the PR based on review comments.
- Another team member: Merge the PR using the GitHub interface.
- All members: Sync your local repo (and your fork) with upstream repo. In this case, your upstream repo is the repo in your team org.
-
Create conflicting PRs.
- Each team member: Create a PR to add yourself under the
Team Members
section in theREADME.md
. - One member: in the
master
branch, remove John Doe and Jane Doe from theREADME.md
, commit, and push to the main repo.
- Each team member: Create a PR to add yourself under the
-
Merge conflicting PRs one at a time. Before merging a PR, you’ll have to resolve conflicts. Steps:
- [Optional] A member can inform the PR author (by posting a comment) that there is a conflict in the PR.
- PR author: Pull the
master
branch from the repo in your team org. Merge the pulledmaster
branch to your PR branch. Resolve the merge conflict that crops up during the merge. Push the updated PR branch to your fork. - Another member or the PR author: When GitHub does not indicate a conflict anymore, you can go ahead and merge the PR.
Evidence:
Acceptable: Evidence of following the forking workflow with the current team members using any repo.
Suggested: Evidence of following the steps in the LO with current team members.
Submission: Show during the tutorial.
W5.7
Can work with a 2KLoC code base
This LO can earn you 3 participation marks, 2 mark for the individual component and 1 bonus mark for the team component. You can omit either one of them.
💡 When working with existing code, a safe approach is to change the code in very small steps, each resulting in a verifiable change without breaking the app. For example, when adding a new sort
command, the first few steps can be,
- Teach the app to accept a
sort
command but ignore it. - Next, teach the app to direct the
sort
command to an existing command e.g.sort
command simply invokes thelist
command internally. - Add a
SortCommand
class but make it simply a copy of the the existingListCommand
. Direct thesort
command to the newSortCommand
. - ...
💡 Note that you can reuse the code you write here in your final project, if applicable.
Individual component:
Requirements: Do an enhancement to [AddressBook - Level2] e.g. add a new command. It can be the same enhancement you did to AddressBook Level1 (at the 1KLoC milestone in week 3). The size of the enhancement does not matter but try to limit to one enhancement (rather than mix many enhancements). In addition,
- update the User Guide
- update existing tests and add new tests if necessary, for both JUnit tests and I/O tests
- follow the coding standard
- follow the OOP style
Optional but encouraged:
- Update the Developer Guide
Submission: Create a PR against Addressbook-Level2. Try to make a clean PR (i.e. free of unrelated code modifications).
Team component:
The team component is to be done by all members, including those who didn't do the individual component.
-
Review PRs created by team members in the Individual Component above i.e. add review comments in the PR created against module repo. You can either give suggestions to improve, or ask questions to understand, the code written by the team member.
-
Requirements: Try to ensure that each PR reviewed by at least one team member and each team member's PR is reviewed by at least one other team member.
-
Submission: Just update PR created in the individual component by adding comments/commits to it.
W5.8
Can conceptualize a product
Covered by: