1/98
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced | Call with Kai | Chat |
|---|
No analytics yet
Send a link to your students to track their progress
What is this function declaring?
virtual void eat() = 0;
This is a virtual function. It declares that the function exists, but has no implementation.
It states that each class must provide its own implementation of the eat() function.
What is an abstract class?
An abstract class is a class with at least one virtual function. It can also contain data and the implementations of other functions.
Is this an example of an abstract class?
class Animal {
protected:
int age;
public:
:age(0)
{
}
void sleep() {
std::cout << "Sleeping\n" << std::endl;
}
virtual void eat() = 0;
};Yes. The only thing making it abstract is virtual void eat() = 0;
What is an interface?
An interface is a class that only contains pure virtual functions (no functions with any kind of implementation, and no data). It must also contain a virtual destructor.
Is this an interface?
class Animal {
public:
virtual ~Animal() {}
virtual void move_x(int x) = 0;
virtual void move_y(int y) = 0;
virtual void eat() = 0;
};Yes, it is an interface. It does not have any kind of data like int age, or std::string, and it only contains pure virtual functions, plus a virtual destructor.
How do we know when to add a virtual destructor?
A virtual destructor should only be added if the class is intended to be a base class, an interface, or an abstract class.
What kind of class is this?
class Animal {
public:
virtual void eat() = 0;
};This is an abstract class.
But technically, it is also acting like an interface. The only reason we wouldn’t call it an interface yet is because it doesn’t contain a virtual destructor.
Given this class:
class Animal {
public:
virtual void eat() = 0;
};What will happen if we then try doing:
Animal animal;We will get a compiler error.
This is because eat() doesn’t actually exist yet, and the compiler only knows that every child must implement eat(). If it doesn’t, then C++ will not allow you to create an object from it.
Why would we create an interface?
To force subclasses to implement certain behavior.
Let’s say we have the following Animal interface:
class Animal {
public:
virtual ~Animal() {}
virtual void move_x(int x) = 0;
virtual void move_y(int y) = 0;
virtual void eat() = 0;
};And then we create a Dog class that extends Animal, but we forgot to implement eat!
What will happen?
class Dog : public Animal {
public:
void move_x(int x) override {
std::cout << "Dog Moved in x direction";
}
void move_y(int y) override {
std::cout << "Dog Moved in y direction";
}
};The compiler will not allow us to forget any of these functions. If we leave one out, then Dog remains abstract, and we can never initialize any objects from it.
What’s the difference between an abstract class and an interface?
An abstract class may contain data, implemented functions, and pure virtual functions.
On the other hand, an interface may only contain pure virtual functions and a virtual destructor.
Create an interface called Vehicle that requires start() and stop(). Then create a Car class that implements both functions. In main(), create a Car object and call both functions.
class Vehicle {
public:
virtual void start() = 0;
virtual void stop() = 0;
virtual ~Vehicle() {};
};
class Car : public Vehicle {
public:
void start() override {
std::cout << "Car starting!\n";
}
void stop() override {
std::cout << "Car stopping\n";
}
};
int main() {
Car myCar = Car();
myCar.start();
myCar.stop();
return 0;
}Create an abstract class called: Employee that contains: std::string name; and a constructor that initializes the name. Also require:
virtual void work() = 0;
Create a child class:
Programmer, that implements work().
Print the programmer's name and work message.
#include <string>
#include <iostream>
class Employee {
public:
std::string name;
Employee(std::string n)
:name(n)
{
}
virtual void work() = 0;
};
class Programmer : public Employee {
public:
Programmer(std::string n)
:Employee(n)
{
}
void work() override {
std::cout << "Hello I am " << name << "." << "I am working on code.";
}
};
int main() {
Programmer frank("Frank");
Programmer sofia = Programmer("Sofia");
sofia.work();
frank.work();
return 0;
}Create an interface called: Shape requiring: draw() and resize().
Then create: Circle that implements both functions.
Make each function print a message. Then test everything inside main()
#include <iostream>
#include <string>
class Shape {
public:
virtual ~Shape() {}
virtual void draw() = 0;
virtual void resize() = 0;
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle" << std::endl;
}
void resize() override {
std::cout << "Resizing a circle" << std::endl;
}
};
int main() {
Circle myCircle;
myCircle.draw();
myCircle.resize();
return 0;
}Note: The only reason we didn’t have to create a Circle constructor is because Circle has no data members. As a result, it relies on the default constructor, and we don’t need to define our own.
Identify the problems with the following code:
#include <string>
#include <iostream>
class Employee {
public:
std::string name;
Employee(std::string n)
:name(n)
{
}
virtual void work() = 0;
};
class Programmer : public Employee {
public:
void work() override {
std::cout << "Hello I am " << name << "." << "I am working on code." << std::endl;
}
};
int main() {
Programmer sofia = Employee("Sofia");
Programmer frank = Programmer("Frank");
sofia.work();
frank.work();
return 0;
}#include <string>
#include <iostream>
class Employee {
public:
std::string name;
Employee(std::string n)
:name(n)
{
}
virtual void work() = 0;
};
class Programmer : public Employee {
public:
// PROBLEM #1. MISSING A CONSTRUCTOR IN THE PROGRAMMER CLASS
// WITHOUT THE FOLLOWING LINE, IT
// WILL CALL THE EMPLOYEE CONSTRUCTOR:
Programmer(std::string n)
:Employee(n)
{
}
void work() override {
std::cout << "Hello I am " << name << "." << "I am working on code." << std::endl;
}
};
int main() {
// PROBLEM #2. THIS LINE IS ILLEGAL.
// BECAUSE WE CANNOT INSTANTIATE AN ABSTRACT CLASS.
// Programmer sofia = Employee("Sofia");
// This becomes okay once we add a constructor in Programmer.
Programmer frank = Programmer("Frank");
// However, a simpler way to do it is just:
// Programmer frank("Frank");
sofia.work();
frank.work();
return 0;
}Can we instantiate objects of Abstract classes or Interfaces?
No, we cannot.
Create a table that shows the similarities and differences between an Interface, an Abstract class, and a Concrete class.
An Interface
Can only contain purely virtual functions
Must contain a virtual destructor
Has no data members
Cannot be instantiated
An Abstract Class
Must contain at least one virtual function
Must contain a virtual destructor
Can have functions and data members
Cannot be instantiated
A Concrete Class
Has no virtual functions
Must contain a virtual destructor if it is a base class
Can have functions and data members
Can be instantiated
What is a class diagram?
A diagram that summarizes what classes exist, what data they contain, what functions, and how they are related.

