3

This is a bit experimentation from my part because I had written a lot of procedural code back in my school days hence I have trouble in thinking in OOP way i.e. to find proper classes and relationship between them. I know I cannot do this every time but I need to create some kind of correlation which may help me understand if I am thinking in right direction or not.

Below is the code of a parking lot allocation problem:

/* 

A parking lot can have many slots which has different size.
A slot can be occupied by at most two vehicles.

Checkin:

 * Find the slot for the vehicle based on size

Checkout:

 * Calculate total fare based on vehicle size and duration

Questions:

  * Design bottom up or top down i.e. Vehicle, Slot or ParkingLot 
*/ 

// Slot details, 3 slots
capacity = [4, 2, 2];

// Vehicle details, 4 vehicles
sizes  = [2, 1, 2, 2];
parked = [1, 0, 1, 1];
start  = ['001', nil, '002', '003'];
end    = ['005', nil, '006', '008'];

// slot vehicle relation
vehicleSlot = [0, nil, 0, 1];

function checkin (vehicle) {
  for slot, size in capacity:
    if capacity[slot] >= size[vehicle]:
      start[vehicle]  = currentTimestamp;
      vehicleSlot[vehicle] = slot;
      capacity[slot] -= size[vehicle];
      return true;
  return 'No slot empty'
}

function checkout (vehicle) {
  if (vehicleSlot[vehicle] == nil):
    return 'Invalid checkout'

  slot = vehicleSlot[vehicle];
  capacity[slot] += size[vehicle];
  end[vehicle] = currentTimestamp;
  vehicleSlot[vehicle] = nil;
  return pricingAlgo(vehicle);
}

function pricingAlgo (vehicle) {
  // size * duration
  var duration = (end[vehicle] - start[vehicle]);
  return sizes[vehicle] * duration;
}

The code may not be completely correct but it shows what I am trying to do at least. I need to know if there is a way to evolve this code to a good OOP program?

  • Are two cars in slot 0 initially? Can more than one car be in a slot? – David Conrad Aug 18 '17 at 21:28
  • @DavidConrad yes – CodeYogi Aug 18 '17 at 21:28
  • "...which may help me understand if I am thinking in right direction or not." First understand your problem domain well. Talk your way through what happens. Come up with problems, like the driver lost his money and can't pay to get out. When that is complete only then think about classes. Primarily think about what they do, and just let properties reveal themselves. I'd say all OO beginners spit out obvious properties, casually clump them into this or that class and then think "what can I do with these properties?" That is bass-ackwards! – radarbob Aug 18 '17 at 23:01
  • Some readers of this forum will love to shoot me for this suggestion: Think of objects as separate programs. Develop your solution based on those programming blocks. You end system will ask the blocks to do what they are good for to achieve a certain task. The more you can have independent blocks (specialized objects) the better. You could identify the functions/subs in your procedural code and begin to group related functions into objects and isolate those with there data in their own object. This is, to me, is the easiest way to bridge to the OO world. Advanced OO comes later. – NoChance Aug 19 '17 at 18:17
  • @NoChance you are making some sense, why not shoot an example? :) – CodeYogi Aug 19 '17 at 18:22
8

The fundamental problem here isn't procedural vs. OOP. It's index based indirection vs data structure.

The code you presented is tapping into the very powerful concept of indirection. But it's naked! Things are what they are because of where they are in some indexed thing. It works, but it makes my head hurt. Mostly because it leaves some things as an idea that you can't pin down enough to even give it a name.

It's so confusing in fact it took me quiet a while to realize you're not enforcing your second business rule at all:

A slot can be occupied by at most two vehicles

Which means if your capacity is [4, 2, 2] I should be able to fit vehicles whose total size is 16. Your code will max out at 8. Either that or it's a bug to express a slots capacity as 3, or any other odd number. Which is just a flat out dangerous thing to do to your maintenance programmer.

Indirection is powerful. Enough that you probably could enforce this rule by adding a freeSlots array, a new condition on your if's, and a new calculation. But I implore you do go a different way. Why? Because of how scattered that rule would be.

This rule is fundamentally a construction rule. You're not allowed to build a parkinglot that that holds an odd number of cars. It's extremely tempting to ignore the entire concept of "slots" and just worry about spaces that take one car each. Then just ensure you always build an even number of them.

But that forces a hidden rule to emerge: Two spaces in the same slot MUST be the same size! It's tempting at this point to give up and say "well fine then the OOP solution is to create a Slot object that has A and B spaces.

I say hold on. This new rule is also a construction rule. You're not allowed to build a parkinglot that holds an odd number of cars and every even numbered space must be the same size as the next odd space. Do that and now the slot concept vanishes after construction.

List<Spaces> spaces = new ParkingLot(4, 2, 2).toSpaces();

Done this way slots might still exist, somewhere, but out here we don't have to know about them. Which is good since, right now, we don't care about them.

