Language Checklists & UML Class Diagrams
Thomas Kennedy
1 Language Specific Class Checklists
Every semester I draw a table on the whiteboard. I last drew this table on 16 October 2018, and discussed the C++ class checklist and how each item compares to Java and Python 3.
It was obvious that the potato used to take the photo was not properly calibrated. Consider the following table…
C++ | Java | Python 3 |
---|---|---|
Default Constructor | Default Constructor | __init__ |
Copy Constructor | Clone and/or Copy Constructor | __deepcopy__ |
Destructor | ||
finalize (deprecated/discouraged) | __del__ |
|
Assignment Operator (=) | ||
Accessors (Getters) | Accessors (Getters) | Accessors (consider @property ) |
Mutators (Setters) | Mutators (Setters) | Setter (consider @attribute.setter ) |
Swap | ||
Logical Equivalence Operator (==) | equals | __eq__ |
Less-Than / Comes-Before Operator (<) | hashCode | __hash__ |
Stream Insertion Operator (<<) | toString | __str__ |
__repr__ |
||
begin() and end() |
iterator |
__iter__ |
You should recall from your previous coursework that not all accessors are getters and not all mutators are setters. The accessor and mutator rows deal with getters and setters, respectively.
Let us expand the table to include one more language… Rust.
C++ | Java | Python 3 | Rust |
---|---|---|---|
Default Constructor | Default Constructor | __init__ |
new() or Default trait |
Copy Constructor | Clone and/or Copy Constructor | __deepcopy__ |
Clone trait |
Destructor | |||
finalize (deprecated/discouraged) | __del__ |
Drop trait | |
Assignment Operator (=) | |||
Accessors (Getters) | Accessors (Getters) | Accessors (@property ) |
Accessors (Getters) |
Mutators (Setters) | Mutators (Setters) | Setter (@attribute.setter ) |
Mutators (setters) |
Swap | |||
Logical Equivalence Operator (==) | equals | __eq__ |
std::cmp::PartialEq trait |
Less-Than / Comes-Before Operator (<) | hashCode | __hash__ |
std::cmp::PartialOrd trait |
Stream Insertion Operator (<<) | toString | __str__ |
std::fmt::Display trait |
__repr__ |
std::fmt::Debug trait | ||
begin() and end() |
iterator |
__iter__ |
iter() and iter_mut() |
-
The Python
__str__
method generates human readable output (much like the JavatoString
method). -
There is no analogue in C++ or Java to Python’s
__repr__
. The__repr__
method is used for debugging. It, by definition, should generate an unambiguous string representation.
We are not going to look in Java or Python in detail… Yet!.
2 Keeping Track of the Pieces
Take another look through:
- Review 01, Review 02, and Review 03.
- Assignment 1
- Assignment 2
The number of classes (ADTs) ranges from 1 to 5. Think back to when you started Assignment 1. After reading the prompt:
- What was your first step?
- Where did you start?
- Did you try to examine all the pieces… at once?
The title of CS 330 is Object Oriented Programming and Design. We need a way to illustrate classes and objects. Let us start with UML Class diagrams.
3 Drawing UML Class Diagrams
You might be familiar with Dia or Draw.io. I quickly become frustrated by the amount of clicking required to create, or edit, even a basic UML Class diagram.
In class, and in the notes I reference Dia. As a Programmer–who likes vim and neovim–I like my keyboard, and dislike my mouse. For this discussion I will use PlantUML Class Diagram syntax. You can either:
- Set up PlantUML on your own machine
- Use a web interface
3.1 An (old?) Example
Let us revisit Review 03 Example 6. Let us start with some quick markup:
@startuml
hide empty members
class LinkedList {
}
class LinkedList::Node {
}
class House {
}
class Room {
}
@enduml
This markup will generate:
3.2 Refining the Diagram
If we think back to Review 03 Example 6, we will recall a number of connections between classes. Let us take a step back. We want to model the Big Picture.
@startuml
left to right direction
hide empty members
class LinkedList {
}
class LinkedList::Node {
}
class House {
}
class Room {
}
LinkedList *----- LinkedList::Node
House *----- Room
House ------> "container" LinkedList
@enduml
Are you surprised that I did not draw a connection between Room
and LinkedList::Node
? We want to capture the Big Picture. We do not want to capture everything, all at once, at the same time.
The Big Picture is not simply showing everything (or understanding every single piece). The Big Picture is understanding the high-level abstractions (ADTs). The low-level pieces (e.g., variables, algorithms) and implementation details should be examined on-the-fly as needed.
3.3 The Pieces & Remaining Coherent
We do not want a single monolithic UML Class diagram. We want a set of UML Class diagrams that each focus on one aspect of the system. I see two perspectives that we want to capture:
- Domain Specific Constructs (e.g., House and Room)
- Data Structures (e.g., House, LinkedList, and Node).
I think we should start with the first one. Let us take another look at Room.h
from Review 03 Example 6:
#ifndef ROOM_H_DEFINED
#define ROOM_H_DEFINED
#include <iostream>
#include <string>
#include <utility>
using namespace std::rel_ops;
/**
* Monetary cost. Note that in a non-academic setting,
* this would likely be represented by a more robust
* Money ADT--or API.
*/
typedef double Cost;
/**
* A Room Blueprint. This struct, defines
* a room. For the moment this is simply
* a grouping of attributes (variables)
* that describe a Room
*/
class Room {
public:
/**
* Units of length--e.g., meters
*/
static const std::string UNITS;
/**
* Flooring Record for a Room. Note
* that this data-type is meaningless
* outside the context of of Room ADT
* for this scenario.
*/
struct Flooring {
std::string type;
Cost unitCost;
/**
* Default Constructor
*/
Flooring();
/**
* Non-Default Constructor
*/
Flooring(std::string n, Cost c);
};
/**
* One linear dimension. This can be one of
* length, width, or height
*/
typedef double Dimension;
/**
* Container for length and width.
* <p>
* This will allow us to reduce the impact
* of the addition of the height dimension in
* a later example.
* <p>
* For the sake of clarity, I titled this data-type
* DimensionSet, in practice, I would have more likely
* named it Dimensions.
* <p>
* Note that this is now a proper class.
*/
class DimensionSet {
private:
Dimension length;
Dimension width;
public:
/**
* Default to dimensions of 1
*/
DimensionSet();
/**
* Set the length and width to user
* specified values
*/
DimensionSet(Dimension l, Dimension w);
/**
* Set the length
*
* @param v replacement value
*/
void setLength(Dimension v);
/**
* Retrieve the length
*/
Dimension getLength() const;
/**
* Set the width
*
* @param v replacement value
*/
void setWidth(Dimension v);
/**
* Retrieve the width
*/
Dimension getWidth() const;
};
private:
/**
* This is the DimensionSet object--i.e, instance.
*/
DimensionSet dimensions;
/**
* This is the Flooring object--i.e., instance
*/
Flooring flooring;
/**
* This is the name of the room--i.e., a std::string object
*/
std::string name;
public:
/**
* Default Constructor
*/
Room();
/**
* Second, Non-Default Constructor
*
* @param l length
* @param w width
* @param c cost for 1 sq unit of flooring
*
*/
Room(Dimension l, Dimension w, Cost c);
/**
* Third, Non-Default constructor
*
* @param n name
* @param l length
* @param w width
* @param c cost for 1 sq unit of flooring
*
*/
Room(std::string n, Dimension l, Dimension w, Cost c);
/**
* Fourth, Non-Default constructor
*
* @param n name
* @param d dimensions
* @param c cost for 1 sq unit of flooring
* @param fn flooring type
*
*/
Room(std::string n, DimensionSet d, Cost c, std::string fn);
/**
* Permit access to the DimensionSet object
* <p>
* We will explore this more in a later example.
* Our emphsis will be on the return type
*/
const DimensionSet& getDimensions() const;
/**
* Allow the dimensions to be changed
*
* @param l new length
* @param w new width
*/
void setDimensions(Dimension l, Dimension w);
/**
* Permit access to the Flooring object
* <p>
* We will explore this more in a later example.
* Our emphsis will be on the return type
*/
const Flooring& getFlooring() const;
/**
* Allow the flooring to be changed
*
* @param t flooring type
* @param c cost per unit
*/
void setFlooring(std::string t, Cost c);
/**
* Set the name
*
* @param newName
*/
void setName(std::string newName);
/**
* Retrieve the name
*/
std::string getName() const;
/**
* Compute the area of this room
*/
double area() const;
/**
* Retrive cost of flooring for the entire room
*/
Cost flooringCost() const;
/**
* Generate and display a summary for a single (one) room
*
* @param prt Room for which to print the summary
*/
void display(std::ostream& outs) const;
/**
* Logical Equivalence Operator
* <p>
* This is the member function implementation.
* This operator can be implemented as a non-member function.
*/
bool operator==(const Room& rhs) const;
/**
* Less-Than (Comes-Before) Operator.
* <p>
* This is used to assign a lexicographical ordering.
* <p>
* This is the member function implementation.
* This operator can be implemented as a non-member function.
*/
bool operator<(const Room& rhs) const;
};
/**
* Room Stream Insertion (Output) Operator
*
* This is often written as a wrapper for a
* display or print function.
* <p>
* This operator can *NOT* be implemented as a member function.
*/
inline
std::ostream& operator<<(std::ostream &outs, const Room &prt)
{
prt.display(outs);
return outs;
}
#endif
This is an abbreviated listing (I removed many of the inline definitions). We see a few things missing, especially Room::Flooring
and Room::DimensionSet
. Let us update our diagram.
@startuml
left to right direction
hide empty members
class House {
}
class Room {
}
class Room::Flooring {
}
class Room::DimensionSet {
}
House *----- Room
Room *----- Room::Flooring
Room *----- Room::DimensionSet
@enduml
3.3.1 The Room Class
Now we need to add details:
- What are the attributes (data members)?
- What are the behaviors (member functions)?
@startuml
left to right direction
hide empty members
class Room {
std::string name
Room()
Room(Dimension l, Dimension w, Cost c)
Room(std::string n, Dimension l, Dimension w, Cost c)
Room(std::string n, DimensionSet d, Cost c, std::string fn)
double area()
Cost flooringCost()
void display(std::ostream& outs)
bool operator==(const Room& rhs)
bool operator<(const Room& rhs)
}
class Room::Flooring {
std::string type
Cost unitCost
Flooring()
Flooring(std::string n, Cost c)
}
class Room::DimensionSet {
Dimension length
Dimension width
DimensionSet()
DimensionSet(Dimension l, Dimension w)
}
Room *----- Room::Flooring
Room *----- Room::DimensionSet
@enduml
I have added a lot of information, but you probably noticed missing pieces. What about:
- private vs public vs protected
- const vs non-const
- getters?
- setters?
- missing private data members?
class House
?
We want to capture the structure of a Room and all of its components, at a conceptual level. In other words, we want a high level view, the big picture.
- We can assume appropriate getters and setters.
- We will focus on the attributes relevant to the immediate discussion.
- We will leave
class House
until the data structure diagram.
3.3.2 The Data Structures
The data structure diagram is more nebulous. What do we want to capture? We probably want to capture:
- The role of the
LinkedList
. - The iterator interface.
- The lifetime of
Node
s. - What a
Node
stores.
@startuml
left to right direction
hide empty members
class LinkedList {
LinkedList::Node* head
LinkedList::Node* tail
iterator begin()
const_iterator begin()
iterator end()
const_iterator end()
size_t size()
}
class LinkedList::Node {
Room data
LinkedList::Node* next
}
class House {
Collection rooms
House()
House(std::string name)
void addRoom(Room toAdd)
iterator begin()
const_iterator begin()
iterator end()
const_iterator end()
size_t size()
void display(std::ostream& outs)
bool operator==(const House &rhs)
}
LinkedList *----- LinkedList::Node
House ------> "collection" LinkedList
@enduml
3.3.3 A Bonus Diagram - Iterators!
If we are looking at data structures, we should probably look at any abstractions used to traverse, examine, or manipulate those data structures. What about iterators?
@startuml
skinparam classAttributeIconSize 0
hide empty members
class House {
}
interface std::Container <<interface>> {
+begin(): iterator
+end(): iterator
+begin(): const_iterator
+end(): const_iterator
+size(): size_t
}
class House::iterator {
}
interface std::iterator <<interface>> {
+operator++()
+operator++(v: int)
+operator*(): T&
+operator->(): T&
}
House ..|> std::Container
House -> House::iterator: provides
House::iterator ..|> std::iterator
@enduml
4 Let Us Stop Here
We are coming dangerously close to drifting from the purpose of this Module, Object Oriented Design. We have covered the basics of UML Class diagrams–a whirlwind introduction, if you will.