Identify what each of the following represent:
BankAccount = The Class Name
std::string owner and double balance = Data
deposit(double amount) and withdraw(double amount) = Functions

Classify each of these from Weakest to Strongest:
Dependency, Aggregation, and Association.
Dependency = The Weakest Relationship
Association = Weak, but slightly stronger than dependency
Aggregation = The Strongest Relationship
Explain why this is an example of Dependency.
class Printer {
public:
void print() {
std::cout << "Printing\n";
}
};
class Report{
public:
// Recall: Printer& means printer is a reference to
// the Printer object that gets passed in.
void generate(Printer& printer) {
printer.print();
}
};
int main() {
Printer printer;
Report report;
report.generate(printer);
return 0;
}This is Dependency because Report does not store a printer. Printer only appears as a parameter. This means that Report only temporarily uses a Printer. It doesn’t store a Printer, and it only needs one when the function is running.
Therefore, Report does not contain a printer, it just uses one. This is an A uses B relationship.
Explain why this is an example of Association.
class Engine {
};
class Car {
private:
// Pointer to an engine. Holds the address
// of an actual engine.
Engine* engine;
};This is Association because Car has an Engine. It uses a member variable, which means that Car actually stores an engine, and that the engine lasts so long as the car object exists.
Explain why this is an example of Aggregation.
class Engine {
public:
void start() {
std::cout << "Engine started" << std::endl;
}
};
class Car {
private:
Engine* engine;
public:
Car(Engine* e)
:engine(e)
{
}
void start() {
engine->start();
}
};
int main() {
Engine engine;
Car car(&engine);
car.start();
return 0;
}This is Aggregation because Car stores an Engine, but it didn’t create engine, main() created engine, so main() owns it. The Engine can exist without car, because even if the car disappears, the engine still exists. Therefore, this is an example of Aggregation, where A has B, but doesn’t own B, and B can still exist independently of A.
Give a simple definition for what Dependency, Association, and Aggregation mean and how you can recognize them.
Dependency:
Means A uses B.
Dependency describes a temporary use relationship in which a change to one class might affect the other class that uses it.
You can recognize dependency when B is only used as a parameter.
Association:
Means A has a B.
Association is a connection between 2 classes where one object knows about and references another.
You can recognize association when B is stored as a member variable.
Aggregation:
Means A has B, but B can still exist independently of A.
Aggregation is an association where one object belonging to a different class is stored, but this object can still exist independently of the other class.
You can recognize aggregation when B is stored as a member variable, but was not created by A.
Is this Association, Dependency, or Aggregation? Why?
class Student {
};
class Course {
private:
Student* student;
public:
Course(Student* s)
:student(s)
{
}
};This is Aggregation, because Course contains Student, but Student can still exist independently of Course. Course does not own Student.
Identify the relationship:
class Car;
class Mechanic {
public:
void repair(Car& car);
};This is an example of Dependency, because Mechanic only contains a reference to a Car object, but it does not actually contain an instance of a car. It only uses a Car temporarily in its repair() method, but it doesn’t form a permanent relationship to Car.
Identify the relationship:
class Engine {
};
class Car {
private:
Engine engine;
};This is an Association, because Car contains an Engine, and Car cannot exist without an Engine, although the Engine can exist independently of the Car.
Convert this UML Diagram into code:
__________Student_____________
name: String
gpa: double
_____________________________
study()
display()
class Student {
private:
std::string name;
double gpa;
public:
void study();
void display();
Student(std::string n, double g);
};
Student::Student(std::string n, double g)
:name(n), gpa(g)
{
}OR
class Student {
private:
std::string name;
double gpa;
public:
void study();
void display();
Student(std::string n, double g)
:name(n), gpa(g)
{
}
};Create two classes: Library and Book such that:
Library stores a pointer to a Book
The Book is created in main()
The Book is passed into the Library
Then identify what type of relationship this is and why.
class Library {
private:
Book* bookptr;
public:
Library(Book* book)
:bookptr(book)
{
}
};
class Book {
};
int main() {
Book myBook;
Library myLibrary(&myBook);
return 0;
}This is an example of Aggregation, because the book and the library can both exist independently of each other. Library does not own myBook, myBook is owned by main(). If myLibrary were to disappear, myBook would continue to exist.
What is composition?
Composition means one object is made up of another object. If the outer object is destroyed, then the inner object is destroyed too. The lifetime of the smaller object depends on the larger object.
What makes this an example of Composition?
class Engine {
};
class Car {
private:
Engine engine;
};This is Composition because we are not storing a pointer to an engine, like in Aggregation. Here, we actually store an Engine object inside of the Car class. Meaning that the engine is part of the car class itself.
If you were to say: Car myCar, then C++ would automatically create Engine engine inside. You would not need to create an engine separately. Also, if our myCar object were to be destroyed, this means that our engine would be destroyed along with it.
Explain the difference between Aggregation and Composition.
In Aggregation, one class contains an object of another class, or a pointer to an object of another class, but that object can still exist independently of the class that contains it.
On the other hand, in Composition, an object of one class becomes part of another class, and the two cannot be separated. The object is not independent of the class that contains it.
Which is Aggregation and which is Composition?
Book myBook;
Library myLibrary(&myBook);class Library {
private:
Book book;
};// Aggregation
Book myBook;
Library myLibrary(&myBook);Here, Library contains a Book, but myBook can still exist independently of Library. If myLibrary disappears, myBook will continue to exist. Therefore, it is Aggregation
// Composition
class Library {
private:
Book book;
};On the other hand, here, Library contains book as part of the Library itself. If we were to instantiate a Library object, then a Book object would automatically be created with it. If we destroy the Library object, then we destroy the book object too. Therefore, this is Composition.
What is inheritance?
When one class is a sub-type of another class.
For example:
class Vehicle{
};
class Bicycle : public Vehicle {
};Here, Bicycle is a Vehicle.
What will be the output of the following code?
#include <iostream>
class Engine {
public:
Engine() {
std::cout << "Engine created\n";
}
~Engine() {
std::cout << "Engine destroyed\n";
}
};
class Car {
private:
Engine engine;
public:
Car() {
std::cout << "Car created\n";
}
~Car() {
std::cout << "Car destroyed\n";
}
};
int main() {
Car myCar;
return 0;
}Engine created
Car created
Car destroyed
Engine destroyed
Explain the highlighted line in the following example:
class Vehicle {
public:
virtual void accelerate() = 0;
};
class Bicycle : Vehicle {
public:
void accelerate() override {
std::cout << "PEDAL FASTER";
}
};
int main() {
Bicycle my_bike;
// EXPLAIN THIS LINE
Vehicle& my_vehicle = my_bike;
my_vehicle.accelerate();
return 0;
} Vehicle& my_vehicle = my_bike;This means that my_vehicle is a reference to a Vehicle object, my_bike. So now my_vehicle and my_bike can be used interchangeably because they are references to each other.
What type is this demonstrating? Dependency, Association, Aggregation, or Composition?
class Keyboard {
};
class Computer {
private:
Keyboard keyboard;
};This is Composition, because Computer contains Keyboard as part of the class itself. If we were to create a Computer object, this would automatically instantiate a Keyboard object as well. If the Computer object were to be destroyed, then the Keyboard object would be destroyed too.
What type is this demonstrating? Dependency, Association, Aggregation, or Composition?
class Student {
};
class Classroom {
private:
Student* student;
public:
Classroom(Student* s)
:student(s)
{
}
};This is Aggregation. Because classroom contains a pointer to a Student, but a Student can still exist independently of Classroom.
What is Realization?
Realization means that a concrete class provides implementation for the functions required by an interface or abstract class.
Which part of the following example demonstrates Realization?
class Printable {
public:
virtual void print() const = 0;
virtual ~Printable(){}
};class Book : Printable {
private:
std::string title;
public:
Book(std::string t)
:title(t)
{
}
void print() const override {
std::cout << title << std::endl;
}
};Only the part where Book provides an implementation for the print() function.
class Book : Printable {
private:
std::string title;
public:
Book(std::string t)
:title(t)
{
}
// This is the only part that represents Realization
void print() const override {
std::cout << title << std::endl;
}
};Which part of the following example demonstrates Realization? And what does the highlighted line do?
// The Creature class
class Creature {
public:
virtual void allInfo() const = 0;
};
// The Person class
class Person : public Creature {
public:
Person(const std::string& name)
:name(name)
{
}
void allInfo() const override {
std::cout << "My name is " << name;
}
protected:
std::string name;
};
// Main
int main() {
// EXPLAIN WHAT THIS LINE IS DOING
Creature* favaBeanMuncher = new Person("Hannibal Lecter");
favaBeanMuncher->allInfo();
}Only this section demonstrates realization:
void allInfo() const override {
std::cout << "My name is " << name;
}This line creates a Creature pointer that actually points to a Person object. This is why doing:favaBeanMuncher→allInfo() will actually just run the underlying function belonging to Person.
Which of these is Realization, and which is Inheritance?
class Bicycle : public Vehicle {
public:
void accelerate() override {
}
};Inheritance
class Bicycle : public Vehicle {
};Realization
class Bicycle : public Vehicle {
public:
// ONLY THIS PART
void accelerate() override {
}
};
What kind of relationship does this symbol represent?
Association/Generalization
Note: Generalization is just a synonym for Association


