Design Patterns – Designing a Parking Lot

design-patternsdomain-driven-designdomain-modelobject-orientedobject-oriented-design

Description:

Given a parking lot containing multiple levels and each level containing multiple rows and each row in turn containing multiple spots. The questions is to determine if there are enough spots to check if a vehicle can be parked.

Which object is responsible to determine if there is enough spots, I mean its the parking lot object which is supposed to receive the first query but ultimately it should be either level or row to implement the actual algorithm, right?

Its not about a single class but system as a whole.

Requirement:

  1. The parking lot has multiple levels. Each level has multiple rows of spots.

  2. The parking lot can park motorcycles, cars, and buses.

  3. The parking lot has motorcycle spots, compact spots, and large spots.

  4. A motorcycle can park in any spot.

  5. A car can park in either a single compact spot or a single large spot.

  6. A bus can park in five large spots that are consecutive and within the same row. It cannot park in small spots.

The question is that the given system should be able to given answer to the queries like:

Given a car the system should be able to tell if there is a spot for car or not, if yes the answer should be something like:

There is a spot on level x row y and spot z

The issues I face while solving such problems is where to start and how to find correct objects which should represent the domain correctly.

I did try to start somewhere and below is my code:

Enum Size {
  SMALL,
  COMPACT,
  LARGE
}

private class Spot {
  private boolean empty;
  private int number;
  private Size size;

  Spot(Size size, int number) {
    this.size = size;
  }

  full() {
    this.empty = false;
  }

  empty() {
    this.empty = true;
  }

  status() {
    return this.empty;
  }

  boolean canPark(Vehicle vehicle) {
    return false;
  }
}

class LargeSpot extends Spot {
  boolean canPark(Vehicle vehicle) {
    return
      vehicle.size() == Size.LARGE ||
      vehicle.size() == Size.COMPACT ||
      vehicle.size() == Size.SMALL;
  }
}

class CompactSpot extends Spot {
  boolean canPark(Vehicle vehicle) {
    return
      vehicle.size() == Size.COMPACT ||
      vehicle.size() == Size.SMALL;
  }
}

class SmallSpot extends Spot {
  boolean canPark(Vehicle vehicle) {
    return vehicle.size() == Size.SMALL;
  }
}

private class Row {
  private ArrayList<Spot> spots;

  Row(List<Spot> spots) {
    this.spots = spots;
  }

  void spot(Spot spot) {
    this.spots.add(spot);
  }

  List<Spot> getSpots(Vehicle vehicle) {
    List<Spot> emptySpots = new ArrayList<>();
    if (vehicle.size() == Size.LARGE) {
      // five consecutive large spots
      int length = spots.size();
      for (int i = 0; i < length; i++) {
        int j = 0;
        for (; j < 5; j++) {
          if (!spot.canPark(vehicle)) break; // break out of inner loop
        }
        if (j == 5) { // found five spots
          for (int k = i; k < i + j; k++) {
            emptySpots.add(spot);
          }
          i += j;
        }
      }
    } else {
      for (Spot spot : spots) {
        if (spot.canPark(vehicle)) {
          emptySpots.add(spot);
        }
      }
    }

    return emptySpots;
  }
}

private class Level {
  private final int number;
  private final ArrayList<Row> rows;

  Level(number, rows) {
    this.number = number;
    this.rows = rows;
  }
}

class ParkingLot {
  private List<Level> levels;

  ParkingLot(List<Level> levels) {
    this.levels = levels;
  }

  public String status(Vehicle vehicle) {
    List<Spot> emptySpots = new ArrayList<>();
    for (Level level : levels) {
      for (Row : level.rows()) {
        if (row.canPark(vehicle)) {
          emptySpots.addAll(row.getSpots(vehicle));
        }
      }
    }
    return emptySpots;
  }
}

public class Main {
  private final List<Level> floors = new ArrayList<>();
  floors.add(
    new Level(
      0,
      new Row[4]{new SmallSpotSpot(), new CarSpot(), new CarSpot()}));

  floors.add(
    new Level(
      1,
      new Row[4]{
        new LargeSpot(), new LargeSpot(), new LargeSpot(), new LargeSpot()}));

  private final Lot lot = new ParkingLot(floors);
  private final Car car = new Car(Size.Compact);

  System.out.println(lot.status(car));
}

The code definitely broken at many places but the readers can get the faint idea what I am trying to achieve. Also, it can be seen parking Large vehicle is one of the pain points in designing a good solution.

