[SOLVED] CS20006 Assignment 3

29.99 $

Category:

Description

Rate

 

We need to develop a rudimentary railway reservation / booking system (somewhat like IRCTC Train Ticket Booking, but extremely scaled down in features). We present various stages of this development process leading finally to the specific tasks of the assignment.

1 Specification

This is the outline specification that has been acquired from the client.

1.1 Requirement Statement

The entities involved in the booking system design include:

  • Station: Every Station is identified by its name. Booking is done between any two Stations.
  • Railways: It is the Indian railways. It has a collection of Stations with pairwise distance between Stations known a priori. Naturally, there can be only one Railways, called IndianRailways, in the system.
  • Date: Any valid date in dd/MMM/yy format.
  • BookingClass: There are several BookingClasses for travel (as in Indian Railways fare classes explained).Each BookingClass has the following attributes:
    • –  Name: Name of the BookingClass
    • –  Fare Load Factor: The factor by which the fare for travel by this BookingClass would be loaded over the base fare. This may change from time to time.
    • –  Seat / Berth: Whether the BookingClass provides sleeping berths or just seats. This will not change in future.
    • –  AC / Non-AC: Whether BookingClass is air-conditioned or otherwise. This will not change in future.
    • –  # of Tiers: How many tiers exist in the coach for this BookingClass. This will not change in future.
    • –  Luxury / Ordinary: Whether this BookingClass is considered luxurious by the Government. This may change from time to time.New booking classes may be added in future.
  • Booking: A Booking is requested with the following information:
    • –  fromStation: Station from which the travel starts for the Booking. This is given by the name of the Station
    • –  toStation: Station at which the travel ends for the Booking. This is given by the name of the Station
    • –  date: Date of travel for the Booking
    • –  bookingClass: BookingClass for the Booking
    • –  passenger: Details of the passenger including name, aadhaar number, date of birth, gender, mobile number, and category of the passenger. This is for future extension and optional for now.On request of a Booking, the same is processed and fare is computed based on the business logic given in Section 1.3. The Booking is then confirmed with PNR and other details on the output. PNR is serially allocated starting with 1.
  • Passenger: A Passenger may have the following details: – name: Name of the passenger

1

– aadhaar #: Aadhaar Number to be used as a unique ID
– dateOfBirth: Date of birth to be used for verification of age
– gender: Gender of the passenger: male or female
– mobile #: Mobile number (optional)
– category: One of General, Ladies, Senior Citizen, Divyaang, Tatkal, Premium Tatkal

1.2 Assumptions

The following assumptions are made for the design:

  • IndianRailways has a given set of Stations with distances known a priori. The list of Stations and distances between them are given as Master Data in Section 1.4. No new station can be added to the IndianRailways and distance between pair of stations do not change.
  • A Booking, as requested, is always available – between any pair of Stations, on any Date, and for any BookingClass
  • No passenger information is considered for the Booking

1.3 Business Logic

The fare between a pair of stations for a booking class is determined through the following steps:

  • Base Fare Rate: The base fare for every KM of travel = Rs. 0.5. This may change from time to time.
  • Base Fare: The base fare between two stations is computed by multiplying the distance between the stations with the base fare for every KM of travel. The base fare applies to the Sleeper booking class.
  • Loaded Fare: For booking classes other the Sleeper, the fare is loaded by a factor with respect to the Sleeper booking class fare as shown in the Booking Class Matrix (Section 1.4.2). The load factor may change from time to time.
  • AC Surcharge: Further, for air-conditioned classes, AC surcharge of Rs. 50 will be charged on the loaded fare. This may change from time to time.
  • Luxury Tax: Finally, there is a 25% luxury tax to be imposed for all luxury class bookings on the fare computed with surcharge. This may change from time to time. The luxury classification as well as taxation rate may change from time to time.
  • Final fare is rounded to the nearest integer.
  • Date has no effect on the fare.
  • Passenger has no effect on the fare as it is being ignored for now.1.3.1 Example
    For a booking from Delhi to Mumbai:

    By AC3Tier:
    • Distance from Delhi to Mumbai = 1447km
    • Basefare=1447km*Rs. 0.5/km=
    • Loaded fare for AC3Tier = Rs. 723.50 * 1.75 = Rs. 1266.125
    • After adding the AC surcharge, we get Rs. 1266.125 + Rs. 50 = Rs. 1316.125 = Rs. 1316/= (rounded) By ACFirstClass:
    • Distance from Delhi to Mumbai = 1447km
    • Basefare=1447km*Rs. 0.5/km=
    • Loaded fare for ACFirstClass = Rs. 723.50 * 3.0 = Rs. 2170.50
    • After adding the AC surcharge, we get Rs. 2170.50 + Rs. 50 = Rs. 2220.50
    • Finally, we levy the luxury tax to get Rs. 2220.50 * 1.25 = Rs. 2775.625 = Rs. 2776/= (rounded)

2

1.4 Master Data

1.4.1 Stations

IndianRailways has five stations, namely: Mumbai, Delhi, Bangalore, Kolkata, and Chennai. The distances between the stations are given below:

Station Distance Matrix

From To Station
Station Mumbai Delhi Bangalore Kolkata Chennai

Mumbai Mumbai Mumbai Mumbai

Delhi Delhi Delhi

Bangalore Bangalore

X

1447

X

Distance in KM

981
2014

1338

1472
2180

IndianRailways has seven booking classes as follows – shown with their respective attributes: Booking Class Matrix

2150

X 1871
X 1659

350

Kolkata
Distance between a pair of stations is symmetric

1.4.2 Booking Classes

Booking Class

ACFirstClass

(1A)

AC2Tier

(2A)

FirstClass

(FC)

AC3Tier

(3A)

ACChairCar

(CC)

Sleeper

(SL)

SecondSitting

(2S)

Name Fare Seat / Load Berth

Factor

AC 3.00 Berth First Class
AC 2.00 Berth 2 Tier

First 2.00 Berth Class
AC 1.75 Berth 3 Tier

AC 1.25 Seat Chair Car
Sleeper 1.00 Berth

Second 0.50 Seat Sitting

AC # Luxury / of Ordinary

Tiers

Yes 2 Yes 2 No 2 Yes 3 Yes 0 No 3 No 0

Remarks

AC 2 berth
coupe
AC 2 berth inside, 2 berth on side Non-AC 2 berth coupe
AC 3 berth inside, 2 berth on side AC chairs

Non-AC 3 berth inside, 2 berth on side
Bench seating

Luxury Ordinary Luxury Ordinary Ordinary Ordinary Ordinary

2

• New booking classes may be added in future
• Fare load factors may change from time to time
• Luxury / Ordinary categorization may change according to tax rules
• Seat / Berth & AC / non-AC classification, and # of tiers will not change in future

Analysis of Specification

We first analyze the specifications to identify the classes and hierarchy for the design. We also try to extract possible constraints on the design.

• Station and Date are simple data classes.
• Class Railways should be a singleton and should contain the master data of stations and distances. The

singleton should be constant as no station can be added and distances cannot be changed.

3

3

Different Booking Classes should be a polymorphic hierarchy rooted at BookingClasses which may be an abstract base class. Instead of making it a flat hierarchy, it would be good to make it a multi-level hierarchy. This would need identification of abstract base sub-classes that are aligned with one or more properties of the Booking Classes.

If multiple properties are used in organizing the hierarchy, then the model would need multiple inheritance. However, we do not want to use multiple inheritance for the associated complications and inefficiency. Rather, we would use single inheritance on the strongest property and use the rest as HAS-A with polymorphic value based on the leaf class.

Naturally, there can be two candidates for this as Fare Load Factor, # of Tiers, and Luxury / Ordinary are more like pure attributes and clearly not useful for hierarchy:

  • –  AC or Non-AC: Air-condition leads to comfort level, and is not fundamental to travel. So this is a weak candidate.
  • –  Seat or Berth: This is fundamental property for a rail travel. So this is a strong candidate.
    So we may introduce several intermediate abstract base classes on the strong property and its closestassociated attribute, viz. the number of tiers.

    Further we may note that every concrete booking class has all fixed properties and there should be no need to construct more than one object for any of them. So there may be a singleton constant object for each which, kind of, will stand for its polymorphic type.

    The hierarchy should be extensible in future as new booking classes are added.
    Booking may be treated as a simple concrete class with the parameters mentioned in the specification.

    We may keep Passenger as a null-able default for future extension.
    Booking may be also be modeled as a polymorphic base class as with the introduction of Passenger in

    future it is likely to lead to a booking hierarchy.

    Class Passenger may be an empty abstract base class. Since we are not going to use it, we would not need to make objects for the same. However, it would be good to have it as a polymorphic base for future extension, especially since the specification talks of various categories of passengers.

    High Level Design

Based on the analysis, now we carry out the High Level Design (HLD) below for Classes, Interfaces, Constants, Statics, Exceptions, and overall design considerations.

3.1 Design Principles

The following design principles may be adhered to in the HLD:

