The Acme Travel Agency provides various services, including the booking of hotel, plane, and car rental reservations. We will use this simple theme of booking reservations to illustrate various features of .NET throughout the book. In this chapter we design the architecture of a general system for booking different kinds of reservations . We illustrate the reservation system with an implementation of a hotel broker system that supports the following basic features: -
Add a hotel to the list of hotels. -
Show all the hotels. -
Show all the hotels in a particular city. -
Reserve a hotel room for a range of dates. -
Show all the reservations. -
Show all the reservations for a particular customer. The system also maintains a list of customers. Customers may register by giving their name and email address, and they will be assigned a customer ID. The following features are supported in the basic customer management subsystem: In this chapter various lists, such as hotels, reservations, and customers, will be maintained as arrays. In the next chapter we will use .NET collections in place of arrays, and we will implement more features, such as the ability to delete a hotel, cancel a reservation, and the like. In later chapters we will extend the case study in various ways, such as providing a graphical user interface, storing all data in a database, deploying as a Web application, and so on. | The code for our case study is in the CaseStudy folder for this chapter. | Designing the Abstractions Bearing in mind that eventually we want to implement not only a hotel reservation system, but also a system for other kinds of reservations, including plane and car rental, it behooves us at the beginning to look for appropriate abstractions. The more functionality we are able to put in base classes, the less work we will have to do in order to implement a particular kind of reservation system. On the other hand, having more functionality in the base classes can reduce the range of problems to which they are applicable . Good design is a balancing act. Another attribute of well-designed abstractions is that they will survive major changes in implementation. As we shall see later in this book, our VB.NET abstractions of the hotel reservation system remain intact as we implement the system on an SQL Server database. These abstractions will be represented in VB.NET by abstract classes (classes declared with the MustInherit keyword), defined in the file Broker.vb in the CaseStudy folder for this chapter. Reservable Our first abstraction is the thing we are looking to reserve. We will denote this abstraction as simply Reservable . The basic issue in reservations is resource usage. There are a limited number of reservable resources. Hence the key attribute of a Reservable is Capacity . For example, a hotel may have 100 rooms. A flight may have 250 seats. We will also want a unique identifier for a Reservable , which we will denote by m_unitid . (The shorter name m_unitid is used in preference to the longer, more awkward name m_reservableid . Later we will see other use of the terminology "unit." For example, the method to add a reservable is called AddUnit .) For our applications, we are going to introduce an additional attribute, Cost . There is a room rate for a hotel, a ticket cost for a flight, and so on. Note that this attribute may not be applicable to all things that are being reserved. For example, a conference room within a company may not have a cost assigned to it. However, our applications are for commercial customers, so we choose to include Cost in our model. Simplifications Because our case study is designed to illustrate concepts in VB.NET and the .NET Framework, we will choose many simplifications in our design so that we do not become bogged down in detailed design issues. For example, in real life a hotel has several different kinds of rooms, each having a different rate. Similarly, an airplane flight will have different classes of seats. Here the situation in real life is even more complicated, because the price of a seat may vary wildly depending on when the reservation is made, travel restrictions, and so on. To make life simple for us, we are assuming that each instance of a particular reservable will have the same cost. | In VB.NET we will represent a Reservable by an abstract class with the MustInherit keyword. Public MustInherit Class Reservable Private Shared m_nextid As Integer = 0 Protected m_unitid As Integer Protected Friend Capacity As Integer Protected Friend Cost As Decimal Public Sub New(_ ByVal capacity As Integer, _ ByVal cost As Decimal) Me.Capacity = capacity Me.Cost = cost m_unitid = m_nextid m_nextid += 1 End Sub End Class A constructor allows us to specify the capacity and cost when the object is created. The m_unitid is initialized with a shared variable named m_nextid that is automatically incremented. This ID starts out at 0, because it is also going to be used in our implementation as an index into a two-dimensional array to track the number of customers having a reservation at a given reservable on a given date. We will discuss the role of the Private , Protected , Public , and Friend access control specifiers later. Reservation When a customer books a reservation of a reservable, a record of the reservation will be made. The Reservation class holds the information that will be stored. Public MustInherit Class Reservation Public ReservationId As Integer Public UnitId As Integer Public DateTime As DateTime Public NumberDays As Integer Private Shared m_nextReservationId As Integer = 1 Public Sub New() ReservationId = m_nextReservationId m_nextReservationId += 1 End Sub End Class The ReservationId is automatically generated by incrementing a shared member named m_nextReservationId , starting with the value 1. The UnitId member identifies the reservable that was booked. DateTime is the starting date of the reservation, and NumberDays specifies the number of days for which the reservation was made. Broker Our third abstraction, Broker , models a broker of any kind of reservable and is also represented by an abstract class. It maintains a list of reservables, represented by the array m_units , and a list of reservations, represented by the array m_reservations . The two-dimensional array m_numCust keeps track of the number of customers having a reservation at a given reservable on a given day. Public MustInherit Class Broker Private m_maxDay As Integer Private Const m_MAXRESERVATION As Integer = 10 Private Shared m_nextReservation As Integer = 0 Private Shared m_nextUnit As Integer = 0 Private m_numCust(,) As Integer Protected m_reservations() As Reservation Protected m_units() As Reservable Public Sub New(_ ByVal MaxDay As Integer, _ ByVal MaxUnit As Integer) m_maxDay = MaxDay m_numCust = New Integer(MaxDay, MaxUnit) {} m_units = New Reservable(MaxUnit) {} m_reservations = _ New Reservation(m_MAXRESERVATION) {} End Sub ... ReservationResult A simple structure is used for returning the result from making a reservation. Public Structure ReservationResult Public ReservationId As Integer Public ReservationCost As Decimal Public Rate As Decimal Public Comment As String End Structure The Rate is the cost for one day, and ReservationCost is the total cost, which is equal to the number of days multiplied by the cost for one day. The ReservationId is returned as -1 if there was a problem, and an explanation of the problem is provided in the Comment field. This structure is created so that result information can be passed in distributed scenarios, such as Web services, where you cannot throw exceptions. Base Class Logic The base class Broker not only represents the abstraction of a broker of any kind of reservable, it also contains general logic for booking reservations and maintaining a list of reservations. Our ability to capture this logic abstractly gives power to this base class and will make implementing reservations in a derived class relatively simple. Reserve The core method of the Broker class is Reserve . Protected Function Reserve(_ ByRef res As Reservation) As ReservationResult Dim unitid As Integer = res.UnitId Dim dt As DateTime = res.DateTime Dim numDays As Integer = res.NumberDays Dim result As ReservationResult = _ New ReservationResult() ' Check if dates are within supported range Dim day As Integer = dt.DayOfYear - 1 If (day + numDays > m_maxDay) Then result.ReservationId = -1 result.Comment = "Dates out of range" Return result End If ' Check if rooms are available for all dates Dim i As Integer For i = day To day + numDays - 1 If m_numCust(i, unitid) >= _ m_units(unitid).Capacity Then result.ReservationId = -1 result.Comment = "Room not available" Return result End If Next ' Reserve a room for requested dates For i = day To day + numDays - 1 m_numCust(i, unitid) += 1 Next ' Add res to reservation list and return result AddReservation(res) result.ReservationId = res.ReservationId result.ReservationCost = _ m_units(unitid).Cost * numDays result.Rate = m_units(unitid).Cost result.Comment = "OK" Return result End Function The Reserve method is designed to implement booking several different kinds of reservations. Thus the Reservation object, which will be stored in the list of reservations, is created in a more specialized class derived from Broker and is passed as a parameter to Reserve . For example, a HotelBroker will book a HotelReservation , and so on. The UnitId , DateTime , and NumberDays fields are extracted from the Reservation object, and a ReservationResult object is created that is returned. Protected Function Reserve(_ ByRef res As Reservation) As ReservationResult Dim unitid As Integer = res.UnitId Dim dt As DateTime = res.DateTime Dim numDays As Integer = res.NumberDays Dim result As ReservationResult = _ New ReservationResult() ... Next we check that all the dates requested for the reservation are within the supported range (which for simplicity we are taking as a single year). We make use of the DateTime structure from the System namespace. We return an error if a date lies out of range. ' Check if dates are within supported range Dim day As Integer = dt.DayOfYear - 1 If (day + numDays > m_maxDay) Then result.ReservationId = -1 result.Comment = "Dates out of range" Return result End If ... Now we check that space is available for each date, using the m_numCust array that tracks how many customers currently have reservations for each day and comparing against the capacity. The first dimension of this two-dimensional array indexes on days, and the second dimension indexes on the unit ID. (Note that for simplicity we have given our fields and methods names suitable for our initial application, a HotelBroker .) ' Check if rooms are available for all dates Dim i As Integer For i = day To day + numDays - 1 If m_numCust(i, unitid) >= _ m_units(unitid).Capacity Then result.ReservationId = -1 result.Comment = "Room not available" Return result End If Next ... Next, we actually reserve the unit for the requested days, which is implemented by incrementing the customer count in m_numCust for each day. ' Reserve a room for requested dates For i = day To day + numDays - 1 m_numCust(i, unitid) += 1 Next ... Finally, we add the reservation to the list of reservations and return the result. ' Add res to reservation list and return result AddReservation(res) result.ReservationId = res.ReservationId result.ReservationCost = _ m_units(unitid).Cost * numDays result.Rate = m_units(unitid).Cost result.Comment = "OK" Return result End Function Lists of Reservations and Reservables The Broker class also maintains lists of Reservation and Reservable objects. For our simple array implementation, we only implement methods for adding elements. In a later version, we will provide logic to add and remove elements from lists. Private Sub AddReservation(ByRef res As Reservation) m_reservations(m_nextReservation) = res m_nextReservation += 1 End Sub Protected Sub AddUnit(ByRef unit As Reservable) m_units(m_nextUnit) = unit m_nextUnit += 1 End Sub Designing the Encapsulation In our current implementation of Broker all lists are represented by arrays. Since this implementation may not (and in fact will not) be preserved in later versions, we do not want to expose the arrays themselves or the subscripts that are used for manipulating the arrays. We provide public properties NumberUnits and NumberReservations to provide read-only access to the private variables m_nextUnit and m_nextReservation . Public ReadOnly Property NumberUnits() As Integer Get Return m_nextUnit End Get End Property Public ReadOnly Property NumberReservations() _ As Integer Get Return m_nextReservation End Get End Property In our Reservation class the simple fields ReservationId , UnitId , DateTime , and NumberDays are not likely to undergo a change in representation, so we do not encapsulate them. Later, if necessary, we could change some of these to properties without breaking client code. For now, and likely forever, we simply use public fields. Public MustInherit Class Reservation Public ReservationId As Integer Public UnitId As Integer Public DateTime As DateTime Public NumberDays As Integer ... |