If it turns out later it's important to preserve the concept of slots even after construction then we can add it because it's hidden down in ParkingLot and not running around out here with the Vehicles. The complicating factor is even if we add that we need some way to traverse them. The visitor pattern might help but since we don't have to solve that now I wouldn't do it now.

Now if you're an OOP purist you're likely bemoaning the fact that I've exposed spaces. Yes I could push anything that needs to loop the list into ParkingLot. If I did that main would look like this:

Vehicle v1 = new Vehicle(2);
Vehicle v2 = new Vehicle(1);
Vehicle v3 = new Vehicle(2);
Vehicle v4 = new Vehicle(2);

Port out = new OutputPort();
Lot lot = new ParkingLot(out, 4, 2, 2)

JButton checkIn1=new JButton("Check in Vehicle 1");  
checkIn1.addActionListener(new ActionListener(){  
    public void actionPerformed(ActionEvent e){  
        lot.checkIn(v1);  
    }  
});  

JButton checkOut1=new JButton("Check out Vehicle 1");  
checkOut1.addActionListener(new ActionListener(){  
    public void actionPerformed(ActionEvent e){  
        lot.checkOut(v1);
    }  
});  

...

Why all this button nonsense? Because you need some way to introduce time. You could just do it with Thread.sleep(1000) but I was feeling sassy.

I'm treating the Vehicle as a simple value object because I don't see any business rules in it. Therefor it can expose it's size with a getter. But I didn't have to. Starting the show with v1.checkIn(lot) in the JButton code would let Vehicle hide it's size until it felt like telling the lot with lot.checkIn(this, this.size). Either way the lot doesn't need to call back and decides on it's own if the vehicle fits. Once the lot decides what happens it can report the results through the OutputPort. Which might be a log, a GUI control, or your audio speaker for all we care.

There is room here to drop in the code you've already written (with a little massaging). This is what I'd consider a very OOP way of solving this problem. Mostly because every communication is happening in a polymorphic way. The old school OOPers called this "message passing". Lot doesn't know what kind of Vehicle it's getting. Vehicles don't know what they are checking in to. All anything has to know is how to talk to each other.

  • I am yet to read your completed answer but just to make my intentions clear I am fed up with the rule of thumb which are prevailing in the OOP market like each noun should have a class then verb to method but seriously I don't find it fulfilling at all. I have a very logical mind and I want to understand why of the things. – CodeYogi Aug 19 '17 at 6:27
  • @CodeYogi nothing wrong with demanding to know why. If you read this answer I think you'll find it's more full of requirements analysis, decency injection, and polymorphism than rules of thumb. Well, it does have one: always ask what knows about what. – candied_orange Aug 19 '17 at 6:35
  • A thoughtful post indeed. Although bi didn't understand everything in one go but I understood that not every details needs to be exposed. Port is a new concept to me and I really liked that. What about the algorithm? who is responsible to calculate cost? Lot? or some service? – CodeYogi Aug 19 '17 at 7:04
  • @CodeYogi My first inclination is to have the vehicle remember the check in time stamp and the parking lot calculate cost, then tell OutputPort what happened. – candied_orange Aug 19 '17 at 7:19
3

According to Alex Orso in his Software Development Process course, OOP design starts with some grammar;

  1. Look for nouns, they typically correspond to classes, I can see the words vehicle, Slot and ParkingLot in your code
  2. Then look for verbs and associate them with identified nouns, that would make object methods, here you have checkin and checkout are two methods belonging to the vehicle class
  3. Then you look for adjectives and descriptions of the identified classes to define attributes, e.g. size for Slot and also for Vehicle
  4. Look for relations among those classes, we can figure out that a ParkingLot would have one or more Solts of different types, and that a Vehicle would use one of those Slots provided by the ParkingLot

It is also clear that there is a state associated with the Vehicle usage of the Slot that would help us calculate the price. This implies a hidden class Timer used by the Slot.

Now we need to replace global variables and functions with methods and attributes encapsulated inside each class such that only this class would be responsible for using it. Think of it this way, which class among the identified 4 would keep track of its occupancy, is it the Vehicle, the Slot or the ParkingLot? And which one would dispatch incoming Vehicles to check in a Slot? Should the ParkingLot contain Vehicles in addition to Slots and the Timer?

That's how I would think of the problem and its design. Then you re-iterate over your design by introducing scenarios and special cases until you have a valid design.

Finally, whenever you have a working product, you can start looking into Design Patterns to have more efficient and elegant code

-1

DDD perspective

I'd like to start this example from DDD perspective. A good place to start is to look for a consistency boundaries during command (as opposed to query) execution. As a result I come up with an aggregates, main DDD building blocks.

In your first use case, checkin, a command is to reserve a suitable slot. A consistency boundary is that a slot capacity should not be exceeded. Hence an obvious aggregate is a Slot. And it's important that at least in this use case we don't need a Parking lot aggregate, since its state isn't changed. So here is an application service for current use case. It serves as an environment staging place. All repositories, external api client, etc are injected here:

class CheckInApplication
{
    private $slotRepository;
    private $locker;

    public function act(Vehicle $vehicle)
    {
        $this->locker->lock();

        if (is_null($slot = $this->slotRepository->getSuitable($vehicle))) {
            $this->locker->unlock();
            return new NoVacantSpaceResponse();
        }

        $this->slotRepository->save($slot->reserve($vehicle));

        $this->locker->unlock();

        return SlotReservedResponse();
    }
}

And here is how a slot reserving itself could look like:

class Slot
{
    private $id;
    private $capacity;
    private $stays;

    public function __construct(SlotId $id, Capacity $capacity, Stays $stays)
    {
        $this->id = $id;
        $this->capacity = $capacity;
        $this->stays = $stays;
    }

    public function reserve(Vehicle $vehicle)
    {
        if ($this->capacity < $vehicle->capacity()) {
            throw new Exception('Vacant space is not enough');
        }
        $this->capacity = $this->capacity - $vehicle->capacity();
        $this->stays->add(new Stay($this->vehicle, new DateTime('now')));
    }
}

Slot check its integrity rules, that it can accommodate a vehicle, and passes on to Stays object. It is in turn is responsible for holding domain knowledge of Slot being able to accommodate no more than two vehicles:

class Stays
{
    private $stays;

    public function __construct(array $stays)
    {
        $this->stays = $stays;
    }

    public function add(Stay $stay)
    {
        if (sizeof($this->stays) > 1) {
            throw new Exception('Only two cars per slot allowed');
        }

        $this->stays[] = $stay;
    }
}

You can also add a new Stay to Stays, which holds the info about what vehicle occupied the slot and when. It will come in handy a bit later.

I've factored two separate use cases from your checkout scenario. So my second one is charging. After all, no one's gonna let you go until you get charged. Here is an application service:

class ChargeVehicleApplicationService
{
    private $repository;

    public function __construct(SlotRepository $repository)
    {
        $this->repository = $repository;
    }

    public function act(Vehicle $vehicle, SlotId $slotId)
    {
        try {
            return new ChargeResponse($this->repository->getById($slotId)->charge($vehicle));
        } catch (Exception $e) {
            return ErrorResponse($e->getMessage());
        }
    }
}

Slot class is updated with new method:

public function charge(Vehicle $vehicle)
{
    $this->stays->get($vehicle)->charge();
}

I think it's a good idea that Stay class calculates the charge itself. After all, it is a data owner for an algorithm logic. Here is it is (Stay class):

public function charge()
{
    return (DateTime('now') - $this->arrivedAt) * $this->vehicle->capacity();
}

And finally the third use case, releasing the vehicle (in real life I would stick to a single term, but now since I'm writing code on the fly I might be a bit fickle). As always, here is an application service:

class CheckoutVehicleApplicationService
{
    private $repository;

    public function __construct(SlotRepository $repository)
    {
        $this->repository = $repository;
    }

    public function act(Vehicle $vehicle, SlotId $slotId)
    {
        return
            $this->repository
                ->save(
                    $this->repository
                        ->getById($slotId)
                            ->releaseFrom($vehicle)
                )
            ;
    }
}

Here is a Slot's new functionality:

public function releaseFrom(Vehicle $vehicle)
{
    $this->capacity = $this->capacity + $vehicle->capacity();
    $this->stays->releaseFrom($vehicle);
}

And Stay's method just removes the corresponding Stay:

public function releaseFrom(Vehicle $vehicle)
{
    $this->stays =
        array_filter(
            $this->stays,
            function (Stay $stay) use ($vehicle) {
                return !$stay->containsVehicle($vehicle);
            }
        );
}

So although this model and code are not very well thought out, but it shows that with aggregate concept in mind and object-thinking in heart one can come up with quite even responsibilities spreading among domain objects. And this is good, since every object is quite simple.

RDD perspective

This approach implies a bit different accents, though has the same fundamental OO principles. And it can be combined with DDD in any way you'd like. So when employing this technique, I usually start with a design story. What is your app is all about? You're modelling a parking lot. Your software is required to define (by whom?) if there is some vacant room for a vehicle, and if there is, find a proper slot on checkin. On checkout a vehicle should be charged (by whom?) a fee based on vehicle size and duration of stay.

The next step I take is identifying candidate objects and their responsibilities. Let's consider a checkin user case. I have a parking lot -- it holds data about slots. Hence it's a good fit for answering the question about a vacant space and for finding a suitable slot. Would it implement this task for itself? I don't think so. More probably than not parking lot will collaborate with slots, who actually are aware of the fact if they are vacant or not, and if they are, then how much space left. If there is some vacant slot, it can accept a vehicle. So I have two objects with one responsibility each.

Technically, there are usually no repositories, and each object is in charge of its responsibility. For example, saving an object is considered to be that object's responsibility. The same goes for displaying. So the code usually look really odd for most OO programmers. Since there are already plenty of code here, I just leave a link of how this could look like.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.