• Flexible & Extensible Design

  • –  The design should be flexible. That is, it should be easy to change the changeable parameters (like base rate, load factor etc.) easily from the Application space. This should should not need re-building of the library of classes.
  • –  The design should be extensible. That is, it should be easy to add new behaviour (classes) wherever indicated in the specification (like Booking Classes, Booking, Passenger, etc.). This should not require a re-coding of the existing applications.• Minimal Design
    – Only the stated models and behaviour should be coded. No extra class or method should be coded.

    – Less code, less error principle to be followed. • Reliable Design

    – Reliability should be a priority. Everything should work as designed and coded. – Data members, methods and objects should be made constant wherever possible. – Parameters should be appropriately defaulted wherever possible

• Testable Design

4

  • –  Every class should support the output streaming operator for checking intermittent output if needed.
  • –  Every class should be tested with an appropriate test application for its unit functionality (Sec-tion 6.1).
  • –  TestApplications(Section6.2)andregressiontestsuitesshouldbedesignedfortestingtheapplicationon (at least) the common scenarios of use.

    3.2 Classes

  • Class Station HAS-A name.
  • Class Railways is a singleton called IndianRailways. It has a collection of the Stations and their mutualdistances. IndianRailways is a constant object.
  • Class Date is discussed in the lecture modules.
  • Class BookingClasses HAS-A loadFactor. Remaining attributes may be encoded on the methods in the hierarchy classes.
  • Class Booking HAS-A fromStation, toStation date, and bookingClass from the booking request where every station name, date and booking class are assumed to have been given correctly. Further it HAS-A fare computed and PNR allocated. Optionally, it may HAS-A bookingStatus (which would be true for this assignment always) and bookingMessage (which may be “BOOKING SUCCEEDED” for this assignment always).Booking should support Passenger as a null-able parameter for future extension.
  • You may add any class, any data member to a class, or any hierarchy as you need for implementation.Justify your design choice for them.

    3.3 Interfaces

  • Constructors / Destructors: Proper constructor and destructor for every class
  • Copy Functions: Provide user-defined Copy Constructor and / or Copy Assignment Operator for a classif used in the design (should not be needed). Otherwise, block them.
  • Provide output streaming operator for every class to help output process as well as debugging
  • Class Station to have GetName() for accessing its name and GetDistance(.) to get distance to another station.
  • Class Railways to have GetDistance(., .) to get distance between a pair of stations. It should also have proper interface for making it a singleton IndianRailways
  • Class BookingClasses to have GetLoadFactor(), GetName(), IsSitting(), IsAC(), GetNumberOfTiers(), and IsLuxury() to get access to various BookingClasses properties. Depending on the polymorphic hier- archy, these methods may be non-polymorphic and / or polymorphic (and in some case pure) in Book- ingClasses and its various derived classes. Consider making them const methods.
  • Class Booking to have ComputeFare() to implement the fare computation logic. Should it be virtual (polymorphic) for future extensions?
  • You may add any interface to a class (or private / protected methods) as you need for implementation. Justify your design choice for them.3.4 Constants

    The following should be static constants in appropriate classes: • Load Factors of various BookingClasses
    • Base Fare Rate: Rs. 0.50 / km
    • AC Surcharge: Rs. 50.00

• Luxury Tax: 25% on booking amount

5

3.5 Statics

• • • •

3.6

• •

Class Date to have month and day names.

Class Railways to have sStations (list of stations) and sDistStations (distance between stations).

Class BookingClasses to have load factors.

Class Booking to have sBaseFarePerKM, sBookings (list of bookings done), sBookingPNRSerial (next available PNR), sACSurcharge, and sLuxuryTaxPercent

You may add any static to a class as you need for implementation. Errors & Exceptions

All Booking requests are taken to be correct. That is, the Staions as mentioned – do exist, the Date is valid (in future), and no invalid BookingClass is requested

There is no error in input, processing, or output.

No error or exception handling to be incorporated in the design for this assignment. However, structure the code flow well so that they can be incorporated later with minimal changes (adhering to the need of flexibility).

Low-Level Design

4

Based on the High Level Design (HLD), we now perform the Low Level Design (LLD). LLD makes use of the specific constructs and idioms of C++.

4.1 Design Principles

The following design principles may be adhered to in the LLD:

• Encapsulation

  • –  Maximize encapsulation for every class
  • –  Use private access specifier for all data members that are not needed by derived classes, if any. Use protected otherwise.
  • –  Use public access specifier for interface methods and static constants and friend functions only. • STL Containers– Use STL containers (like vector, map, hashmap, list, etc.) and their iterators. Do not use arrays – Use iterators for STL containers. Do not use bare for loops.

    • Pointers & References

    • –  Minimize the use of pointers. Use pointers only if you need null-able entities
    • –  If you use pointer for dynamically allocated objects (should be minimized), remember to delete at an appropriate position.
    • –  Use const reference wherever possible.

4.2 Design of Classes, Data Members & Methods

This is left as an exercise in the assignment. Design based on the HLD and the principles and document well.

5 Implementation

After completing the LLD, we perform the coding (implementation). In this we adhered to a set of basic guidelines and code organization.

5.1 Basic Coding Guidelines

An indicative set of guidelines are listed in Section A. You may add more on your own. 6

5.2 Code Organization

Ideally, the definition of every class (or hierarchy) should be put in a corresponding .h file with the static definitions and method implementations in the respective .cpp. The application should be in Application.cpp file. However, for simplicity, it would be acceptable if all the codes are put in the Application.cpp file with the application.

6 Test Plan

We also need to prepare a test plan to test the implementation at different stages of development so that better quality and productivity can be ensured. Variety of test processes are common. We shall follow two of these in the current assignment.

6.1 Unit Tests

This is typically the basic test process which is engaged during development (however, it may be useful for future testing and debugging as well). In this, we test every class as it is implemented. We test all non-static & static member functions and friend functions. For a class hierarchy, the unit test is done typically at both concrete classes and the overall hierarchy levels specifically checking the polymorphic methods.

For the purpose of understanding, in Section B we illustrate the test plan and test function for a few unit cases for the Fraction class we have developed in Assignment 2.

6.2 Application Test

After the units have been tested, we integrate them into the application and test various scenarios for the application. A sample test application was provided for the Fraction class in Assignment 2. However, since it was just a single class application, the application code looked pretty much like the unit test application code with the exception of the comparison with golden data.

Like the units, we again need to enumerate scenarios for the application in the test plan and write the applica- tion test.

In addition, a sample test application for booking is given in Section C with the expected output in Section C.1. Your codes should pass this test application too.

7 Tasks

The following tasks are to be completed for the assignment:

8

1.

2. 3.

4. 5.

Design: Complete the HLD and the LLD. Document the salient points from your design in Design.txt. Follow the quality guidelines and design principles outlined above.

Implementation: Implement the LLD in C++ following the basic coding guidelines (Section A).

Test Planning: Write a unit test and application test plan in Testplan.txt covering all scenarios. Note that no wrong input or erroneous data situation is to be handled. For example, a Date specified will always be valid. So plan tests based only on correct input data.

Testing: Implement unit test and application test codes and perform testing. For application testing, test with the application given in Section C as well as the application developed by you from the testplan.

Bundle and Submissions: Name and bundle your files as given in Section 8 and submit to Moodle.

Submission of Files

The following files must be submitted as a single ZIP file: 1. Documents.zip

(a) Design.txt: The design document stating the design details (especially LLD) with principles and guidelines followed

7

(b) Testplan.txt: The testplan document stating scenarios for unit tests (with golden output if needed) and the scenarios of the test application.

2. Source.zip:

  1. (a)  Source (.cpp) and header (.h) files for implementation.
  2. (b)  Source (.cpp) and header (.h) files for test application.
  3. (c)  README file that describes the contents of every file in the Source.zip. Also, mention the compiler (with version, and compiler options, if any) that you have used.

3. Outputs.zip

  1. (a)  Output from the given test application (Section C)
  2. (b)  Output from the your test application developed from the test plan
  • The output file can be generated by redirecting the output to a text file or by copy-paste from the console in a text file.
  • There is no need to include the a.out file.
    Every file (with the exception of program output) must have your name and roll number.

9 Marks

The marks are distributed as follows:

Design

[20]

[25]

[20]

[15]

[20]

Non-static & static data members
Non-static1 & static member functions signatures friend function signatures
Design of BookingClasses Hierarchy

Implementation

Breakup

Non-static & static member functions Static data members
friend function

Test Planning

Breakup

Unit Test Scenarios & Goldens (Completeness of scenarios) Application Test Scenarios

Testing

Breakup

Unit Test Application (adherence to test plan) Own Test Application (adherence to test plan)

Output
On given Test Application (Section C)

Output

Quality of Design & Implementation

[4] [4] [2]

[10]

[15] [5] [5]

[15] [5]

[7] [5] [3]

[3] [3] [5] [4] [5]

Breakup

1 Non-static include non-polymorphic as well as polymorphic member functions

Adherence to Design Protocols Singletons

const-ness

Coding Guidelines Extensibility & Flexibity Code Comments

Breakup

8