Best Answer

You're question is about what to model.

On the one hand, there is an obvious structure to the real-world situation: a lot, having levels, having rows, and ultimately having spots. So, one approach would be to model all of those items as entities, many of them as collections, e.g. in a hierarchy of objects.

However, in creating an application, you don't necessarily have to model everything that is in the real world, and, the things you do model don't all have to be first-class entities.

What I'm getting at is that you can model all the spots with a single table having primary key as: lot, level, row, spot, and then with attributes type/size (e.g. compact, handicapped, boat/rv), free/occupied.

A search for an available spot is a query against the table, and an update that marks a spot as free/occupied is a simple transaction.

There are lots of other options: in OOP, you can maintain two simple lists: occupied and non-occupied. The non-occupied list can be broken out into multiple lists by categtories, or maybe even sorted. The manager holding the collection of spots could search and move items between the lists.

In summary, you should study the domain queries and commands beyond the obvious real-world structure of the real-world parking lot. It could be that this extra structure (e.g levels, rows) provides almost no real domain value other than simply identifying where the free spot is. So, there is little reason to organize into multiple tables, and/or, perhaps there is no reason to model a "level" itself as an entity when the level can be captured as a simple (value) attribute.


(There is no way that a parking lot of even tens of thousands of spots would tax a modern computer system, so almost any approach would work. In such case do the simplest as @DocBrown is suggesting. Now, if you were doing hotels, you might reach internet scale, and then eventually you'd have some scaling things to deal with.)


We can never model everything, thus, modeling, by definition, is abstraction, in the sense of hiding irrelevant detail so we can focus on what is truly relevant.

Identify the use cases that are relevant to the domain; from those identify the queries and commands to modify the model — starting with noting well the inputs/givens & outputs/desired results — and from those identify what to model along with the actual queries and commands. With this level of understanding, you will begin to see which modeled entities have what responsibilities and which are merely value attributes. If we model more than necessary, we may loose clarity.


Update for your update: Don't model more than you need. Don't use subclasses when value attributes will do. Don't use object hierarchy when attributes will do.

For example, you're showing modeling the size of the spot using subclassing. What happens when a particular spot is a handicapped spot? Now you need a subclass for each handicapped spot size (e.g. class RVHandicappedSpot). This is class explosion, and a code smell when using classes for modeling things that could otherwise be modeled with attributes.

Further, using attributes you can have numeric ranges rather than only enums, e.g. for the length of a spot. When we model using classes, it constrains us to distinct values more like the equivalent of enums (compared to numbers).


Regarding the bus parking on multiple spots:

Essentially there are two separate problems: identifying the bus parking spots, and allocating them. These can be performed at different times; they do not need to be done together.

I'd suggest you model all potential bus parking spots in advance, as spots that themselves are a collection of other spots. Then, you can determine which of the potential bus parking spots are fully unoccupied, and hand them out as needed. There are a number of considerations here, such as not carelessly hampering bus parking by handing out random car parking spots that belong to the bus parking.

I see the bus parking operations as mostly algorithmic, rather than something solved by a complex data structure. It is similar to register allocation in compiler technology (e.g. graph coloring) when the processor has register pairs, and such.


There are probably three options for checking whether a spot and vehicle match: the Spot can check if a Vehicle fits in it, or a Vehicle can check if a Spot fits it, or a manager (the Lot or even some third party) can check for the match.

Which is best for your domain is a good question to ask.

Some of the criteria for trade offs involve: do either spot or vehicle have specialization (subclasses) to whom question should be delegated. If so they should be consulted. Do either the spot or vehicle have private data that influences matching that cannot be see by the other? If so, they should be given a chance to approve/deny the match.

I might make the bus parking spots a spot subclass that is a collection of spots. In that case, there would be some class hierarchy among kinds of spots, but it is not clear to me whether the generic spot algorithm (based on spot size) is sufficient or a specializing override would be needed for the subclass.

Also, we want a stable interface for the outside consuming clients to use.

So, my first take on that would be that I would have that be the manager's (the Lot's) responsibility so that from an external interface point of view the consuming client code only has to deal with the manager, even as the code evolves internally.

However, the manager may simply choose to delegate to the spot or to the vehicle or both (e.g. both have to say yes, then it is a match).

class ParkingLot {
    boolean Matches(Spot spot, Vehicle vehicle) {
        return spot.Matches(vehicle) && vehicle.Matches(spot);
    }
}
Related Topic