This site is from a past semester! The current version will be here when the new semester starts.

Week 11 [Fri, Mar 24th] - Topics

Detailed Table of Contents

Guidance for the item(s) below:

Previously, you learned about design patterns, and a some example design patterns. Let's continue that journey this week.

[W11.1] More Design Patterns

Guidance for the item(s) below:

First, let's learn two more widely-cited design patterns.


Design → Design Patterns → MVC Pattern → What


Can explain the Model View Controller (MVC) design pattern


Most applications support storage/retrieval of information, displaying of information to the user (often via multiple UIs having different formats), and changing stored information based on external inputs.


The high coupling that can result from the interlinked nature of the features described above.


Decouple data, presentation, and control logic of an application by separating them into three different components: Model, View and Controller.

  • View: Displays data, interacts with the user, and pulls data from the model if necessary.
  • Controller: Detects UI events such as mouse clicks and button pushes, and takes follow up action. Updates/changes the model/view when necessary.
  • Model: Stores and maintains data. Updates the view if necessary.

The relationship between the components can be observed in the diagram below. Typically, the UI is the combination of View and Controller.

Given below is a concrete example of MVC applied to a student management system. In this scenario, the user is retrieving the data of a student.

In the diagram above, when the user clicks on a button using the UI, the ‘click’ event is caught and handled by the UiController. The ref frame indicates that the interactions within that frame have been extracted out to another separate sequence diagram.

Note that in a simple UI where there’s only one view, Controller and View can be combined as one class.

There are many variations of the MVC model used in different domains. For example, the one used in a desktop GUI could be different from the one used in a web application.


Design → Design Patterns → Observer Pattern → What

Video Q+

Can explain the Observer design pattern


An object (possibly more than one) is interested in being notified when a change happens to another object. That is, some objects want to ‘observe’ another object.

Consider this scenario from a student management system where the user is adding a new student to the system.

Now, assume the system has two additional views used in parallel by different users:

  • StudentListUi: that accesses a list of students and
  • StudentStatsUi: that generates statistics of current students.

When a student is added to the database using NewStudentUi shown above, both StudentListUi and StudentStatsUi should get updated automatically, as shown below.

However, the StudentList object has no knowledge about StudentListUi and StudentStatsUi (note the direction of the navigability) and has no way to inform those objects. This is an example of the type of problem addressed by the Observer pattern.


The ‘observed’ object does not want to be coupled to objects that are ‘observing’ it.


Force the communication through an interface known to both parties.

Here is the Observer pattern applied to the student management system.