A Coding Guidelines

It is advised to follow the guidelines below while coding:
• Use CamelCase for naming variables, classes, types and functions
• Every name should be indicative of its semantics
• Start every variable with a lower case letter
• Start every function and class with an upper case letter
• Use a trailing underscore ( ) for every non-static data member
• Use a leading ’s’ for every static data member
• Do not use any global variable or function (except main(), and friends)
• No constant value should be written within the code – should be put in the application as static • Prefer to pass parameters by value for build-in type and by const reference for UDT
• Every polymorphic hierarchy must provide a virtual destructor in the base class
• Prefer C++ style casting (like static cast<int>(x) over C Style casting (like (int))
• The project should compile without any compiler warning
• Indent code properly
• Comment the code liberally and meaningfully
• Adopt more guidelines as you prefer. Try to document them

9

B Unit Testing Fraction Class

As an example of unit test, let us consider the Fraction class we have developed in Assignment 2. We illustrate the test for its one overloaded constructor (Fraction(int = 1, int = 1)) and operator+ only. For this we enumerate the different possible cases to test in a unit test plan.

B.1 Unit Test Plan for Fraction
We elucidate the unit test plan for constructor and add operator.

B.1.1 Test Scenarios for Construction of Objects

We consider the Fraction(int = 1, int = 1) constructor. The scenarios (including normalization, sign han- dling, and default) are:

• Normalization

1. Improper fraction in reduced form
2. Improper fraction in irreduced form
3. Proper fraction in reduced form
4. Proper fraction in irreduced form
5. Fraction 0 with arbitrary denominator

• Sign handling

1. Fraction with negative numerator
2. Fraction with negative denominator
3. Fraction with negative numerator & denominator

• Default parameters
1. Fraction with only numerator

2. Fraction with no parameter

B.1.2 Test Scenarios for Addition Operator

Weconsidertheoverloadedaddoperatorfriend Fraction operator+(const Fraction&, const Fraction&). The scenarios are (considering the given constructor):

1. Add two fractions
2. Add a fraction with an integer 3. Add an integer with a fraction

Rest of the Fraction class can be tested by preparing a similar plan. B.2 Unit Test Implementation for Fraction

For unit testing, we write a static function in the class that has this test code. In the application, we use the ’golden output’ for every test and compare for equality. If the expected output is not obtained, a message on test error is printed.

B.2.1 Fraction Class Code

Here is the relevant parts of the class including the static test function signature

#ifndef __FRACTION_HXX// Control inclusion of header files
#define __FRACTION_HXX
/************ C++ Headers ************************************/
#include <iostream>// Defines istream & ostream for IO
using namespace std;

10

/************ CLASS Declaration ******************************/
class Fraction {

public:

// CONSTRUCTORS
// ------------
Fraction(int = 1, int = 1);// Uses default parameters. Overloads to
// Fraction(int, int);
// Fraction(int);
// Fraction();
// BINARY ARITHMETIC OPERATORS USING FRIEND FUNCTIONS
// --------------------------------------------------
friend Fraction operator+(const Fraction&, const Fraction&);
// Other member functions, static functions, friend functions
// ...
// STATIC UNIT TEST FUNCTION
// ------------------------
static void UnitTestFraction(); // Test application for Fraction
// Data members
// ...
#endif // __FRACTION_HXX

B.2.2 Fraction Class Unit Test Application Code

// To unit test class Fraction
void Fraction::UnitTestFraction() {
    // Check difference cases of fraction construction
    Fraction f1(5, 3);   // Improper fraction in reduced form
    Fraction f2(15, 9);  // Improper fraction in irreduced form
    Fraction f3(3, 5);   // Proper fraction in reduced form
    Fraction f4(9, 15);  // Proper fraction in irreduced form
    Fraction f5(0, 2);   // Fraction 0 with arbitrary denominator
    Fraction f6(-2, 3);  // Fraction with negative numerator
    Fraction f7(2, -3);  // Fraction with negative denominator
    Fraction f8(-2, -3); // Fraction with negative numerator & denominator
    Fraction f9(5);      // Fraction with only numerator
    Fraction f10;        // Fraction with no parameter
    // Check if every object is constructed in the desired way
    if (f1.iNumerator_ != 5 || f1.uiDenominator_ != 3)    // Check members
        cout << "Fraction Consturction Error on Fraction(5, 3)" << endl;
    if (f2.iNumerator_ != 5 || f2.uiDenominator_ != 3) // Check members & reduction
        cout << "Fraction Consturction Error on Fraction(15, 9)" << endl;
    if (f3.iNumerator_ != 3 || f3.uiDenominator_ != 5) // Check members
        cout << "Fraction Consturction Error on Fraction(3, 5)" << endl;
    if (f4.iNumerator_ != 3 || f4.uiDenominator_ != 5) // Check members & reduce
        cout << "Fraction Consturction Error on Fraction(9, 15)" << endl;
    if (f5.iNumerator_ != 0 || f5.uiDenominator_ != 1) // Check members with denominator = 1
        cout << "Fraction Consturction Error on Fraction(0, 2)" << endl;

11

    if (f6.iNumerator_ != -2 || f6.uiDenominator_ != 3) // Check members
        cout << "Fraction Consturction Error on Fraction(-2, 3)" << endl;
    if (f7.iNumerator_ != -2 || f7.uiDenominator_ != 3) // Check members & sign flip
        cout << "Fraction Consturction Error on Fraction(2, -3)" << endl;
    if (f8.iNumerator_ != 2 || f8.uiDenominator_ != 3) // Check members & sign flip
        cout << "Fraction Consturction Error on Fraction(-2, -3)" << endl;
    if (f9.iNumerator_ != 5 || f9.uiDenominator_ != 1) // Check default on second parameter
        cout << "Fraction Consturction Error on Fraction(5)" << endl;
    if (f10.iNumerator_ != 1 || f10.uiDenominator_ != 1) // Check default on both parameters
        cout << "Fraction Consturction Error on Fraction" << endl;
    // Check addition of two fractions
    Fraction f11 = f1 + f3; // Add two fractions
    Fraction f12 = f1 + 1;  // Add a fraction with an integer
    Fraction f13 = 1 + f3;  // Add an integer with a fraction
    if (f11.iNumerator_ != 34 || f11.uiDenominator_ != 15) // Check members on add
        cout << "Fraction Addition Error on Fraction(5, 3) + Fraction(3, 5)" << endl;
    if (f12.iNumerator_ != 8 || f12.uiDenominator_ != 3) // Check members on add
        cout << "Fraction Addition Error on Fraction(5, 3) + 1" << endl;
    if (f13.iNumerator_ != 8 || f13.uiDenominator_ != 5) // Check members on add
        cout << "Fraction Addition Error on 1 + Fraction(3, 5)" << endl;

return; }

12

C Test Application for Booking

// Test application for booking
void BookingApplication() {
    // Bookings by different booking classes
    // <BookingClasses>::Type() returns the constant object of the respective type
    Booking b1(Station("Mumbai"), Station("Delhi"), Date(15, 2, 2021), ACFirstClass::Type());
    Booking b2(Station("Kolkata"), Station("Delhi"), Date(5, 3, 2021), AC2Tier::Type());
    Booking b3(Station("Mumbai"), Station("Kolkata"), Date(17, 3, 2021), FirstClass::Type());
    Booking b4(Station("Mumbai"), Station("Delhi"), Date(23, 3, 2021), AC3Tier::Type());
    Booking b5(Station("Chennai"), Station("Delhi"), Date(25, 4, 2021), ACChairCar::Type());
    Booking b6(Station("Chennai"), Station("Kolkata"), Date(7, 5, 2021), Sleeper::Type());
    Booking b7(Station("Mumbai"), Station("Delhi"), Date(19, 5, 2021), SecondSitting::Type());
    Booking b8(Station("Delhi"), Station("Mumbai"), Date(22, 5, 2021), SecondSitting::Type());
    // Output the bookings done where sBookings is the collection of bookings done
    vector<Booking*>::iterator it;
    for (it = Booking::sBookings.begin(); it < Booking::sBookings.end(); ++it) {
        cout << *(*it);
    }

return; }

int main() {

    BookingApplication();

return 0; }

Your implementation of classes needs to compile with the above application and output details of every booking done. A sample output could look as follows. It is not necessary to match every line of the output. But the same information should be available in your output.

C.1 Test Output

BOOKING SUCCEEDED:
PNR Number = 1
From Station = Mumbai
To Station = Delhi
Travel Date = 15/Feb/2021
Travel Class = AC First Class
 : Mode: Sleeping
 : Comfort: AC
 : Bunks: 2
 : Luxury: Yes

Fare = 2776

BOOKING SUCCEEDED:
PNR Number = 2
From Station = Kolkata
To Station = Delhi
Travel Date = 5/Mar/2021
Travel Class = AC 2 Tier
 : Mode: Sleeping
 : Comfort: AC
 : Bunks: 2
 : Luxury: No

13

Fare = 1522

BOOKING SUCCEEDED:
PNR Number = 3
From Station = Mumbai
To Station = Kolkata
Travel Date = 17/Mar/2021
Travel Class = First Class
 : Mode: Sleeping
 : Comfort: Non-AC
 : Bunks: 2
 : Luxury: Yes

Fare = 2518

BOOKING SUCCEEDED:
PNR Number = 4
From Station = Mumbai
To Station = Delhi
Travel Date = 23/Mar/2021
Travel Class = AC 3 Tier
 : Mode: Sleeping
 : Comfort: AC
 : Bunks: 3
 : Luxury: No

Fare = 1316

BOOKING SUCCEEDED:
PNR Number = 5
From Station = Chennai
To Station = Delhi
Travel Date = 25/Apr/2021
Travel Class = AC Chair Car
 : Mode: Sitting
 : Comfort: AC
 : Bunks: 0
 : Luxury: No

Fare = 1413

BOOKING SUCCEEDED:
PNR Number = 6
From Station = Chennai
To Station = Kolkata
Travel Date = 7/May/2021
Travel Class = Sleeper
 : Mode: Sleeping
 : Comfort: Non-AC
 : Bunks: 3
 : Luxury: No

Fare = 830

BOOKING SUCCEEDED:
PNR Number = 7
From Station = Mumbai
To Station = Delhi
Travel Date = 19/May/2021
Travel Class = Second Sitting
 : Mode: Sitting
 : Comfort: Non-AC
 : Bunks: 0
 : Luxury: No

Fare = 362

14

BOOKING SUCCEEDED:
PNR Number = 8
From Station = Delhi
To Station = Mumbai
Travel Date = 22/May/2021
Travel Class = Second Sitting
 : Mode: Sitting
 : Comfort: Non-AC
 : Bunks: 0
 : Luxury: No

Fare = 362

The above test application is given as a sample. In addition, you should write your own unit and application tests.

15

D Clarifications
D.1 HAS-A relationship

A HAS-A relationship simply means having a component of an object (part of object) – it is Composition or Aggregation. It translates to having data members.
For example,

Car HAS_A Registration_Number
Car HAS_A Engine
Car HAS_A Model_Name
Car HAS_A Wheel // 4 wheeler
Car HAS_A Owner
Wheel HAS_A SerialNumber
Wheel HAS_A Make
Wheel HAS_A Radius
Engine HAS_A Number
Engine HAS_A Capacity // in cc

is modeled as

class Engine {
protected: // Engines could be specialized
    const string engineNumber_; // every engine has a unique number that cannot change
    const int capacity_;        // every engine has a unique capacity that cannot change
};
class Wheel {
protected: // Wheels could be specialized
    const string SerialNumber_; // every wheel has a unique serial number that cannot change

}

const string& make_;
const double radius_;
// Every wheel has a make that can be from a set of known makers
// Hence, reference to static maker names should be used
// Make cannot change
// Radius cannot change

};

class Car {
protected: // Cars could be specialized
    const string registrationNumber_;
    const Engine engine_;
    const string& modelName_;
    Wheel wheels_[4];
    string& ownerName_;
// Registration number cannot change
// Engine cannot change
// Every car has a model that can be from a set of
// known models. Hence, reference to static model
// names should be used. Model cannot change
// Wheels may change; but must all be the same
// make / radius
// Owner can change for re-sale

We should not use private inheritance for HAS-A.

Note: Besides showing HAS-A, the above example also illustrates different situations for const-ness and reference

D.2 Private Inheritance

Note the following from the presentation:

  • Private inheritance means nothing during software design, only during software implementation
  • Private inheritance means is-implemented-in-terms of. It is usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions

16

• It is good for enforcing a policy over a hierarchy. For example the following example of an uncopyable base class, which can be used to disable the copy constructor and assignment operator in derived classes:

             class Uncopyable {
                 protected:
                     Uncopyable() {}
                     ~Uncopyable() {}
                 private:
                     Uncopyable(const Uncopyable&);
                     Uncopyable& operator=(const Uncopyable&);
             };
             class Derived : private Uncopyable { ... };

We should not use private inheritance unless we really need it. It is usually rare for most projects.

D.3 Virtual Destructor

The guidelines have the following:

Every polymorphic hierarchy must provide a virtual destructor in the base class

This has attracted the following observation:

An abstract base classes cannot have a constructor or a destructor. So does this mean that this guideline is redundant?

So let us try to understood an Abstract Base Class better. It is a base class which has at least one pure virtual function. That’s it. And hence we cannot instantiate it; but we can always construct objects of it.

In any hierarchy, we must first construct the base class object before we can construct the derived class object (reverse for the destructor). So all classes concrete or abstract, must have constructor and destructor (which either you provide or get free from the compiler). A class is Abstract means that we cannot have an instantiation for it. But it will always get constructed and destructed as a base part of the derived class.

Finally, if the destructor is not virtual in a polymorphic hierarchy, then the destruction will not get dynamically dispatched to the derived class destructor and slicing will result. Let us look at the example below:

#include <iostream>
#include <string>
using namespace std;
class Base {
    const int id_;
    static int sObjId;
protected:
    Base() : id_(sObjId++) { cout << "Base Constructor for object id = " << id_ << "n"; }
public:
    virtual ~Base();        // Line 1
    //virtual ~Base() = 0;  // Line 2
    virtual string GetName() const = 0; // Pure virtual function
    int GetId() const { return id_; }
};
Base::~Base() { cout << "Base Destructor for object id = " << id_ << "n"; }

17

class DerivedOne : public Base {
    const string name_;
public:
    DerivedOne(const string& name) : Base(), name_(name) { cout << "DerivedOne Constructorn"; }
    ~DerivedOne() { cout << "DerivedOne Destructorn"; }
    string GetName() const {
        return "DerivedOne: " + name_ + "n";

} };

class DerivedTwo : public Base {
    const string name_;
public:
    DerivedTwo(const string& name) : Base(), name_(name) { cout << "DerivedTwo Constructorn"; }
    ~DerivedTwo() { cout << "DerivedTwo Destructorn"; }
    string GetName() const {
        return "DerivedTwo: " + name_ + "n";

} };

int Base::sObjId = 0;
int main() {
    //Base b; // Line 3: Does not compile - Base is abstract
    const Base *p = new DerivedOne("obj1");
    cout << p->GetName();
    const Base *q = new DerivedTwo("obj2");
    cout << q->GetName();
    delete q;
    delete p;

return 0; }

class Base is abstract due to GetName() and we cannot instantiate (un-commenting Line 3 is an error). But it has constructor and destructor both. So, from the above code we get the following output as expected.

Base Constructor for object id = 0
DerivedOne Constructor
DerivedOne: obj1
Base Constructor for object id = 1
DerivedTwo Constructor
DerivedTwo: obj2
DerivedTwo Destructor
Base Destructor for object id = 1
DerivedOne Destructor
Base Destructor for object id = 0

Now, in Line 1, remove the word virtual, and the output changes to:

Base Constructor for object id = 0
DerivedOne Constructor
DerivedOne: obj1
Base Constructor for object id = 1
DerivedTwo Constructor
DerivedTwo: obj2

18

Base Destructor for object id = 1
Base Destructor for object id = 0

Clearly, the destructors of DerivedOne and DerivedTwo are not getting called as the destructor of Base is non-polymorphic. This is called slicing, as we just chop off the the head of the object leaving the torso behind. Interestingly, we can make the destructor itself pure virtual as well in (comment Line 1 and un-comment Line 2), and still provide its implementation, and we will get correct output as above. Often that is the default way around to start a polymorphic hierarchy:

class Base {
public:
    virtual ~Base() = 0;
};
// Derived classes ...

Hence, Every polymorphic hierarchy must provide a virtual destructor in the base class

D.4 Source Header File and Library Conventions

D.4.1 Source File

Every C++ (C) source file should have .cpp (.c) extension. Many compilers decide the language rules to apply based on the file extension.

Normally, in a C++ project, even pure C code can be written appropriately for a C++ compiler (check the header file conventions for that). So .c extension may not be used at all. These were needed earlier (over a decade or more earlier) when C++/C mix in a project was needed to be handled separately either by C compilation (.c) or by C++ compilation (.cpp). In fact, some compilers also had a support for .cxx where the compiler automatically detected whether to compile by C or by C++.

So, we shall use .cpp only. D.4.2 Header File

Every header file (C++ or C) should be .h because we should write C in C++ compilable manner. However, since a header file is included in some source/s, its extension really does not matter to the compiler. It is more for the readability of the program.

Historically, .hpp extension was used by some companies to mean a C++ header and distinguish from a C .h header. These are legacy now. Some companies, in keeping with .cxx, used .hxx too.

So, we shall use .h only.
Once-only Inclusion of Header Files: Enclose every project header file with CPP guards as follows:

// Start of "Station.h"
#ifndef _STATION_H
#define _STATION_H
// Header file codes
#endif //_STATION_H
// End of "Station.h"
// Start of "Railways.h"
#ifndef _RAILWAYS_H
#define _RAILWAYS_H
#include "Station.h"
// Header file codes

19

#endif //_RAILWAYS_H
// End of "Railways.h"

Now when we include, say in, Booking.cpp as:

// Start of Booking.cpp
#include "Station.h"
#include "Railways.h"
// Source file codes
// End of Booking.cpp

the second inclusion of Station.h through Railways.h is excluded by the CPP guards. Otehrwise, we shall face re-definition errors.

In the C and C++ programming languages, #pragma once is a non-standard but widely supported pre-processor directive designed to cause the current source file to be included only once in a single compilation. It is better to avoid it (and use the scheme given above) for better portability.

D.4.3 Include Library

We have to be, however, more careful when we use the include library names as explained below:
• C++ Standard Library Headers: Include a C++ standard library header by name within angle

brackets (<>):
#include <iostream>

         using namespace std;

All symbols are within the std namespace.
With angle brackets (<>), the header will be searched in the C++ standard library path defined in the

system.
• C Standard Library Headers: In a C++ source, include a a C standard library header by name within

angle brackets after prefixing it with c and dropping the .h. So stdio.h is included as: #include <cstdio>

         using namespace std;

All symbols are again within the std namespace.
With angle brackets (<>), the header will be searched in the C++ / C standard library path defined in

the system. cstdio is actually the C++ version of stdio.h.
In a pure C source (that will be compiled as C), include a C standard library header by name (with .h

extension) within angle brackets (<>). So stdio.h is included as: #include <stdio.h>

All symbols are within the global namespace.

With angle brackets (<>), the header will be searched in the C standard library path defined in the system.

Never include a C standard library header in C++ as the .h version
• Project Headers: Include a project header by name within a pair of double quotes (“”):

20

D.5

The symbols will be in the global namespace or the namespaces defined in the project headers. With double quotes (“”), the header will be searched in the project path defined for the project.

static, const, Reference & Singleton What should be Singleton?

There are several singletons on the project including the IndianRailways, BookingClasses etc.

It would be best to implement them as Meyer’s singleton (example is given in Section D.6.3) as no life-time management will be needed. In the solution discussed in the class, we create the singleton dynamically. So the responsibility to release it remains with programmer before main() terminates. With a number of singletons in the project, it is very likely to miss the release and the resources would leak (the singleton may be holding some system resource like database access).

What should be the access specifier?
Always use private for data members, protected for data members needed by the inherited classes, and

public otherwise. Constructor and Destructor should be private for singletons. What should be static?

Any object that should be available from the beginning to the end of the program, should be a static. Examples are collection of Stations, distances between Stations etc.

What should be const? A value / object, by default, should be const unless it needs to change during the computation.

  • –  What should be const value member?
    A value / object that cannot change after the construction of an object. Like name of a Station etc. It is constructed / destructed with the object.
  • –  What should be const reference member?
    A value / object that cannot change after the construction of an object and refers to an object that is defined (constructed / destructed) by some other object already.
  • –  What should be const member function?
    By default every member function should be const. Make it non-const if it is needed to changevalues and / or consider mutable to selectively change only a few data members in an otherwise const object.

    const-ness should be treat more semantically than syntactially. For more details check Section D.6.3

    What should be the access specification?

  • –  private / protected: All data members, static or non-static, should be protected for a base class and private otherwise. Constructor / Destructor etc. should be protected for a singleton that is a base class, and private otherwise.
  • –  public: All interface methods need to be public.A Few Simple Design Principles

    Class Members

D.6

D.6.1

#include "Railways.h"
  • For items like name (or loadFactor ), it is better to use simple string (or double). Define a new class when it has behaviour, validation etc. Like Date (dd/mmm/yy: 01/Mar/21 or dd/mm/yy: 01/03/21).In the same line, we should make name a private member of, say, class Station like const string name ;
  • Yes, it is good to block copy functions (by providing them in private) if you do not need them so that in case they are used, you will get a compilation error. Note that in most contexts you should be using call- /return-by-reference for UDTs (and call-/return-by-value for built-in types) and hence you would rarely need copying. Define properly when you need.

21

D.6.2 Hierarchy Design

It is time to vaccinate for COVID-19. Let us look at the priority rules1 for vaccination to decide on a model of the population for a vaccination schedule allocation application. In decreasing order of priority, vaccine should be administered too:

1. Healthcare Workers (HCWs)
2. Frontline Workers (FLWs). FLW is a generalization of HCW
3. Senior Citizens (SCs), that is age > 59 years
4. Person with Co-Morbidity (PCMs), where age > 44 years and has one of the following Co-Morbidity

• Diabetes
• Heart failure with hospital admission in the past one year
• Post-cardiac transplant
• Valvular heart disease
• End-stage kidney disease on haemodialysis
• Severe respiratory disease with hospitalisation in the last two years • Primary immunodeficiency diseases
• HIV infection
• Angina and hypertension/diabetes on treatment
• …

5. More conditions of Phase 3 …
Looking at the above, we can identify the following attributes:

  1. Nature of Work: Non-Frontline, Frontline, and Healthcare
  2. Age: Below 45, Between 45 and 59, and Above 59
  3. Co-Morbidity: Some 20 odd types. For simplicity we take this as Boolean: Non-Co-Morbid and Co- Morbid
  4. Gender: Female and Male. It is natural to have some criteria in future on this.
  5. State: States on India. It is natural to have some criteria in future on this.

It is easy to see that the attributes will be available in the population is a totally mixed manner and in total we have 3 × 3 × 2 × 2 = 36 possible combination classes (ignoring the explosive possibility of States). Question is – how do we put them in a hierarchy? Naturally, every class will multiply inherited these properties in some path or other. That would not be a hierarchical design, it will be an anarchy.

So we decide to rank the attributes by their uniqueness. Model single inheritance on one or two of them and keep the rest as pure attribute values to be taken care of in the member function algorithms. For example, if we consider age as the most priority attribute (as we do in many population-related applications), we shall end up in a chaos as HCW, FLW, and Non-FLW would all exist in all ages. So age is a bad choice as a primary.

Hence, for a proper hierarchy, we should look at the natural flow of the model. Here, we can easily see that HCW in (1) is most specialized, FLW in (2) is the next, and (3) onwards it is the non-FLW. So Nature of Work should be the primary hierarchy parameter. Next, we see that other attributes have no effect on HCW and FLW. So only non-FLW is to be classified further. Here (3) predominates the age rule. Finally the Co-Morbidity takes over. Going by this principle from the analysis, we get 9 concrete classes and 3 levels.

1These are sample, incomplete, inaccurate rules not to be used in real COVID vaccination decision making 22

// General population
// Abstract Base Class
class Person { };
// Frontline Workers
class FLW : public Person { };
// Non-Frontline Workers
class nonFLW : public Person { };
// Healthcare Workers
class HCW : public FLW { };
// Senior Citizen. Age > 59
class SC : public nonFLW { };
// Mid-Age Citizen. Age < 59 & > 44
class MC : public nonFLW { };
// Young Citizen. Age < 45
class YC : public nonFLW { };
// Co-Morbidity
class CM : public MC { };
// NonCo-Morbidity
class nonCM : public MC { };

// (1) to (5)

// (2)

// (3) to (5)

// (1)

// (3)

// (4) to (5)

// (5)

// (4)

// (5)

We can make it more compact, if we remove class CM and class nonCM from the hierarchy and just treat co-morbidity as a boolean attribute. This gives us 7 concrete classes and 2 levels. Naturally, gender and state both are also treated as attributes and we do not consider them for the hierarchy design.

So, the thumb rule would be to rank the attributes for the computation model and use one or two of them to create a two or three level hierarchy (ISA). Rest of the attributes should remain as attributes (HAS-A).

D.6.3 Semantics of const-ness
We often get confused between different shades of const-ness and their treatment in software design. It can

feature in a variety of different contexts including:
• Natural Constants [static const in library]: Like π, e, c, etc.
• Universal Constants [static const in library]: Like days of the week etc. • Design Constants

– Value Constants: Values that are constant for

  • ∗  Life time of a particular object in a particular execution of the software [const data member]: Like Basic Pay of an Employee
  • ∗  Life time of a particular object in every execution of the software [const data member]: Like Date of Birth of an Employee
  • ∗  Life time of all objects in a particular execution of the software [static const in application]: Like Month of Pay Computation
  • ∗  Life time of all objects in every execution of the software in a period, but can periodically change [static const in application]: Like the rate of Dearness Allowance of Employees (changes every six months to a year)
  • ∗  Life time of all objects in every execution of the software in a period, but can occasionally change – a change which is likely to software version change [static const in library]: Like Pay Scales / Levels of Employees (may change once a five to ten years)

23

– Type Constant [const or non-const singleton in code contexts]: Like Gender can be of one of two types – male and female; Dexterity can be of one of two types – left and right; Verb tenses in English can be of one of three types – present, past and future, etc. These are type constants that can be coded by representative singleton type objects. These objects stand for the type and may or may not be const. Here is a code example for gender.

  #include <iostream>
  #include <string>
  using namespace std;
  class Gender { const string name_;
  protected:
      Gender(const string& name) : name_(name) {}
  public:
      virtual const string Print() const = 0;
  };
  class Male : public Gender {
      Male() : Gender(Male::sName) {}
      static const string sName;
  public:
      // Singleton of Male that represents the type Male
      static const Gender& Type() {
          // May be non-const if the type has changeable behavior
          static const Male theObj;
          return theObj;
      }
      const string Print() const { return "I am a man"; }
  };
  class Female : public Gender {
      Female() : Gender(Female::sName) { }
      static const string sName;
  public:
      // Singleton of Female that represents the type Female
      static const Gender& Type() {
          // May be non-const if the type has changeable behavior
          static const Female theObj;
          return theObj;
      }
      const string Print() const { return "I am a woman"; }
  };
  // Names defined as static constants
  const string Male::sName = "Male";
  const string Female::sName = "Female";
  class Person {
      const string name_;
      const Gender& gender_;
  public:
      Person(const string& name, const Gender& gender) :
          name_(name), gender_(gender) {}
      friend ostream& operator<<(ostream& os, const Person& p) {
          os << p.name_ << " says: " << p.gender_.Print() << endl;
          return os;

} };

24

int main() {
    Person p1("Ramen Bag", Male::Type());
    Person p2("Elisa Tang", Female::Type());
    cout << p1;
    cout << p2;

return 0; }

-----
Outputs:
Ramen Bag says: I am a man
Elisa Tang says: I am a woman
// Type constants by singleton
// Type constants by singleton

– Behavior Constant [const in code contexts]: Like const pointer, reference, member function etc. • Implementation Constants [static const in library / application]: Like array size
• … Constant:

D.6.4 Testing

  • Every class must be tested for all its functionality in the Unit Test. Hence, it is advisable to keep every class simple – catering to one or two core ideas of the design only. You’ll have more classes – each for every unique concept your design has. This is often better than having few complex classes.
  • Application Test should test for all scenarios of intended use of the software. It is good to prepare an exhaustive test plan for the scenarios first and then code each into the application.
  • We may or may not have separate header files for testing.
  • While all unit tests may be written directly for this assignment, you may consider using test frameworkslike Google Test, Boost.Test etc. It will be good fun. D.6.5 Null-able

• Null-able parameters can be used for parameters (in an interface) to keep provision for future expansion and / or optional behavior. Easiest way to implement it is to use pointer with NULL as default. For example,

         // Using Pointer
         class Passenger {
         // ...
         };
         void DoBooking( // Parameters ...,
             const Passenger* = NULL); // Nullable

Alternately, we could have a specific constant static object of the class (being passed as parameter) interpreted as null and use as default parameter value. For example,

// Using Reference
class Passenger {
// ...
public:
    static const Passenger nullPassenger;
};
const Passenger Passenger::nullPassenger;
// Semantically treated as a null passenger

25

         void DoBooking( // Parameters ...,
             const Passenger& = Passenger::nullPassenger); // Nullable

D.7 Sample: Representing an Undirected Weighted Graph

Here we show an example of how to represent a (fixed) weighed undirected graph. This will help understand various aspects of the design that have been highlighted above. Consider the following project files carefully. Try to build and check out.

D.7.1 Node.h #ifndef __NODE_H

#define __NODE_H
// ***** Node class definition of a graph
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** C++ Standard Library Headers
#include <iostream>
#include <string>
using namespace std;
// Nodes of a weighted undirected graph
class Node {
    const string name_;
public:
    Node(const string& n) : name_(n) {
#if _DEBUG
    cout << "Node " << n << " created" << endl;
#endif // _DEBUG
    }
#if _DEBUG
    ~Node() {
        cout << "Node " << name_ << " destroyed" << endl;
    }
#endif // _DEBUG
    const string& GetName() const throw() { return name_; }
    friend ostream& operator<<(ostream& os, const Node& s) {
        os << s.name_;

return os; }

};
#endif // __NODE_H

D.7.2 Node.cpp

// ***** Node class implementation of a graph
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** Project Headers
#include "Node.h"

26

D.7.3 Graph.h #ifndef __GRAPH_H

#define __GRAPH_H
// ***** Graph class definition
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** C++ Standard Library Headers
#include <iostream>
#include <string>
#include <map>
using namespace std;
// ***** Project Headers
#include "Node.h"
#include "Exception.h"
// Forward declaration
class Node;
// Weighted undirected graph
class Graph {
    Graph();
    ~Graph();
    // List of nodes as name-node pairs
    static map<const string, const Node*> sNodes;
    // List of weights as <node, node>-weight pairs
    static map<pair<const Node*, const Node*>, int> sWeights;
public:
    // Singletone Graph
    static const Graph& TheGraph() {
        // Local static singletone object
        // Gets instantiated on the first call
        // Gets cleaned up with other static objects after main()
        static const Graph theGraph;
        return theGraph;
    }
    // Gets the weight between two nodes
    // Throws Bad_Node if the name of either node does not exist
    // Throws Bad_Edge if the nodes are not connected
    int GetWeight(const string& srcName, const string& dstName) const
        throw (Bad_Node, Bad_Edge) // Exception specification - may not compile in some compilers
    ;
    // Gets the node of a given name
    const Node* GetNode(const string& name) const
        throw (Bad_Node) // Exception specification - may not compile in some compilers
    ;
};
#endif // __GRAPH_H

27

D.7.4 Graph.cpp

// ***** Graph class implementation
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** C++ Standard Library Headers
#include <iostream>
#include <string>
#include <map>
using namespace std;
// ***** Project Headers
#include "Graph.h"
#include "Exception.h"
// ***** static Definitions
map<const string, const Node*> Graph::sNodes;
map<pair<const Node*, const Node*>, int> Graph::sWeights;
// ***** Implementation of Graph
Graph::Graph() {
    // May read the node names and weights from a file
    sNodes["A"] = new Node("A"); // Dynamically created, needs deletion
    sNodes["B"] = new Node("B");
    sNodes["C"] = new Node("C");
    sNodes["D"] = new Node("D");
    sWeights[make_pair(sNodes["A"], sNodes["B"])] = 3;
    sWeights[make_pair(sNodes["A"], sNodes["C"])] = 2;
    sWeights[make_pair(sNodes["B"], sNodes["D"])] = 9;
    sWeights[make_pair(sNodes["C"], sNodes["D"])] = 7;
#if _DEBUG
    cout << "Graph created" << endl << endl;
#endif // _DEBUG
}
Graph::~Graph() {
    // Cleans up the nodes created
    map<const string, const Node*>::iterator it;
    for (it = sNodes.begin(); it != sNodes.end(); ++it)
        delete it->second;
#if _DEBUG
    cout << "Graph destroyed" << endl;
#endif // _DEBUG
}
const Node* Graph::GetNode(const string& name) const
    throw (Bad_Node) // Exception specification - may not compile in some compilers
{
    // Looks for a node with matching name
    map<const string, const Node*>::iterator it;
    for (it = sNodes.begin(); it != sNodes.end(); ++it)
        if (it->first == name)
            return it->second;
    throw Bad_Node("No node as " + name);

28

}

int Graph::GetWeight(const string& srcName, const string& dstName) const
    throw (Bad_Node, Bad_Edge) // Exception specification - may not compile in some compilers
{
    int weight = 0;
    try {
        // Get the nodes
        const Node* psNode = GetNode(srcName);
        const Node* pdNode = GetNode(dstName);
        // Get the weight between the nodes if they are connected
        map<pair<const Node*, const Node*>, int>::iterator weight_it;
        weight_it = sWeights.find(make_pair(psNode, pdNode));
        if (weight_it == sWeights.end()) {
            weight_it = sWeights.find(make_pair(pdNode, psNode));
            if (weight_it == sWeights.end())
                throw Bad_Edge("No edge between " + srcName + " and " + dstName);

}

        weight = weight_it->second;
    }
    catch (Bad_Node&) {
        throw;

}

    return weight;
}

D.7.5 Exception.h #ifndef __EXCEPTION_H

#define __EXCEPTION_H
// ***** Exception class definitions for the project
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** C++ Standard Library Headers
#include <iostream>
#include <string>
#include <exception>
using namespace std;
// My exception class
class Exception : public exception {
    string message_;
public:
    Exception(const string& msg) : message_(msg) { }
    const char* what() {
        return message_.c_str();
    }

};

// Thrown for a missing node for a name
class Bad_Node : public Exception {
public:
    Bad_Node(const string& msg) : Exception(msg) { }
};

29

// Thrown for a missing edge between two nodes
class Bad_Edge : public Exception {
public:
    Bad_Edge(const string& msg) : Exception(msg) { }
};
#endif // __EXCEPTION_H

D.7.6 App.cpp

// ***** Test Application for Graph
// ***** Author: P P Das
// ***** Date: 01-Mar-2021
// ***** Version: 1.0
// ***** Known bugs: None
// ***** C++ Standard Library Headers
#include <iostream>
#include <string>
#include <map>
#include <cassert>
using namespace std;
// ***** Project Headers
#include "Graph.h"
#include "Exception.h"
// Application wrapper of GetWeight to handle exceptions
int GetLinkWeight(const string& n1, const string& n2) throw (Bad_Node, Bad_Edge) {

int w = 0; try {

        w = Graph::TheGraph().GetWeight(n1, n2);
        cout << "Weight between " << n1 << " & " << n2 << " is = " << w << endl;
    }
    catch (Bad_Node& bn) {
        cout << bn.what() << endl;
    }
    catch (Bad_Edge& bl) {
        cout << bl.what() << endl;
    }

return w; }

int main() {
    // Test cases checking with golden output
    // These will assert on mis-matched output on Debug Build
    // All asserts will vanish in a Release Build and there will be no output
    // Both nodes exist with weight given in the called order
    assert(3 == GetLinkWeight("A", "B"));
    // Both nodes exist with weight given in reverse of the called order
    assert(3 == GetLinkWeight("B", "A"));
    // Both nodes exist but are not connected
    assert(0 == GetLinkWeight("B", "C"));
    // First node does not exist
    assert(0 == GetLinkWeight("E", "B"));
// Second node does not exist

30

assert(0 == GetLinkWeight("A", "F"));
    // Both nodes do not exist - first node to be reported
    assert(0 == GetLinkWeight("G", "H"));

cout << endl;

return 0; }

D.7.7 Output in Debug build ( DEBUG is on)

Note that debug messages are given to track creation and destruction of objects:

Node A created
Node B created
Node C created
Node D created
Graph created
Weight between A & B is = 3
Weight between B & A is = 3
No edge between B and C
No node as E
No node as F
No node as G
Node A destroyed
Node B destroyed
Node C destroyed
Node D destroyed
Graph destroyed

D.8 Queries

D.8.1 Suryam Arnav Kalra

Sir , I have some doubts in this assignment.

  1. Section 3.2 : Sir , it is written that the class Station HAS-A name , class BookingClasses HAS-A loadFactor but sir since name is just a string and loadFactor is just a double value, do I have to create separate classes for name and loadFactor or do I simply define them as string and double in their respective classes?
  2. Section 3.3 : Sir , it is written to block the copy functions so do I write them in private with no imple- mentation?
  3. Sir , for the unit tests do I have to create a unit test for each class with all it’s member functions / utilities tested ?
  4. SIr , for the applications testing file what extra shall I add since the file given seems to cover all the possibilities that we want to test for this application ?

D.8.2 Yashica Patodia

I am having a confusion regarding the HAS-A portion of the assignment.

1. From what I understand, private inheritance of classes is a HAS-A relationship but a HAS-A relationship is not necessarily a private inheritance. Please correct me if I am wrong.

So here, my doubt is should ’name’ be a private attribute of class ’Station’ or should I make a class ’name’ and then do private inheritance. I am unable to understand how to implement a HAS-A relationship on properties.

31

D.8.3 Rajas Bhatt

I have the following clarifications related to Assignment 3 of CS20006

  1. In section 3.2 it has been written that ”Class Station HAS-A Name”. In normal circumstances, this denotes aggregation. As covered in the class, aggregation can be done in multiple ways. However, I think that using a c++ string instead of a custom Name class should work here. I don’t know what will be better.
  2. In section 3.2 it has been written that ”Booking should support Passenger as a NULL-able parameter for future extension”. What does this mean?
  3. In many instances, HAS-A relationships have been mentioned. Do these denote simple aggregation or necessarily Private Inheritance as covered in the class?
  4. Can we use .hpp files instead of .h files?
  5. In section 3.3 , bullet 4, it has been said that Class station should have getDistance() to get the distance to any other station, but aren’t we implementing all distance calculation machinery in the Railways class?
  6. While designing unit tests for each class, do we really need to have header files? I don’t think this is necessary at all.
  7. I have decided to keep BookingClasses as an Abstract base class (having two virtual functions), which has two derived classes (Seat and Berth) and they have my actual classes (like ACChairCar) . But the design is such that attributes, like name, ac/non-ac must be kept in the BookingClasses class itself. Now, since the BookingClasses is abstract, while constructing an object for my ACChairCar class, I cannot assign the attributes name , ac/non-ac etc. What should I do?

D.8.4 Nakul Aggarwal

  1. Sir, in the class Railways, why do we have to assign stations and pairwise-distances to static data members? If static data members are used then they will have to be non-const because they are to be changed upon instantiation of the singleton object. But at the same time keeping this constant information specific to the singleton instance of the Railways class in non-const attributes can be problematic. Can’t we simply use non-static–const–private data members (to store stations and pairwise-distances) as was illustrated in one of the lectures?
  2. I have a doubt in one guideline (page 9): Every polymorphic hierarchy must provide a virtual destructor in the base class. The only polymorphic hierarchy that we have in this project is the booking-class hierarchy. In this hierarchy all the base classes are abstract; and abstract base classes cannot have a constructor or a destructor. So does this mean that this guideline is redundant or have I misinterpreted it?

D.8.5 Nisarg Upadhyaya

  1. Regarding statics in the singleton class Railways: What is the reason for making sStations and sDistSta- tions statics in the Railways class. The design requires the class to be a singleton. The data members (private or public) if made static can be accessed using the scope resolution operator. I am not sure about this but intuitively it feels wrong to allow such access for a singleton class. Shouldn’t the access to any method/attribute be using the name of the singleton class, i.e., Railways::IndianRailways.data or Railways::IndianRailways.func()
  2. Regarding BookingClasses hierarchy: I couldn’t comprehend the line ”Rather, we would use single inher- itance on the strongest property and use the rest as HAS-A with polymorphic value based on the leaf class” on page 4. I understand that we are trying to create a single/double level hierarchy of abstract base classes with the first level on the basis of Seat/Berth inheriting from the root BookingClasses followed by the classes for different tiers inheriting from the Berth class. Then we can create any singleton concrete booking class inheriting from a leaf class of the aforementioned hierarchy that matches the Seat/Berth and tier specification required by the concrete class. What is the part ”use the rest as HAS-A with polymorphic value based on the leaf class” trying to convey?
  3. Regarding constants: Why have changeable parameters like load factors, base fare rate, etc. been made static constants? Under 3.1 Design Principles, it has been asked that it should be easy to change these parameters from the application space.

32

  1. Regarding copy functions: Does blocking of copy function mean we explicitly set them as private or provide no implementation and only definition for the same?
  2. Regarding testing: Separate source and header files are required only for application testing right? For unit tests, we can have a static unit test function for each class whose implementation can be provided in the implementation file along with the implementation of the class as done in the unit testing of Fraction class? Also, confirming that no output is required for the unit tests developed.

D.8.6 Ashutosh Kumar Singh

  1. So when you say that we encode the attributes on the methods, it means that there does not have an explicit member variable for AC/Non-AC, luxury, etc., right? We just return an appropriate boolean value from the virtual functions isAC() or isLuxury() as the case is. Is this correct?
  2. Also, sir in the format of the date on page 1, it is given dd/mmm/yy, I feel that it is an error. Could you please tell what the correct format is? I believe if we follow the modules, then it should be dd/mm/yyyy.
  3. According to what I understood, ”golden output” means that it is the theoretical output that we feel is correct. Is this right or is there something more to it?
  4. There are various initializations which are to be done, for example creating the IndianRailways object, then as it is said that ” it should be easy to change the changeable parameters (likebase rate, load factor etc.) easily from the Application space”, so does this mean that these static constants should be initialized in Application.cpp? What I was thinking of is to initialize all the changeable statics in Application.cpp and all the non-changeable statics in their respective .cpp files itself.
  5. I wanted to know where should the function bodies for each unit test function for each class be kept. Should each unit test function be in the respective source file of the class, or should all of them be in a different file like something called UnitTests.cpp?Also, sir how are the unit test functions to be executed? I thought of putting all the unit test functions in a single file like UnitTests.cpp as mentioned, and then write a temporary main function in that class and then call each unit test function one by one. Is this how it is done, or is there some other procedure that is followed?

D.8.7 Aryan Singh

1. I was going through the lectures of singleton class for implementing the Railways assignment. In that I noticed that we explicitly call delete Printer::printer();

So Sir is it a good practice to call the destructor at the end of Main() or we should ignore calling delete .

D.8.8 Yindukuri Jayanth Phani Sai

1. I would like to know whether we are supposed to implement tests manually or we are allowed to use frameworks like Google Test, Boost.Test etc For the Unit Test Implementations part of the Assignment 3. I felt that learning and working with new frameworks in C++ might be fun.

D.8.9 Deep Majumder

1. I really love languages with ”strong” compilers that statically detect a lot of bugs. My first ”real” language was Java and I used to be quite impressed by its strictness. That is until I found out about Rust, which eliminates memory errors (and seg-faults) statically. Also Kotlin – I really find it useful to interoperate with JVM based libraries, while having a stricter compiler.

PPD: I love strong (type-safe) compilers too. But they come with an efficiency cost. None of these languages match (or come even near) the efficiency of C++ which often is a critical issue. So C++ is being elevated in its type-safety through various generations of C++11, 14, 17, 20, 23.

But enough of ranting – I would like to discuss with you what I feel about Singletons (especially since I am using it quite a lot now). As you mentioned in class, Singletons have two uses – one for objects that should exist uniquely and otherwise for creating types with an identity. The first one is of course quite necessary – and while we need hacks like Meyer’s singleton to represent it properly, while in Java we simply make everything static, it is still okay.

PPD: I would disagree. Meyer’s singleton is not a hack. It is one of the most beautiful implementation of a singleton devoid of any runtime support and / or resource management wrappers (like Smart Pointers). Further, I would rather call types with an identity as type constants with behavior.

33

The other use, as you mentioned, is to represent things like days of the week with types and have a compiler guarantee. However, one problem I can see is that since C++ class hierarchies are essentially open, we can easily add a ”eight” day by subclassing it in our application code.

PPD: You are right. Refering to Gender example, we can try to subclass Male and / or Female. It cannot be done because the constructors are private. However, if they are written with less rigour (constructor in protected, it takes a parameter etc.), you would be able to sub-class MoonMale from Male and MoonFemale from Female into the applications space as follows:

#include <iostream>
#include <string>
using namespace std;
class Gender {
    const string name_;
protected:
    Gender(const string& name) : name_(name) {}
public:
    virtual const string Print() const = 0;

};

class Male : public Gender {
    static const string sName;
protected:
    // Constructor moved to protected and provided with a defaulted parameter
    Male(const string& name = "") : Gender(name.empty()? Male::sName: name) {}
public:
    static const Gender& Type() {
        static const Male theObj;
        return theObj;
    }
    const string Print() const { return "I am a man"; }
};
class Female : public Gender {
    static const string sName;
protected:
    // Constructor moved to protected and provided with a defaulted parameter
    Female(const string& name = "") : Gender(name.empty()? Female::sName: name) { }
public:
    static const Gender& Type() {
        static const Female theObj;
        return theObj;
    }
    const string Print() const { return "I am a woman"; }
};
const string Male::sName = "Male";
const string Female::sName = "Female";
class Person {
    const string name_;
    const Gender& gender_;
public:
    Person(const string& name, const Gender& gender) : name_(name), gender_(gender) {}
    friend ostream& operator<<(ostream& os, const Person& p) {
        os << p.name_ << " says: " << p.gender_.Print() << endl;
        return os;

}

34

};

   // In Application
   class MoonMale : public Male {
       MoonMale() : Male(MoonMale::sName) {}
       static const string sName;
   public:
       static const Gender& Type() {
           static const MoonMale theObj;
           return theObj;
       }
       const string Print() const { return "I am a moon man"; }
   };
   // In Application
   class MoonFemale : public Female {
       MoonFemale() : Female(MoonFemale::sName) {}
       static const string sName;
   public:
       static const Gender& Type() {
           static const MoonFemale theObj;
           return theObj;
       }
       const string Print() const { return "I am a moon woman"; }
   };
   // In Application
   const string MoonMale::sName = "MoonMale";
   const string MoonFemale::sName = "MoonFemale";
   int main() {
       Person p1("Ramen Bag", Male::Type());
       Person p2("Elisa Tang", Female::Type());
       Person mp1("$__X9 @oo8", MoonMale::Type());
       Person mp2("!__V7 ?zv6", MoonFemale::Type());
       cout << p1;
       cout << p2;
       cout << mp1;
       cout << mp2;

return 0; }

PPD: This will be easy to fix. We need to use private inheritance – C++’s version of final2 in Java (of course, C++11 has direct support of final with Java-like semantics). Now you cannot sub-class. The question still remains what if we sub-class from the base directly. You cannot stop that. Possible solution is to use a hack (kind of) – provide a typedef wrapper on these type constants in the library space within the base class. So you may be able to define an eight day, but you would not be able to use it interchangeably in the library calls. Bad, but effective. This is the required changes in the Gender for it.

2A class declared as a final class, cannot be subclassed
35

#include <iostream>
#include <string>
using namespace std;
// Forward declarations needed for the typedef’s
class Male;
class Female;
class Gender {
    const string name_;
protected:
    Gender(const string& name) : name_(name) {}
public:
    virtual const string Print() const = 0;
    // Types are hardcoded here
    typedef Male HumanMale;
    typedef Female HumanFemale;

};

class Male : public Gender {
    Male() : Gender(Male::sName) {}
    static const string sName;
public:
    static const Gender& Type() {
        static const Male theObj;
        return theObj;
    }
    const string Print() const { return "I am a man"; }
};
class Female : public Gender {
    Female() : Gender(Female::sName) { }
    static const string sName;
public:
    static const Gender& Type() {
        static const Female theObj;
        return theObj;
    }
    const string Print() const { return "I am a woman"; }
};
const string Male::sName = "Male";
const string Female::sName = "Female";
class Person {
    const string name_;
    const Gender& gender_;
public:
    Person(const string& name, const Gender& gender) : name_(name), gender_(gender) {}
    friend ostream& operator<<(ostream& os, const Person& p) {
        os << p.name_ << " says: " << p.gender_.Print() << endl;
        return os;

} };

int main() {
    // You cannot generate these types in library space (Gender) namespace

36

    Person p1("Ramen Bag", Gender::HumanMale::Type());
    Person p2("Elisa Tang", Gender::HumanFemale::Type());
    cout << p1;
    cout << p2;

return 0; }

PPD: So we have something better, though hacky. That is the price for performance.

It is a silly thing to do and will never get past code review – but it is still a problem that the compiler potentially could have solved but did not. Inspired by the Java enum solution, I tried looking into C++ scoped enums (which came in as late as C++11). On the face of it, it looks okay:

enum class DayOfWeek { SUN, MON, TUE, WED, THU, FRI, SAT };

However, I found out that underneath scoped enums, is really an underlying integer data type (like regular enums). So we can use static cast and to a disastrous effect. Like: DayOfWeek apocalypse = static cast<DayOfWeek>(7). This is thoroughly dis-satisfying and insufficient.

As a comparison, the Haskell solution would be something like:

data Class = ACFirstClass
             | FirstClass
             | ACThreeTier
             | ACTwoTier
             | Sleeper
             | ACChairCar
             | SecondSitting
class BookingClass a where
getName
getNumTiers
isSitting
isAc
getLoadFactor :: a -> Double
:: a -> String
:: a -> Int
:: a -> Bool
:: a -> Bool

The Java solution will be pretty similar, using an enum which implements an interface. Long story short – the C++ solution, as far as I know, is quite clunky and not even bulletproof. So Sir, I would like to know if it is possible to make a better, safer solution to this problem in C++, perhaps using more advanced C++? Or are we stuck with this? Because although C++ doesn’t have the same language beauty of Haskell or Rust, it is still the industrial standard and I really need to know it well. So Sir, could you please help me out with this?

PPD: I wish I had an elegant solution. We can ask Dr. Bjarne Stroustrup or Dr. Scott Meyers or Dr. Herb Sutter or Dr. Andrei Alexandrescu or some other strong expert of C++ / Java and similar languages. I would suggest that you write to them for their recommendations. Meanwhile, let me observe that the examples given by you (somewhat misguided by my DayOfWeek analogy), are about enumerated (in semantic sense) values. And there could be other simpler solutions for it, like in case of Date’s. This example is taken from Item 18: Make interfaces easy to use correctly and hard to use incorrectly in Effective C++, 3rd Ed. by Scott Meyers.

struct Day {
    explicit Day(int d) : val(d) {}
    int val;

};

struct Month {
    explicit Month(int m) : val(m) {}
    int val;

};

37

struct Year {
    explicit Year(int y) : val(y){}
    int val;

};

class Date {
public:
    Date(const Month& m, const Day& d, const Year& y);
...

};

Date d(30, 3, 1995);                    // error! wrong types
Date d(Day(30), Month(3), Year(1995));  // error! wrong types
Date d(Month(3), Day(30), Year(1995));  // okay, types are correct

PPD: Now Month can have 12 valid choices only. Like as follows:

class Month {
public:
    static Month Jan() { return Month(1); }
    static Month Feb() { return Month(2); }
    ...
    static Month Dec() { return Month(12); }
    ...
// functions returning all valid
// Month values; see below for
// why these are functions, not
// objects
// other member functions
     private:
     explicit Month(int m);  // prevent creation of new
                             // Month values
         ...                 // month-specific data

};

     Date d(Month::Mar(), Day(30), Year(1995));

PPD: So the above simple design style can be used to have type-specific constant values. However, our real need in the context are Type Constants with with Behavior. So I chose the solution I provided. But I would never claim that it is indeed the best solution and will look forward to having a better way in C++. Anyone who can educate me on it – I would simply love it.

D.8.10 Rajat Bachhawat

1. In section 5.2 Code Organization, it is mentioned that we have to create an ’Application.cpp’. Is this the same test application that we have to make for carrying out application testing as mentioned in Task 7.4 Testing? Or is this a separate .cpp file that we have to create with a proper text based interface to accept user input and make bookings?

38