What kind of relationship does this symbol represent?
Inheritance


What kind of relationship does this symbol represent?
Realization

What kind of relationship does this symbol represent?
Dependency


What kind of relationship does this symbol represent?
Aggregation


What kind of relationship does this symbol represent?
Composition


What does this diagram represent?
It represents a composition relationship where a car is composed of carburetors.
We also know that the car is composed of one and only one carburetor, while the carburetor can belong to 0 or 1 car.

What does this diagram represent?
It represents a dependency relationship where a car is dependent on a wheel, or in other words, a car uses a wheel.

What does this diagram represent?
It represents an aggregate relationship where professor is an aggregate of a class. A professor has one or more classes, while a class can only belong to one and only one professor.

What does this diagram represent?
It represents an Inheritance relationship where Student and Professor inherit from Person. So both Professor and Student are subclasses of Person.
What are Design Idioms about? Why are they important?
Design Idioms are about how classes are organized. They’re important because they allow code to be easily modifiable, which matters a lot when designing software.
What does Coupling describe?
Coupling describes how dependent classes are on each other.
It measures how many other classes are likely to break if you change one class.
Is this an example of low coupling or tight coupling?
class Printer {
public:
void print() {
std::cout << "Printing";
}
};
class Report {
public:
void generate(Printer& printer) {
printer.print();
}
};This is an example of low coupling/loose coupling because the two classes are minimally dependent on each other. Report only needs a single thing: print() from Printer. Even if we were to completely redesign Report and add methods like generatePDF() or generateHTML(), it’s highly unlikely to affect Printer. Likewise, if you were to add multiple new methods to Printer, it’s highly unlikely to affect anything in Report. Therefore, we can say they have low coupling because changes in one class only minimally affect the other.
Why is loose coupling good?
Loose coupling is good because it allows you to only focus on one class when debugging. You don’t need to understand 25 other unrelated classes in order to be able to fix a single class.
Is this an example of low coupling or tight coupling?
class Engine {
public:
int horsepower;
};
class Car {
public:
Dashboard* dashboard;
};
class Transmission {
public:
Engine* engine;
};
class Dashboard {
public:
Transmission* transmission;
};This is an example of high coupling because all the classes are interconnected. If we suddenly decide to change Engine, we suddenly also need to change Car and Transmission, because they all rely on each other. The changes spread everywhere because the classes are so tightly coupled.
Why is this called the Diamond Problem?
class A {
};
class B : public A {
};
class C : public A {
};
class D : public B, public C {
};Because the inheritance patterns form a diamond!
B inherits from A, C also inherits from A, and D inherits from both B and C. Ultimately, this forms a diamond shape:

