Language Checklists & UML Class Diagrams

Thomas Kennedy

Contents:

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()
 

We are not going to look in Java or Python in detail… Yet!


2 Keeping Track of the Pieces

Take another look through:

The number of classes (ADTs) ranges from 1 to 5. Think back to when you started Assignment 1. After reading the prompt:

  1. What was your first step?
  2. Where did you start?
  3. 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:

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:

  1. Domain Specific Constructs (e.g., House and Room)
  2. 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:

Room.h
#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.

 
review-03-example-6-step-3.puml
@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:

  1. What are the attributes (data members)?
  2. What are the behaviors (member functions)?

review-03-example-6-step-4.puml
@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:

  1. private vs public vs protected
  2. const vs non-const
  3. getters?
  4. setters?
  5. missing private data members?
  6. 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.

  1. We can assume appropriate getters and setters.
  2. We will focus on the attributes relevant to the immediate discussion.
  3. 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:

  1. The role of the LinkedList.
  2. The iterator interface.
  3. The lifetime of Nodes.
  4. What a Node stores.

review-03-example-6-step-5.puml
@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?

 

review-03-example-6-step-6.puml
@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.