Unit 7
Summative Assessment Reflection
The goal of the assignment was to design a system for a humanoid robot using several UML diagrams to define its structure and behavior. After reviewing potential application areas, I selected a restaurant scenario, where the robot operates as a waiter. This setup offered a practical case for analyzing coordination between different system actors, including tables, a kitchen, the robots themselves, and a central orchestrator. The assignment required creating an activity diagram, a sequence diagram, a class diagram, and a state transition diagram.
The activity diagram served as a useful tool for modeling the interaction between a robot and a customer. It allowed to define the core behavioral flow, including the ordering, serving, and payment loops. In contrast, the sequence diagram introduced some limitations. Due to its rigid and linear structure, it contributed little that could not be communicated through a brief textual description, especially in a setting where flexibility in representation is important.
While I do not usually rely on state transition diagrams in practice, I found their use here beneficial. The diagram made it easier to delineate the system’s discrete states and define which operations can be performed concurrently. This separation also helped to identify opportunities for parallelization or asynchronous processing.
The class diagram facilitated mapping out core domain relationships such as Table, Bill, and OrderItem. However, the attempt to reflect every association type and cardinality felt more academic than practical. In a real development workflow, I would typically sketch or prototype the structure in code and adjust it as requirements evolve. This iterative approach aligns more closely with agile development principles and helps avoid premature design decisions.
In conclusion, while I remain more inclined toward lightweight modeling methods, this assignment highlighted the value of diagramming when used selectively. In particular, diagrams proved helpful for clarifying workflows, identifying logic gaps, and communicating responsibilities within the system. It also reinforced the idea that different diagram types serve different purposes, and their value depends on the context in which they are applied.
ePortfolio Activities
Discuss the ways in which data structures support object-oriented development. Use examples of three different data structures to contextualise your response.
While various data structures such as list, dictionary, tuple, and set are not exclusive to the object-oriented development, they can indeed support it and have broad range of applications in practice.
The dictionary can serve as perhaps the most vivid example of how the different data structures may support the object-oriented development. Dictionary provides a way to implement a dynamic dispatch mechanism. Instead of relying solely on the if-else branching and predefined processing strategies, it’s possible to associate (and later redefine) different behaviors based on the class of the passed object. It also allows to adjust the set of consumers by interacting with the dictionary and redefine, remove, or add new behaviors where it’s needed.
class File: pass
class Directory: pass
def handle_file(obj): print("Handling file")
def handle_directory(obj): print("Handling directory")
dispatch = {
File: handle_file,
Directory: handle_directory
}
obj = File()
dispatch[type(obj)](obj)
However, the implementation of typing in Python leads to this and other patterns to be very error-prone, as the language is dynamically-typed and it’s possible that the dispatch
dictionary’s structure is inadvertently broken in other code sections. In Kotlin, for example, this same code may be expressed in the following way, where dispatch
would be statically typed, only allowing KClass
objects as keys and FileSystemObject
consumers as values. This and further examples are in Kotlin.
import kotlin.reflect.KClass
interface FileSystemObject
class File : FileSystemObject
class Directory : FileSystemObject
val dispatch = mapOf<KClass<out FileSystemObject>, (FileSystemObject) -> Unit>(
File::class to { println("Handling file") },
Directory::class to { println("Handling directory") }
)
val obj = File()
dispatch[obj::class]?.let { it(obj) }
Similarly, in a statically typed language it is possible to define a list or a set with a base class or even an interface as the type parameter. For example, the Directory
class from the example above may implement some logic to list the directory entries, which can be either files or also directories. Instead of declaring two different getters in a class, it’s possible to define a return value using the FileSystemObject
interface and allow the caller method to handle the values as it is reasonable in each case. At the same time, it’s possible to call the methods and access values that are declared in the parent interface.
interface FileSystemObject {
val name: String
}
class File(override val name: String) : FileSystemObject
class Directory(override val name: String) : FileSystemObject {
val directoryEntries: List<FileSystemObject>
get() = TODO()
}
val directory = Directory("sample directory")
fun printDirectoryEntriesRecursively(directory: Directory) {
directory.directoryEntries.forEach { directoryEntry ->
println(directoryEntry.name) //The exact type of the directory entry is not clear here
when (directoryEntry) {
is Directory -> {
println("Entry is a directory")
printDirectoryEntriesRecursively(directoryEntry)
}
is File -> {
println("Entry is a file")
}
}
}
}
Similarly, in the example above, the type for directoryEntries
is declared as a List
. Therefore it is possible to call methods such as get()
, contains()
, or indexOf()
on directoryEntries
. However, List
is an interface in Kotlin, and does not contain any logic per se. An example where this is useful is when a developer decides on an argument type or the return type for a function. The use of an interface allows to hide the details of the implementation and instead focus on the specifics of the preferred data structure for the implemented function: whether it should store elements in a specific order, allow duplicates, etc.
In the example below, the function containsOranges()
accepts Set<String>
as its argument and returns a boolean value depending on whether the set contains "oranges"
. The three sets in the example are different implementations of this data structure, but they can equally be processed by the containsOranges()
function, which only depends on the contains()
method of a Set
. In a real-life situation, these sets could have originated from different parts of a program where the requirements for the set performance or functionality may differ. However, it’s still possible to reuse the same piece of code to process these different set implementations. This illustrates how polymorphism and data structures support each other in an object-oriented environment.
import java.util.*
val values = listOf("apples", "oranges", "bananas")
val treeSet = TreeSet(values)
val hashSet = HashSet(values)
val linkedHashSet = LinkedHashSet(values)
fun containsOranges(set: Set<String>): Boolean =
"oranges" in set
containsOranges(treeSet)
containsOranges(hashSet)
containsOranges(linkedHashSet)