Why is the Diamond Problem a problem? Reference the following:
class A {
int x;
};
class B : public A {
};
class C : public A {
};
class D : public B, public C {
};The Diamond Problem is a problem because it creates ambiguity.
For example, if we were to do:
D myD;
myD.x;The compiler gets confused, it doesn’t know whether you want to access the x inherited from B or C. It’s the reason why we need to be careful with multiple inheritance.
What is the solution to the Diamond Problem?
class A {
int x;
};
class B : public A {
};
class C : public A {
};
class D : public B, public C {
};The solution is to use virtual inheritance, because virtual inheritance tells C++ that even if multiple classes inherit from A, only one copy of A should be shared. That way, there is only a single version of x that can be accessed from A, because there is only a single copy of A that exists among both classes.
class A {
int x;
};
class B : virtual public A {
};
class C : virtual public A {
};
class D : public B, public C {
// does not need to be virtual because it inherits from two completely different classes (not the same class)
};What is Cohesion about?
Cohesion is about how focused a class is on a single responsibility.
Does the following example have high cohesion or low cohesion?
class BankAccount {
private:
double balance;
public:
void deposit();
void withdraw();
double getBalance();
};This example has high cohesion because it is very focused. Everything is about Bank accounts and the class only has a single job.
Does the following example have high cohesion or low cohesion?
class Tasks {
public:
void depositMoney();
void drawGraphics();
void playMusic();
void sendEmail();
};This has low cohesion because the class has no real purpose, no single responsibility.
What does method-level cohesion describe?
Method level cohesion just describes how focused a method is on a single task.
For example, here’s a method with high cohesion:
void saveCharacter() {}Here’s a method with low cohesion:
void saveCharacterAndPlaySoundAndPrintReport() {}What is the problem with this? What is this example demonstrating?
class Student {
public:
void all_info() {
std::cout << "Student info\n";
}
};
class Mathematician {
public:
void all_info() {
std::cout << "Mathematician info\n";
}
};
class MathStudent : public Student,
public Mathematician {
};
int main() {
MathStudent bob;
bob.all_info();
}This is demonstrating Multiple Inheritance Ambiguity. The reason this is a problem is because it creates ambiguity surrounding which all_info() method we want to access.
What can we do to eliminate all ambiguity here?
class Student {
public:
void all_info() {
std::cout << "Student info\n";
}
};
class Mathematician {
public:
void all_info() {
std::cout << "Mathematician info\n";
}
};
class MathStudent : public Student,
public Mathematician {
};
int main() {
MathStudent bob;
bob.all_info();
}We must use the scope resolution operator to specify which implementation of the all_info() method we want to use in main.
What is the problem with this? What is this example demonstrating?
int main() {
MathStudent bob;
bob.mathematician::all_info();
}This version explicitly tells it to use the Mathematician version.
What do we want to maximize and what do we want to minimize when it comes to Coupling and Cohesion?
We want to maximize Cohesion and minimize Coupling.
Remember that Cohesion describes how focused a class is, and Coupling describes how interconnected/dependent classes are on each other. The idea is that we want to minimize Coupling so that changing one class doesn’t break 10 others, and we want to maximize Cohesion so that every method has one clear responsibility.
Suppose changing Customer requires modifying Order, Invoice, Payment, Shipping, and Notification. Does this suggest loose coupling or high coupling? Why?
This suggest Tight/High Coupling because it shows that classes are highly dependent on each other, and that making a change in one class is very likely to break many others.
Why is this an example of bad design?
class Fish {
private:
std::string fishType;
};
class Rice {
private:
std::string riceType;
};
class Sushi {
private:
Rice rice;
Fish fish;
};This is bad design because every Sushi object is forced to contain fish. This means that something like a cucumber or avocado roll would not work. The design is very rigid.
What is Decoupling?
Decoupling is about ways of reducing dependency strength. Note that it is not about removing dependencies between classes, just making them weaker.
Why is this an example of better design?
class Filling {
public:
virtual ~Filling(){}
};
class Fish : public Filling {
};
class Cucumber : public Filling {
};
class Avocado : public Filling {
};
class Sushi {
private:
std::vector<Filling*> fillings;
};This is better because Sushi is no longer dependent on Fish. Sushi only depends on Filling, which means it could take on any of the values that extend Filling: Cucumber, Avocado, and Fish.
Is this an example of tight coupling or loose coupling?
class TextFile {
public:
void read(){
}
void write(){
}
};
class UI {
private:
TextFile file;
};This is an example of tight coupling, because UI is highly dependent on TextFile. This is bad because it means that changing TextFile to a different type, like SQLite, would require changes to both TextFile and UI.
Why is this an example of better design?
class DataSource {
public:
virtual void read() = 0;
virtual void write() = 0;
virtual ~DataSource(){}
};
class TextFile : public DataSource {
public:
void read() override {
std::cout << "Reading text file";
}
void write() override {
std::cout << "Writing text file";
}
};
class SQLiteDB : public DataSource {
public:
void read() override {
std::cout << "Reading database";
}
void write() override {
std::cout << "Writing database";
}
};
class UI {
private:
DataSource* dataSource;
public:
UI(DataSource* ds)
:dataSource(ds){}
};This is an example of better design because it makes use of interfaces and abstraction in order to avoid making the UI class dependent on a single type of DataSource. Now all the DataSources are easily interchangeable, and changing one will not affect UI.
Would you describe the dependency between Car and Engine as being relatively weak or relatively strong?
class Engine {
};
class Car {
private:
Engine engine;
};The dependency is strong, because any change to Engine will directly affect Car, and Car cannot exist without an Engine.
Suppose we have:
class PDFFile {
};
class ReportViewer {
private:
PDFFile file;
};Tomorrow the company decides that they want to support Word documents, Excel files, and HTML reports. Would this design be easy or difficult to extend? Why?
This design would be difficult to extend, because right now, ReportViewer directly relies on PDFFile. Changing PDFFile to something else would force a change in ReportViewer as well.
What is the purpose of using SOLID Design Principles?
SOLID Design Principles exist for the purpose of helping us achieve high cohesion and loose coupling.
What does each part of SOLID stand for?
S = Single Responsibility Principle
O = Open/Closed Principle
L = Liskov Substitution Principle
I = Interface Segregation Principle
D = Dependency Inversion Principle
What does the Single Responsibility Principle state?
It states that a class should only have a single clear responsibility. Essentially, it states that a class should have high cohesion.
Is this a good or bad example of the Single Responsibility Principle?
class User {
private:
std::string user;
std::string password;
public:
void createPost() {
}
void connectToDatabase() {
}
void savePost() {
}
};This is a bad example of the Single Responsibility Principle. This is because the User class doesn’t have a single clear responsibility. We are unsure if it is supposed to represent a single person, a database connection, or a forum posting system, because the reality is that it does all 3 at once. Too many responsibilities have been condensed into a single class.
Is this a good or bad example of the Single Responsibility Principle?
class User {
private:
std::string username;
std::string password;
};
class PostWriter {
public:
void createPost(){}
};This is a good example of the Single Responsibility Principle (SRP) because now the User class only has a single clear responsibility, of storing a username and a password, and likewise, the PostWriter class only has a single responsibility of creating a post.
Is it okay if we have a semicolon here instead of curly braces?
class User {
private:
std::string username;
std::string password;
};
class PostWriter {
public:
void createPost(); // semicolon instead of {}
};Yes it is perfectly fine to use either a semicolon or curly braces here.
What does the Open/Closed Principle state?
The Open/Closed principle states that classes are open to extension, but closed for modification. This means that you shouldn’t keep editing existing code to support new use cases, you should create entirely new classes.
Does this violate the OCP (Open/Closed Principle)?
class UIWidget {
private:
std::string type;
public:
void draw() {
if(type == "Button") {
std::cout << "Draw Button";
} else {
std::cout << "Draw Panel";
}
}
};Yes, this does violate the Open/Closed principle, because the moment you want to add new features to the UI system, like TextBoxes, ScrollBars, Labels, etc, you end up having to add many new else if statements to draw().
Eventually, void draw() gets so long and full of else if statements that the method keeps on growing forever, so errors accumulate and it becomes harder to test.
Does this violate the OCP (Open/Closed Principle)?
class UIWidget {
public:
virtual void draw() = 0;
virtual ~UiWidget(){};
};
class UIButton : public UIWidget {
public:
void draw() override {
std::cout << "Drawing button";
}
};This does not violate the Open/Closed Principle, because here, the UIWidget class is easily extendable. We aren’t modifying existing code to add new functionality, we are just creating new classes for it, which is in direct support of the Open/Closed Principle.
Rewrite this using inheritance and an interface.
class Animal {
public:
void makeSound() {
if(type == "Dog"){
} else if(type == "Cat") {
}
}
};class Animal {
public:
virtual void makeSound() = 0;
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "Woof";
}
};
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "Meow";
}
};What does the Liskov Substitution Principle state?
It states that objects of a base type should be replaceable with objects of a derived type.
Does this violate or support the Liskov Substitution Principle?
class Canvas {
private:
UIButton* button;
UITextBox* textbox;
UIScrollbar* scrollbar;
public:
Canvas(UIButton* button, UITextBox* textbox) {
this->button = button;
this->textbox = textbox;
}
void drawScreen() {
button->draw();
textbox->draw();
}
};
class UIWidget {
public:
virtual void draw() = 0;
};
class UIButton : public UIWidget {
public:
void draw() override {
std::cout << "Drawing button";
}
};
class UITextBox : public UIWidget {
public:
void draw() override {
std::cout << "Drawing TextBox";
}
};This does not violate the Liskov Substitution Principle, because it allows us to substitute members of a base class with members of a derived class, for example:
UIWidget* widget;
widget = new UIButton();
widget = new UITextBox();However, it does violate the Open/Closed principle and demonstrates bad architectural design because it has tight coupling. If you wanted to add new features to the UI, like a scrollbar or search bar, you would need to modify existing code instead of creating new classes.
What does the Interface Segregation Principle state?
The Interface Segregation Principle states that interfaces should be small and focused so that classes are not forced to implement unnecessary methods.
Does this violate or support the Interface Segregation Principle?
class Animal {
public:
virtual void walk() = 0;
virtual void swim() = 0;
virtual void fly() = 0;
};This violates the Interface Segregation Principle because it forces all subclasses of Animal to implement unnecessary methods that are not necessarily applicable.
For example:
class Animal {
public:
virtual void walk() = 0;
virtual void swim() = 0;
virtual void fly() = 0;
}
class Fish : public Animal {
};
class Human : public Animal {
};Here, both Fish and Human are forced to implement fly, despite neither of them being able to fly.
Does this violate or support the Interface Segregation Principle?
class Walkable {
public:
virtual void walk() = 0;
};
class Swimmable {
public:
virtual void swim() = 0;
};
class Human : public Walkable, public Swimmable {
public:
virtual void swim() = 0;
};This supports the Interface Segregation Principle because it creates smaller, more focused interfaces, and doesn’t force subclasses to implement unnecessary behavior.
What does the Dependency Inversion Principle state?
The Dependency Inversion Principle states that classes should depend on Interfaces and abstractions, not on classes focused on low level implementation detail.
Or in other words:
High level classes should not depend on low-level classes. Both should depend on abstractions.
Does this violate or support the Dependency Inversion Principle?
class UI {
private:
TextFile file;
};
class TextFile {
public:
void read();
void write();
};This violates the Dependency Inversion principle because TextFile is a low-level, detail implementation class, and UI, which is a higher level, more complex class depends on it instead of an interface.
Does this violate or support the Dependency Inversion Principle?
class DataSource {
public:
virtual void read() = 0;
virtual void write() = 0;
virtual ~DataSource() {}
};
class TextFile : public DataSource {
};
class SQLite : public DataSource {
};
class UI {
private:
DataSource* dataSource;
};This supports the Dependency Inversion Principle, because now the high-level class, UI, only depends on the DataSource interface rather than the low-level implementation detail, TextFile.
Summarize what SOLID stands for, and what each part of the acronym means.
S = Single Responsibility Principle: One class should only have a single clear responsibility
O = Open/Closed Principle: Classes are open for extension, but closed for modification. New classes should be added in order to add new features.
L = Liskov Substitution Principle: Objects of a base class should be replaceable with objects of a derived type.
I = Interface Segregation Principle: Interfaces should be small and focused, and they should not force classes to implement unnecessary behavior/methods.
D = Dependency Inversion Principle: High-level classes should never depend on low-level implementation details. Both low-level and high-level classes should depend on interfaces and abstractions.
What does the Law of Demeter state?
The Law of Demeter states that classes and objects should only have limited knowledge about other classes, and should only interact with their immediate friends.
Let’s say we have 3 classes: Cashier, Customer, and Wallet.
Suppose the Cashier wants $20, and we do:
Customer customer;
customer.getWallet().getMoney(20);Why is this a problem?
This is a problem because the Cashier is reaching through multiple objects in order to access the customer’s money. The cashier already knows too much information about the Customer that they should not have access to. The Cashier should not have access to information about how the Customer stores their money, whether or not the customer even has a Wallet, what functions the Wallet contains, etc.
This is also a problem because it significantly increases coupling. Now Cashier depends on both Customer and Wallet.
Does this example violate the Law of Demeter?
class Customer {
private:
Wallet wallet;
public:
Customer(int money)
:wallet(money)
{
}
int getMoney(int amount) {
return wallet.getMoney(amount);
}
};
class Cashier {
public:
void takeMoney(Customer& customer) {
int money = customer.getMoney(20);
std::cout << money;
}
};No, it supports the Law of Demeter because the Cashier is no longer reaching into the Customer’s wallet in order to grab their money.
The Cashier has limited information about the Customer and their method of payment, and the Cashier can only access money through the Customer.
What is a Train Wreck?
A Train Wreck is when you have a huge chain of calls, for example:
obj.getX().getY().getZ().getAll();Why are Train Wrecks bad?
Train Wrecks are bad because they force us to reach through other objects in order to access data, and they dramatically increase coupling, making it very easy to break an entire class just by breaking the chain.