Reputation: 1080
I am trying to learn Design practices and OOPs. I am using parking lot problem as sample to start.
I have a GeneralParkingLot
interface and a Vehicle
interface.
GeneralParkingLot
has only one function returnParkingLotSize
, Vehicle
interface has multiple Vehicle attributes.
I have created a class DowntownParkingLot
which extends GeneralParkingLot
and has other attributes like listOfCars
, availableSlots
etc. and a Car
Class which extends Vehicle
class.
I have a HandlerClass
which handles incoming command and inside that class I have decalared a DownTownParkingLot
object and multiple functions to handle commands, so in each function I just pass the object of DowntownParkingLot
and operate on it.
I have created different services like CreateParkingLotObject
, ParkACar
, FreeASlot
etc. which are called by the command handler.
I have also created unit Tests to test my application.
My problem is if I want to extend my current parking lot to have additional functionality like lets say multiple floor attribute or If I want to now handle multiple parking lots instead of one, so what would be the best way to extend my GeneralParkingLot
class or DowntownParkingLot
class. I have also read about adapter and decorator patterns, but I think those are useful when I am already following a particular design pattern from start, In my case I didnt follow any particular pattern, so what would be the best way to extend my code. I am asking this because sometimes we encounter a class which is not made according to any design pattern and is being used in multiple places (like a lot of API's etc), so what is the best way to extend such a code. Is refactoring from start the only option? or creating new classes which inherit from old classes? What would be the best way? Also I would like to use the already created unit tests as much as possible and not rewrite same test cases again.
Upvotes: 7
Views: 487
Reputation: 43
First let's work on some terminology here, so it won't confuse people later. Here I assume the context is Java since it tagged as Java.
Do not mix 'class' and 'interface'. class defines an object and interface defines behaviour. A class can't extends an interface, but only implements it. You can however use an interface to extends another interface, adding more behaviours.
Do not mix 'attribute' and 'field'. For a class, attributes usually mean private access, whereas fields mean public access via getter/setter.
Second, let's work on OOP concept. OOP is an abstraction of real world. How well your application designs totally depends on how well you know the domain.
Now let's work on your example. The main concept in your case is only one: Parking Lot. However, in different domains, it means different abstractions.
In a payment application, it cares about two things: space & time. How many open spaces left and where are they? For each unit inside the parking-lot, how long has it parked and how much will be changed when it leaves.
In a traffic control application, it has a totally different perspective: how much traffic can flow, and what is the best route to guide the flow.
See, different domains result different designs even on very simple concept. If you find yourself stumbling on the design. Try to forget about class/interface or OOP, and just focus on understanding your domain first, which resolves the issue at most of the time.
Let's start to work on some specifics now. (as I'm not sure about your domain, I'll simple explain the design process based on my understanding. You can absorb the idea and tailor your design).
GeneralParkingLot:
since it's a interface, it defines behaviours. and what behaviours a parking-lot should have? Well, bottom line: it should tell availableParkingSpaces
, it should be able to park
a vehicle and charge
when it leaves. Now we have 3 behaviours defined:
int getAvailableParkingSpaces();
int park(Vehicle v);
int charge(Vehicle v);
DowntownParkingLot
: this should be your object, which has attributes and implements behaviours.
class DowntownParkingLot implements GeneralParkingLot{
private int totalSpaces;
private int[] parking_slots;
private Map<Integer, Vehicle> parkedVehicle; //<park_space_id, vehicle>
//implements of interface methods
}
MultiFloorParkingLot
: I'm not sure how this multi-floor would affect your design, as it's hidden inside your class.
Does your multi-floor-parking-lot provide anything extra in term of behaviours? If it does, it should be an interface extending GeneralParkingLot
as
interface MultiFloorParkingLot extends GeneralParkingLot{
//provide extra behaviours here
}
If not, it should be a class whose parking spaces are stored differently, as
class MultiFloorParkingLot implements GeneralParkingLot{
private int[][] parking_slots; // locate parking_id by floor + park_num
//everything else should not changed much.
}
Personally, I don't think multi-floor any different than single-floor. Multi- or single floor is a concept of building structure, and it useful only when your application needs to locate/guide vehicle to available spaces. Either it's in 'floor 4, column A, #24', or in 'Area 1, column B, #25' makes no difference in concept, the difference lies on implementation, which should be encapsulated inside your class itself. But again, it depends on your domain.
Vehicle
: since it's a concept of parking-lot's vehicle, we do not care its brand, performance, or anything related to Vehicle itself.
interface Vehicle {
String getLicense();
void park(GeneralParkingLot p);
}
Car
: for car, it should at least have a license plate, used as ID.
class Car implements Vehicle{
String licensePlate;
//if your domain needs to track how long it parks
long enterTimeStamp, exitTimeStamp;
@Override
public void park(GeneralParkingLot p){
//do something that car needs to do, then register car on parking-lot
p.park(this);
}
}
See here? The process of parking a car into parking-lot is a process of registering a car to parking-lot. Both of them will probably needs to do something to finish the process. And what they will do respectively SHOULD NOT be done by outside class. The open-close principle: you can tell me what to do, but can't tell me how to do it. This leaves flexibility to change the details of different car in different parking-lot.
HandlerService
: I'm not sure how this handler service will work, but in OO concept, the behaviour should be performed by object itself. Let's code:
class HandlerService {
//let's use park a car as example.
//notice that your using interface instead of class.
//programming on interface, not class
public void parkACar(Vehicle v, GeneralParkingLot p){
v.park(p);
}
}
Anyhow, programming is merely a tool to solve a problem. And OOP is just a way of abstraction. A good, flexible design depends on your domain knowledge more than programming skills. So treat your business analysis well and befriend with them. ^-^
Hopefully it helps.
Upvotes: 0
Reputation: 12022
Is refactoring from start the only option? or creating new classes which inherit from old classes? What would be the best way?
Really depends on what you expect future changes might be. OO design is one part solving the immediate need as cheaply as possible and one part predicting what future changes might be.
For example, lets take the MultiFloorDowntownParkingLot
example provided by @Tobias. If you expect that in the future you will have TollParkingLot
and TollMultiFloorParkingLot
and all sorts of variations, then decorator is preferred because the cost of changing your code to support decorator will be recovered when you go to implement these various parking lots. But if you suspect that your parking lots will be simple variations of the GeneralParkingLot
then just extending GeneralParkingLot
will suffice.
Another thing to consider is composition vs inheritance. You might be able to leverage composition to solve some of the requirements. For example, I could create the toll feature as a new class that wraps (or is composed of) a GeneralParkingLost
object (of which MultiFloorParkingLot
can easily be wrapped by this toll parking lot object).
It is very easy to over think this. You might have to timebox your design phase and go with the cheapest solution that solves the problem and takes care of any expected changes. The book Code Complete covers these topics nicely. In that book it tries to list out what parts of a program might change over time and can then be designed with the fact that these part might change.
Unless you have a clear requirements that suggests you will need decorator I would go with @Tobias answer and composition to fill in any gaps and deal with aggregation (a collection of parking lots should not be a GeneralParkingLot
).
Upvotes: 0
Reputation: 178
Okay, here is my answer after I read the comments on your question:
A Downtown Parking Lot with several floors is clearly also a Downtown Parking Lot. So it is clear, that we will want to have a class that represents a MultiFloorDowntownParkingLot
derived from DowntownParkingLot
if it is not the same class.
As you identified well, you already have the implementation for a SingleFloorDowntownParkingLot
which is also a DowntownParkingLot
.
DowntownParkingLot
can either be single- or multi floor so it should be abstract and implement the GeneralParkingLot
interface and have the same methods as your current DowntownParkingLot
(but their implementation should be in SingleFloorParkingLot
).
A MultiFloorDowntownParkingLot
consists of multiple SingleFloorParkingLot
s and works like a wrapper or multiplexer for every method. listOfCars
for example will only concatenate the lists returned by all its floors.
This model can be extended in many ways:
DowntownParkingLot
can have multiple entrances. The single floor implements this and the multi floor parking lot organizes which entrances of the single floors are outward and which connect the floors among themselves.can have multiple slot sizes. A
SingleFloorDowntownParkingLothas one slot size. The
MultiFloorDowntownParkingLot` has all slot sizes of its floors.It is important to notice that for the user, only DowntownParkingLot
is important. It is never necessary for the user to know wether a specific DowntownParkingLot
has a single or multiple floors (although it could benefit in some cases, but this is works just as well). Only the constructor of the DowntownParkingLot
has to decide which subclass he uses.
Of course, there are many other possibilities, and their usability depends highly on the use case, but this is fairly general, I think.
I hope, I could help!
Upvotes: -1
Reputation: 21124
I think you have to use Visitor
pattern here. The definition of the visitor
pattern as specified by Gang of Four
is,
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Assuming you keep your ParkingLot
hierarchy intact, you can define new visitor for each new operation which has to be added. This yields minimal or no changes to your existing classes and unit tests.
Upvotes: 2
Reputation: 18235
In your case, your top most class is an interface so you cannot add attributes directly.
One way to solve your problem is create a top most implementation class which implement the GeneralParkingLot
interface:
public abstract CommonParkingLot implements GeneralParkingLot {
// add your missing attributes here
protected int floorNo;
}
(If you just want to add attributes without provide some default implementation for GeneralParkingLot
then you can omit the implements
part)
And then have your implementation class extends this CommonParkingLot
:
public class DowntownParkingLot extends CommonParkingLot implement GeneralParkingLot {
// Now all missing attributes are accessible here
}
For missing functionalities, the default
function interface should come handy. (the keyword is language specified), you can add as more function as you need to the GeneralParkingLot
interface with a default body for it.
One more point, your current implementation kinda restricted to a DowntownParkingLot
because you don't add common functionality to the interface. I would suggest you to refactor the HandlerClass
class a bit:
public interface GeneralParkingLot {
int returnParkingLotSize();
ParkingLotObject createParkingLotObject();
boolean parkACar();
int freeASlot();
default public void parkMultipleCar() {
throws new UnsupportedOperationException();
}
}
Now in your HandlerClass
should operate on the GeneralParkingLot
instead of DowntownParkingLot
.
By doing this, you can add more GeneralParkingLot
without changing code.
Upvotes: 4
Reputation: 1629
Inheritance is good for the text books, in practice it is very restrictive. you should use Interfaces and many different implementatons:
interface ParkingLot {
void park(Vehicle v);
}
interface Vehicle {
int size();
boolean isDIsabled();
}
You expose only what is absolutly needed to operate in the highst level. like carpark park a vehicle. Vehicle can only fit into a parking slot size or disabled parking. That is how the carpark know where to park your car.
then each implementation class do what it needs to do to make the process work. I think floors is internal implementation detail that may not need to be exposed on the API but be used internally.
Also recommended to use a factory to build carparks based on floors, lifts, toilets, ... so you have a way to build a generic carpark from configuration and not hardcoded.
you can use a "command" to pass a park command between your carparks until the first one find a siutable parking for your car. In this case you chain all your carparks and pass the request between them. It can also be used internaly between floors, as each floor will try to find a match from ground floor to the roof.
You can "decorate" your car with features that relates to finding a parking spot. so again you have a generic car with different features added on. Same for the carpark design.
when you pass the park command the carpark will read the decorated features and make a dicission if it has a parking or not.
Similar way the security works for users roles (auth) for websites for example.
Upvotes: 1