During the initialization of the system,

  1. First, create the relevant objects.

    StudentList studentList = new StudentList();
    StudentListUi listUi = new StudentListUi();
    StudentStatsUi statsUi = new StudentStatsUi();
  2. Next, the two UIs indicate to the StudentList that they are interested in being updated whenever StudentList changes. This is also known as ‘subscribing for updates’.

  3. Within the addUi operation of StudentList, all Observer object subscribers are added to an internal data structure called observerList.

    // StudentList class
    public void addUi(Observer o) {

Now, whenever the data in StudentList changes (e.g. when a new student is added to the StudentList),

  1. All interested observers are updated by calling the notifyUIs operation.

    // StudentList class
    public void notifyUIs() {
        // for each observer in the list
        for (Observer o: observerList) {
  2. UIs can then pull data from the StudentList whenever the update operation is called.

    // StudentListUI class
    public void update() {
        // refresh UI by pulling data from StudentList

    Note that StudentList is unaware of the exact nature of the two UIs but still manages to communicate with them via an intermediary.

Here is the generic description of the observer pattern:

  • <<Observer>> is an interface: any class that implements it can observe an <<Observable>>. Any number of <<Observer>> objects can observe (i.e. listen to changes of) the <<Observable>> object.
  • The <<Observable>> maintains a list of <<Observer>> objects. addObserver(Observer) operation adds a new <<Observer>> to the list of <<Observer>>s.
  • Whenever there is a change in the <<Observable>>, the notifyObservers() operation is called that will call the update() operation of all <<Observer>>s in the list.

In a GUI application, how is the Controller notified when the “save” button is clicked? UI frameworks such as JavaFX have inbuilt support for the Observer pattern.


Guidance for the item(s) below:

Given below are a few more topics that continue the theme of design patterns, but have been made optional to reduce the module workload.


Design → Design Patterns → Other design patterns


Design → Design Patterns → Combining design patterns


Design → Design Patterns → Using design patterns


Design → Design Patterns → Design patterns versus design principles


Design → Design Patterns → Other types of patterns

Guidance for the item(s) below:

The next topic is like 'design patterns at architecture level'. In fact, the MVC pattern you saw earlier comes close to this category too.

[W11.2] Architectural Styles



Design → Architecture → Styles → What

Design → Architecture → Introduction → What

Can explain architectural styles

Software architectures follow various high-level styles (aka architectural patterns), just like how building architectures follow various architecture styles.

n-tier style, client-server style, event-driven style, transaction processing style, service-oriented style, pipes-and-filters style, message-driven style, broker style, ...


Design → Architecture → Styles → n-Tier Style → What

Can identify n-tier architectural style

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.


Design → Architecture → Styles → Client-Server Style → What

Can identify the client-server architectural style

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 use the client-server style.


Design → Architecture → Styles → Event-Driven Style → What

Can identify event-driven architectural style

Event-driven style controls the flow of the application by detecting from event emitters and communicating those events to interested event consumers. This architectural style is often used in GUIs.

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.


Design → Architecture → Styles → Transaction Processing Style → What

Can identify transaction processing architectural style

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 , which are then sent to a central dispatching unit, which in turn dispatches the transactions to various other units to execute.


Design → Architecture → Styles → Service-Oriented Style → What

Can identify service-oriented architectural style

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 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.



Design → Architecture → Styles → Using styles

Can explain how architectural styles are combined

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.



Design → Architecture → Styles → More styles

Follow up notes for the item(s) above:

As with design patterns, we covered only a few architecture styles. Although you didn't have to design an architecture in the tP, knowing the existence of these styles might come in handy in your future projects.

Guidance for the item(s) below:

Earlier, you learned that can improve the test case quality. Even when applying them, the number of test cases can increase when the SUT takes multiple inputs. Let's see how we can deal with such situations.

[W11.3] Test Cases: Combining Multiple Inputs

Video Q+


Quality Assurance → Test Case Design → Combining Test Inputs → Why

Can explain the need for strategies to combine test inputs

An SUT can take multiple inputs. You can select values for each input (using equivalence partitioning, boundary value analysis, or some other technique).

An SUT that takes multiple inputs and some values chosen for each input:

  • Method to test: calculateGrade(participation, projectGrade, isAbsent, examScore)
  • Values to test:
    Input Valid values to test Invalid values to test
    participation 0, 1, 19, 20 21, 22
    projectGrade A, B, C, D, F
    isAbsent true, false
    examScore 0, 1, 69, 70, 71, 72

Testing all possible combinations is effective but not efficient. If you test all possible combinations for the above example, you need to test 6x5x2x6=360 cases. Doing so has a higher chance of discovering bugs (i.e. effective) but the number of test cases will be too high (i.e. not efficient). Therefore, you need smarter ways to combine test inputs that are both effective and efficient.


Quality Assurance → Test Case Design → Combining Test Inputs → Test input combination strategies

Can explain some basic test input combination strategies

Given below are some basic strategies for generating a set of test cases by combining multiple test inputs.

Let's assume the SUT has the following three inputs and you have selected the given values for testing:

SUT: foo(char p1, int p2, boolean p3)

Values to test:

Input Values
p1 a, b, c
p2 1, 2, 3
p3 T, F

The all combinations strategy generates test cases for each unique combination of test inputs.

This strategy generates 3x3x2=18 test cases.

Test Case p1 p2 p3
1 a 1 T
2 a 1 F
3 a 2 T
... ... ... ...
18 c 3 F

The at least once strategy includes each test input at least once.

This strategy generates 3 test cases.

Test Case p1 p2 p3
1 a 1 T
2 b 2 F
3 c 3 VV/IV

VV/IV = Any Valid Value / Any Invalid Value

The all pairs strategy creates test cases so that for any given pair of inputs, all combinations between them are tested. It is based on the observation that a bug is rarely the result of more than two interacting factors. The resulting number of test cases is lower than the all combinations strategy, but higher than the at least once approach.

This strategy generates 9 test cases:

See steps

Test Case p1 p2 p3
1 a 1 T
2 a 2 T
3 a 3 F
4 b 1 F
5 b 2 T
6 b 3 F
7 c 1 T
8 c 2 F
9 c 3 T

A variation of this strategy is to test all pairs of inputs but only for inputs that could influence each other.

Testing all pairs between p1 and p3 only while ensuring all p2 values are tested at least once:

Test Case p1 p2 p3
1 a 1 T
2 a 2 F
3 b 3 T
4 b VV/IV F
5 c VV/IV T
6 c VV/IV F

The random strategy generates test cases using one of the other strategies and then picks a subset randomly (presumably because the original set of test cases is too big).

There are other strategies that can be used too.


Quality Assurance → Test Case Design → Combining Test Inputs → Heuristic: Each valid input at least once in a positive test case

Can apply heuristic ‘each valid input at least once in a positive test case’

Consider the following scenario.

SUT: printLabel(String fruitName, int unitPrice)

Selected values for fruitName (invalid values are underlined):

Values Explanation
Apple Label format is round
Banana Label format is oval
Cherry Label format is square
Dog Not a valid fruit

Selected values for unitPrice:

Values Explanation
1 Only one digit
20 Two digits
0 Invalid because 0 is not a valid price
-1 Invalid because negative prices are not allowed

Suppose these are the test cases being considered.

Case fruitName unitPrice Expected
1 Apple 1 Print label
2 Banana 20 Print label
3 Cherry 0 Error message “invalid price”
4 Dog -1 Error message “invalid fruit"

It looks like the test cases were created using the at least once strategy. After running these tests, can you confirm that the square-format label printing is done correctly?

  • Answer: No.
  • Reason: Cherry -- the only input that can produce a square-format label -- is in a negative test case which produces an error message instead of a label. If there is a bug in the code that prints labels in square-format, these tests cases will not trigger that bug.

In this case, a useful heuristic to apply is each valid input must appear at least once in a positive test case. Cherry is a valid test input and you must ensure that it appears at least once in a positive test case. Here are the updated test cases after applying that heuristic.

Case fruitName unitPrice Expected
1 Apple 1 Print round label
2 Banana 20 Print oval label
2.1 Cherry VV Print square label
3 VV 0 Error message “invalid price”
4 Dog -1 Error message “invalid fruit"

VV/IV = Any Invalid or Valid Value VV = Any Valid Value


Quality Assurance → Test Case Design → Combining Test Inputs → Heuristic: No more than one invalid input in a test case

Quality Assurance → Test Case Design → Combining Test Inputs → Heuristic: Each Valid Input at Least Once in a Positive Test Case

Can apply heuristic ‘no more than one invalid input in a test case’

Consider the test cases designed in [Heuristic: each valid input at least once in a positive test case].

After running these test cases, can you be sure that the error message “invalid price” is shown for negative prices?

  • Answer: No.
  • Reason: -1 -- the only input that is a negative price -– is in a test case that produces the error message “invalid fruit”.

In this case, a useful heuristic to apply is no more than one invalid input in a test case. After applying that, you get the following test cases.

Case fruitName unitPrice Expected
1 Apple 1 Print round label
2 Banana 20 Print oval label
2.1 Cherry VV Print square label
3 VV 0 Error message “invalid price”
4 VV -1 Error message “invalid price"
4.1 Dog VV Error message “invalid fruit"

VV/IV = Any Invalid or Valid Value VV = Any Valid Value



Quality Assurance → Test Case Design → Combining Test Inputs → Mix

Can apply multiple test input combination techniques together

Consider the calculateGrade scenario given below:

  • SUT: calculateGrade(participation, projectGrade, isAbsent, examScore)
  • Values to test: invalid values are underlined
    • participation: 0, 1, 19, 20, 21, 22
    • projectGrade: A, B, C, D, F
    • isAbsent: true, false
    • examScore: 0, 1, 69, 70, 71, 72

To get the first cut of test cases, let’s apply the at least once strategy.

Test cases for calculateGrade V1

Case No. partici- pation projectGrade isAbsent examScore Expected
1 0 A true 0 ...
2 1 B false 1 ...
3 19 C VV/IV 69 ...
4 20 D VV/IV 70 ...
5 21 F VV/IV 71 Err Msg
6 22 VV/IV VV/IV 72 Err Msg

VV/IV = Any Valid or Invalid Value, Err Msg = Error Message

Next, let’s apply the each valid input at least once in a positive test case heuristic. Test case 5 has a valid value for projectGrade=F that doesn't appear in any other positive test case. Let's replace test case 5 with 5.1 and 5.2 to rectify that.

Test cases for calculateGrade V2

Case No. partici- pation projectGrade isAbsent examScore Expected
1 0 A true 0 ...
2 1 B false 1 ...
3 19 C VV 69 ...
4 20 D VV 70 ...
5.1 VV F VV VV ...
5.2 21 VV/IV VV/IV 71 Err Msg
6 22 VV/IV VV/IV 72 Err Msg

VV = Any Valid Value VV/IV = Any Valid or Invalid Value

Next, you have to apply the no more than one invalid input in a test case heuristic. Test cases 5.2 and 6 don't follow that heuristic. Let's rectify the situation as follows:

Test cases for calculateGrade V3

Case No. partici- pation projectGrade isAbsent examScore Expected
1 0 A true 0 ...
2 1 B false 1 ...
3 19 C VV 69 ...
4 20 D VV 70 ...
5.1 VV F VV VV ...
5.2 21 VV VV VV Err Msg
5.3 22 VV VV VV Err Msg
6.1 VV VV VV 71 Err Msg
6.2 VV VV VV 72 Err Msg

Next, you can assume that there is a dependency between the inputs examScore and isAbsent such that an absent student can only have examScore=0. To cater for the hidden invalid case arising from this, you can add a new test case where isAbsent=true and examScore!=0. In addition, test cases 3-6.2 should have isAbsent=false so that the input remains valid.

Test cases for calculateGrade V4

Case No. partici- pation projectGrade isAbsent examScore Expected
1 0 A true 0 ...
2 1 B false 1 ...
3 19 C false 69 ...
4 20 D false 70 ...
5.1 VV F false VV ...
5.2 21 VV false VV Err Msg
5.3 22 VV false VV Err Msg
6.1 VV VV false 71 Err Msg
6.2 VV VV false 72 Err Msg
7 VV VV true !=0 Err Msg


Statements about test input combinations

Combine test inputs for the consume method

Guidance for the item(s) below:

Testing is the first thing that comes to mind when you hear 'Quality Assurance' but there are other QA techniques that can complement testing. Let's first take a step back and take a look at QA in general, followed by a look at some other QA techniques.

[W11.4] Other QA Techniques


Quality Assurance → Quality Assurance → Introduction → What

Can explain software quality assurance

Software Quality Assurance (QA) is the process of ensuring that the software being built has the required levels of quality.

While testing is the most common activity used in QA, there are other complementary techniques such as static analysis, code reviews, and formal verification.


Quality Assurance → Quality Assurance → Introduction → Validation versus verification


Can explain validation and verification

Quality Assurance = Validation + Verification

QA involves checking two aspects:

  1. Validation: are you building the right system i.e., are the requirements correct?
  2. Verification: are you building the system right i.e., are the requirements implemented correctly?

Whether something belongs under validation or verification is not that important. What is more important is that both are done, instead of limiting to only verification (i.e., remember that the requirements can be wrong too).



Quality Assurance → Quality Assurance → Formal Verification → What


Can explain formal verification

Formal verification uses mathematical techniques to prove the correctness of a program.

An introduction to Formal Methods


  • Formal verification can be used to prove the absence of errors. In contrast, testing can only prove the presence of errors, not their absence.


  • It only proves the compliance with the specification, but not the actual utility of the software.
  • It requires highly specialized notations and knowledge which makes it an expensive technique to administer. Therefore, formal verifications are more commonly used in safety-critical software such as flight control systems.


Guidance for the item(s) below:

Do you know the difference between a library and a framework?

Programmers often reuse code in various ways. The next few topics aim to clarify the difference between several forms reusable software comes in.

[W11.5] Reuse




Implementation → Reuse → Introduction → What

Can explain software reuse

Reuse is a major theme in software engineering practices. By reusing tried-and-tested components, the robustness of a new software system can be enhanced while reducing the manpower and time requirement. Reusable components come in many forms; it can be reusing a piece of code, a subsystem, or a whole software.


Implementation → Reuse → Introduction → When

Can explain the costs and benefits of reuse

While you may be tempted to use many libraries/frameworks/platforms that seem to crop up on a regular basis and promise to bring great benefits, note that there are costs associated with reuse. Here are some:

  • The reused code may be an overkill (think using a sledgehammer to crack a nut), increasing the size of, and/or degrading the performance of, your software.
  • The reused software may not be mature/stable enough to be used in an important product. That means the software can change drastically and rapidly, possibly in ways that break your software.
  • Non-mature software has the risk of dying off as fast as they emerged, leaving you with a dependency that is no longer maintained.
  • The license of the reused software (or its dependencies) restrict how you can use/develop your software.
  • The reused software might have bugs, missing features, or security vulnerabilities that are important to your product, but not so important to the maintainers of that software, which means those flaws will not get fixed as fast as you need them to.
  • Malicious code can sneak into your product via compromised dependencies.




Implementation → Reuse → APIs → What

Can explain APIs

An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.

A class has an API (e.g., API of the Java String class, API of the Python str class) which is a collection of public methods that you can invoke to make use of the class.

The GitHub API is a collection of web request formats that the GitHub server accepts and their corresponding responses. You can write a program that interacts with GitHub through that API.

When developing large systems, if you define the API of each component early, the development team can develop the components in parallel because the future behavior of the other components are now more predictable.



Implementation → Reuse → APIs → Designing APIs



Implementation → Reuse → Libraries → What

Can explain libraries

A library is a collection of modular code that is general and can be used by other programs.

Java classes you get with the JDK (such as String, ArrayList, HashMap, etc.) are library classes that are provided in the default Java distribution.

Natty is a Java library that can be used for parsing strings that represent dates e.g. The 31st of April in the year 2008

built-in modules you get with Python (such as csv, random, sys, etc.) are libraries that are provided in the default Python distribution. Classes such as list, str, dict are built-in library classes that you get with Python.

Colorama is a Python library that can be used for colorizing text in a CLI.


Implementation → Reuse → Libraries → How

Can make use of a library

These are the typical steps required to use a library:

  1. Read the documentation to confirm that its functionality fits your needs.
  2. Check the license to confirm that it allows reuse in the way you plan to reuse it. For example, some libraries might allow non-commercial use only.
  3. Download the library and make it accessible to your project. Alternatively, you can configure your to do it for you.
  4. Call the library API from your code where you need to use the library's functionality.



Implementation → Reuse → Frameworks → What

Can explain frameworks

The overall structure and execution flow of a specific category of software systems can be very similar. The similarity is an opportunity to reuse at a high scale.

Running example:

IDEs for different programming languages are similar in how they support editing code, organizing project files, debugging, etc.

A software framework is a reusable implementation of a software (or part thereof) providing generic functionality that can be selectively customized to produce a specific application.

Running example:

Eclipse is an IDE framework that can be used to create IDEs for different programming languages.

Some frameworks provide a complete implementation of a default behavior which makes them immediately usable.

Running example:

Eclipse is a fully functional Java IDE out-of-the-box.

A framework facilitates the adaptation and customization of some desired functionality.

Running example:

The Eclipse plugin system can be used to create an IDE for different programming languages while reusing most of the existing IDE features of Eclipse.


Some frameworks cover only a specific component or an aspect.

JavaFX is a framework for creating Java GUIs. Tkinter is a GUI framework for Python.

More examples of frameworks

  • Frameworks for web-based applications: Drupal (PHP), Django (Python), Ruby on Rails (Ruby), Spring (Java)
  • Frameworks for testing: JUnit (Java), unittest (Python), Jest (JavaScript)


Implementation → Reuse → Frameworks → Frameworks versus libraries

Can differentiate between frameworks and libraries

Although both frameworks and libraries are reuse mechanisms, there are notable differences:

  • Libraries are meant to be used ‘as is’ while frameworks are meant to be customized/extended. e.g., writing plugins for Eclipse so that it can be used as an IDE for different languages (C++, PHP, etc.), adding modules and themes to Drupal, and adding test cases to JUnit.

  • Your code calls the library code while the framework code calls your code. Frameworks use a technique called inversion of control, aka the “Hollywood principle” (i.e. don’t call us, we’ll call you!). That is, you write code that will be called by the framework, e.g. writing test methods that will be called by the JUnit framework. In the case of libraries, your code calls libraries.




Implementation → Reuse → Platforms → What

Can explain platforms

A platform provides a runtime environment for applications. A platform is often bundled with various libraries, tools, frameworks, and technologies in addition to a runtime environment but the defining characteristic of a software platform is the presence of a runtime environment.

Technically, an operating system can be called a platform. For example, Windows PC is a platform for desktop applications while iOS is a platform for mobile applications.

Two well-known examples of platforms are JavaEE and .NET, both of which sit above the operating systems layer, and are used to develop enterprise applications. Infrastructure services such as connection pooling, load balancing, remote code execution, transaction management, authentication, security, messaging etc. are done similarly in most enterprise applications. Both JavaEE and .NET provide these services to applications in a customizable way without developers having to implement them from scratch every time.

  • JavaEE (Java Enterprise Edition) is both a framework and a platform for writing enterprise applications. The runtime used by JavaEE applications is the JVM (Java Virtual Machine) that can run on different Operating Systems.
  • .NET is a similar platform and framework. Its runtime is called CLR (Common Language Runtime) and it is usually used on Windows machines.

Guidance for the item(s) below:

While not examinable, the next few sections explain some basic cloud computing concepts that are relevant to software engineers.

[W11.6] Cloud Computing : OPTIONAL


Implementation → Reuse → Cloud Computing → What


Implementation → Reuse → Cloud Computing → Iaas, PaaS, and SaaS

Guidance for the item(s) below:

To complete the picture about UML, given below are a peek into the other types of UML diagrams.

[W11.7] Other UML Models : OPTIONAL


Design → Modelling → Modelling Behaviors Communication diagrams


Design → Modelling → Modelling Behaviors State machine diagrams


Design → Modelling → Modelling Structure → Deployment diagrams


Design → Modelling → Modelling Structure → Component diagrams


Design → Modelling → Modelling Structure → Package diagrams


Design → Modelling → Modelling Structure → Composite structure diagrams


Design → Modelling → Modelling Behaviors Timing diagrams


Design → Modelling → Modelling Behaviors Interaction overview diagrams