Lecture 16

Last Time: Observer Pattern, Casting This Time: Casting, Coupling vs. Cohesion, SOLID

  1. const_cast - the only type of cast that can “remove” constness. Example: using some library that gives us:
void g(int* p);

Let’s say we know g doesn’t modify the int pointed to by p in any way. Also, I have a const int* I’d like to call g with.

Compiler will prevent us from calling g, because it might modify p. (that is it might modify our const int*, which is p, since we pass it in as an argument).

I can use const_cast to call g in the following way:

void f(const int* p){
	g(const_cast<int*> p);
}

Generally, const_cast should be avoided.

Another example, working on legacy codebase that doesn’t use const anywhere. We want to add consts, make our program const-correct.

Issue: const- poisoning 🍎👎. Adding const in one location often means we must add it to other locations to allow it to continue to compile.

We can use const-cast to bridge between const-correct and non-const-correct parts of our program. Make small independent parts of the program const-correct, use const-cast to allow the program to compile as the work is done.

  1. Dynamic_cast: Used for safely casting between pointers/references in an inheritance hierarchy.
Book* pb = ...;
static_cast<Text*> (pb)->getTopic();

Only safe if pb actually points to a Text.

Instead,

Book* pb = ...;
Text* pt = dynamic_cast<Text*>(pb);

If the cast succeeds (i.e dynamic type is Text), then pt points at the Text.

Otherwise, pt is set to nullptr.

if (pt) cout << pt->getTopic();
else cout << "Not a Text!"

Also can be used on references. (Not just on pointers)

Text t{...};
Book& br = t;
Text& tr = dynamic_cast<Text&>(br);

What if br is actually referencing a comic?

Cannot set it to null, no such thing as a null reference.

If dynamic_cast fails with a reference: throw a std::bad_cast exception.

There exist smart pointer versions of each of these casts:

static_pointer_cast
const_pointer_cast
dynamic_pointer_cast

Cast shared_ptrs to other shared_ptrs. ??????? Don’t really understand ?????

Those allow us to make decisions based on RTTI (run-time type information).

void whatIsIt(shared_ptr<Book> b) {
	if(dynamic_pointer_cast<Text>(b)) cout << "Text";
	else if(dynamic_pointer_cast<Comic>(b)) cout << "Comic";
	else cout << "Normal Book";
}

Note*: This function is poor OOD. Don’t do this.

Note

Also Note: dynamic_cast only works if you have at least one virtual method.

Recall: polymorphic assignment problem. We considered making operator= virtual.

  • operator= non-virtual: partial assignment
  • operator= virtual: mixed assignment
Text t1, t2;
Book& b1 = t1;
Book& b2 = t2;
b1 = b2;

Let’s make operator= virtual in Book. ???????? WTF

Text& Text::operator=(const Book& other) {
	// line below throws if `other` is not a Text
	Text& tother = dynamic_cast<const Text&>(other);
	if (this == &tother) return *this;
	Book::operator=(other);
	topic = tother.topic;
	return *this;
}

Measures of Design Quality

Question: How can we evaluate the quality of our code - what is good, what is bad, when we’re not just following a particular design pattern?

We’ll discuss a number of ways:

  • code smells - refactoring
  • coupling/cohesion - how do my classes interact?
  • SOLID design principles

First: Coupling + cohesion

Coupling: Description of how strongly different modules depend on one another.

Low:

  • Function calls with primitive args/results
  • Function calls with structs/arrays as results
  • Modules affect each other’s control flow
  • Modules sharing global data

High:

  • Modules have access to each other’s implementation (friend)

Ideally, we desire low coupling:

  • easier to reason about our program
  • easier to make changes

We can cheat: If I put everything in one class, super low coupling!

Counterbalancing force: Cohesion: How closely do the parts of my module relate to one another?

Low:

  • parts of module are completely unrelated, e.g. <utility>
  • module has a unifying common theme, e.g. <algorithm>
  • Elements manipulate state over lifetime of an object, e.g. <fstream> didn’t understand this at first, but Ross went over this again in the Final Review CS247

High:

  • Elements are cooperating to perform exactly on a task

Cheat at cohesion:

  • put every function in its own class, then they each do only one thing! ?????? What does that mean????

The Goal

We strive for: low coupling, high cohesion

Ex: whatIsIt function - exhibits high coupling with the Book hierarchy.

Any new classes in Book hierarchy necessitate changes to this function. Which is generally not good right?


SOLID Design Principles

Acronym of 5 principles: purpose is to reduce software rot (the property that long lasting codebase become more difficult to maintain)

  1. Single Responsibility Principle
  2. Open-Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Single Responsibility Principles (SRP): “A class should only have one reason to change”.

i.e. a class should do one thing, not several.

A change to a program spec requires a change to the source.

If changes to different parts of the spec require changes in the same class, then SRP is violated.

Example: Consider ChessBoard Class

class ChessBoard {
	...
	... cout << "Your Move";
};

This is actually somewhat bad design. ChessBoard should not print to the screen.

What if I instead want to print to a file? Cannot use the same class without replacing couts with printing to a file.

class ChessBoard {
	ostream& out;
	istream& in;
	public:
		ChessBoard(ostream& out, istream& in): out{out}, in{in} {}
		...
		...
		out << "Your Move";
};

Little more flexible, but still has issues.

What if we want a graphic display? Or to change the language, or to communicate over the internet?

We have low cohesion, violating SRP. ChessBoard is doing multiple things: manipulate state, implement logic, control rendering to the screen.

Instead: ChessBoard should communicate via results, parameters, exceptions. Allow other classes to handle communication with the user.

Next: CS247 Lecture 17