Lesson 6: Object-Oriented Programming
Activity 23: Creating Game Characters
- Create a Character class that has a public method moveTo that prints Moved to position:
class Character {
public:
void moveTo(Position newPosition) {
position = newPosition;
std::cout << “Moved to position “ << newPosition.positionIdentifier << std::endl;
}
private:
Position position;
};
- Create a struct named Position:
struct Position {
// Fields to describe the position go here
std::string positionIdentifier;
};
- Create two classes Hero and Enemy that are derived from the class Character:
// Hero inherits publicly from Character: it has
// all the public member of the Character class.
class Hero : public Character {
};
// Enemy inherits publicly from Character, like Hero
class Enemy : public Character {
};
- Create a class Spell with the constructor that prints the name of the person casting the spell:
class Spell {
public:
Spell(std::string name) : d_name(name) {}
std::string name() const {
return d_name;
}
private:
std::string d_name;
};
- The class Hero should have a public method to cast a spell. Use the value from the Spell class:
public:
void cast(Spell spell) {
// Cast the spell
std::cout << “Casting spell “ << spell.name() << std::endl;
}
- The class Enemy should have a public method to swing a sword which prints Swinging sword:
public:
void swingSword() {
// Swing the sword
std::cout << “Swinging sword” << std::endl;
}
- Implement the main method that calls these methods in various classes:
int main()
{
Position position{“Enemy castle”};
Hero hero;
Enemy enemy;
// We call moveTo on Hero, which calls the method inherited
// from the Character class
hero.moveTo(position);
enemy.moveTo(position);
// We can still use the Hero and Enemy methods
hero.cast(Spell(“fireball”));
enemy.swingSword();
}
Activity 24: Calculating Employee Salaries
- We can create a class Employee with two virtual methods, getBaseSalary and getBonus, since we want to change those methods based on the type of employee:
class Employee {
public:
virtual int getBaseSalary() const { return 100; }
virtual int getBonus(const Deparment& dep) const {
if (dep.hasReachedTarget()) {
}
return 0;
}
- We also define a method, getTotalComp, which does not need to be virtual, but will call the two virtual methods:
int getTotalComp(const Deparment& dep) {
}
};
- We then derive a Manager class from it, overriding the method for computing the bonus. We might also want to override getBaseSalary if we want to give a different base salary to managers:
class Manager : public Employee {
public:
virtual int getBaseSalary() const override { return 150; }
virtual int getBonus(const Deparment& dep) const override {
if (dep.hasReachedTarget()) {
int additionalDeparmentEarnings = dep.effectiveEarning() - dep.espectedEarning();
return 0.2 * getBaseSalary() + 0.01 * additionalDeparmentEarnings;
}
return 0;
}
};
- Create a class Department as shown:
class Department {
public:
bool hasReachedTarget() const {return true;}
int espectedEarning() const {return 1000;}
int effectiveEarning() const {return 1100;}
};
- Now, in the main function, call the Department, Employee, and Manager classes as shown:
int main()
{
Department dep;
Employee employee;
Manager manager;
std::cout << “Employee: “ << employee.getTotalComp(dep) << “. Manager: “ << manager.getTotalComp(dep) << std::endl;
}
Activity 25: Retrieving User Information
- We have to write the code that can be independent of where the data is coming from. So, we create an interface UserProfileStorage for retrieving the CustomerProfile from a UserId:
struct UserProfile {};
struct UserId {};
class UserProfileStorage {
public:
virtual UserProfile getUserProfile(const UserId& id) const = 0;
virtual ~UserProfileStorage() = default;
protected:
UserProfileStorage() = default;
UserProfileStorage(const UserProfileStorage&) = default;
UserProfileStorage& operator=(const UserProfileStorage&) = default;
};
- Now, write the UserProfileCache class that inherits from UserProfileStorage:
class UserProfileCache : public UserProfileStorage {
public:
UserProfile getUserProfile(const UserId& id) const override {
std::cout << “Getting the user profile from the cache” << std::endl;
return UserProfile(); }
};
void exampleOfUsage(const UserProfileStorage& storage) {
UserId user;
std::cout << “About to retrieve the user profile from the storage” <<std::endl;
UserProfile userProfile = storage.getUserProfile(user);
}
- In the main function, call the UserProfileCache class and exampleOfUsage function as shown:
int main()
{
UserProfileCache cache;
exampleOfUsage (cache);
}
Activity 26: Creating a Factory for UserProfileStorage
- Write the following code that needs the UserProfileStorage class, as shown. To allow that, we provide a factory class, which has a method create that provides an instance of UserProfileStorage. Write this class making sure that the user does not have to manage the memory for the interface manually:
#include <iostream>
#include <memory>
#include <userprofile_activity18.h>
class UserProfileStorageFactory {
public:
std::unique_ptr<UserProfileStorage> create() const {
return std::make_unique<UserProfileCache>();
}
};
- We want the UserProfileStorageFactory class to return a unique_ptr so that it manages the lifetime of the interface:
void getUserProfile(const UserProfileStorageFactory& storageFactory) {
std::unique_ptr<UserProfileStorage> storage = storageFactory.create();
UserId user;
storage->getUserProfile(user);
// The storage is automatically destroyed
}
- Now, in the main function, call the UserProfileStorageFactory class as shown:
int main()
{
UserProfileStorageFactory factory;
getUserProfile(factory);
Activity 27: Using a Database Connection for Multiple Operations
- First, create a DatabaseConnection class that can be used in parallel. We want to reuse it as much as possible, and we know we can use std::async to start a new parallel task:
#include <future>
struct DatabaseConnection {};
- Assuming there are two functions updateOrderList(DatabaseConnection&) and scheduleOrderProcessing(DatabaseConnection&), write a function that creates a DatabaseConnection and gives it to the two parallel tasks. (Note that we don’t know which task finishes first):
void updateOrderList(DatabaseConnection&) {}
void scheduleOrderProcessing(DatabaseConnection&) {}
- You must understand when and how to create a shared_ptr. You can also use the following code to write the shared_ptr correctly.
/* We need to get a copy of the shared_ptr so it stays alive until this function finishes */
void updateWithConnection(std::shared_ptr<DatabaseConnection> connection) {
updateOrderList(*connection);
}
There are several users of the connection, and we do not know which one is the owner, since the connection needs to stay alive as long as anyone is using it.
- To model this, we use a shared_ptr. Remember that we need a copy of the shared_ptr to exist in order for the connection to remain valid:
/* We need to get a copy of the shared_ptr so it stays alive until this function finishes. */
void scheduleWithConnection(std::shared_ptr<DatabaseConnection> connection) {
scheduleOrderProcessing(*connection);
}
- Create the main function as follows:
int main()
{
std::shared_ptr<DatabaseConnection> connection = std::make_shared<DatabaseConnection>();
std::async(std::launch::async, updateWithConnection, connection);
std::async(std::launch::async, scheduleWithConnection, connection);
}