2

Let's say I have a client that is consuming Vehicle objects sent from a server.

I have two types of vehicles: Car and Truck. Some Cars can be inHybridMode but not all of them. The server currently supplies three specific models in a list: Prius, Camaro, and F150. In the client I have created this hierarchy:

abstract class Vehicle
    String name;

abstract class Car extends Vehicle

abstract class Truck extends Vehicle

interface Hybrid
    boolean isInHybridMode()    

class Prius extends Car, implements Hybrid

class Camaro extends Car

class F150 extends Truck

I've chosen to make Hybrid an interface so that the inheritance structure is not crazy (and super messy if ever got a hybrid truck).

The client can easily create these vehicles from the server output (let's say JSON). I just check the type field and create the appropriate concrete class.

[
   {
      "name":"bob's car",
      "type":"Prius",
      "inHybridMode":true
   },
   {
      "name":"joe's car",
      "type":"Camaro"
   }
]

My question is, how can I handle the situation where the server returns me a vehicle that I don't (yet) have a concrete class for?

{
   "name":"alice's car",
   "type":"Camry",
   "inHybridMode":true
}

My options are:

  1. Completely ignore this type and don't create an object. This will cause problems when I try to do vehicles.size().

  2. Make Vehicle (and Car and Truck) non-abstract and just create a Vehicle object with corresponding name field

  3. Create a new class called UnknownHybrid extends Car, implements Hybrid. This will act as a catch-all for any vehicle that is of unknown type and has inHybridMode field.

  4. Crash horribly, forcing me to implement a Camry class.

I like #3 now while this problem is simple, but I don't want to create catch-alls for every combination of interfaces that I later create.


UPDATE:

First, thanks for all of the great responses.

In an attempt to simplify my problem for question-asking I left some things out that you identified. I'll answer those questions:

  1. I actually have type and model. Type is either Car or Truck, only. Camry, Camaro, F150, etc. is the model.

  2. I only need Car, Truck, and Hybrid behavior. There is no such thing as Camry behavior, only in that it is Car + Hybrid behavior.

    I added the model classes (Camry, F150, etc.) so that I'd have a class which could implement Hybrid interface. I think I'll have fewer models than combinations of interfaces. Eventually I'll have other "mix in" behavior like Convertible, 4WD, etc. So I thought I'd just create classes for each model instead of each combination of interface (HybridConvertibleCar, etc.)

  3. As for business requirements: display info about Car, display info about Truck, display green indicator when in Hybrid mode, handle unknown models that may or may not be hybrid, convertible, 4wd.

    The thing I'm trying to avoid (and the current implementation) is that all of the behavior for hybrid, convertible, 4wd is all contained in the Vehicle class. So I have a lot of models that have fields/methods that don't pertain to them. For example Truck has an isConvertibleTopDown method that always returns "false".

  4. There is more to Hybrid than just isInHybridMode. I just removed most of it for the sake of the question.

  5. If all I had was Vehicle I'd just display vehicle-specific data (e.g. name).

  • 1
    How does each option affect your software's correctness (i.e. its ability to satisfy your software's business requirements)? To evaluate the options, we must know the real-world consequences (pros and cons) of each option. In the absence of real-world consequences, software design would typically focus on the robustness, flexibility and ease of maintenance. However, business requirements almost always comes first. – rwong Sep 10 '16 at 4:30
  • Should this question focus on (1) the presence of extra fields (or lack thereof) in the JSON which causes problem when instantiating objects based on JSON description, or (2) the issue of server communicating a newer version (schema) of the object, one which the client does not yet support? – rwong Sep 10 '16 at 4:33
  • What would your client program be unable to do if you only have a class Vehicle that has as string property "type" and a boolean property "isInHybridMode" (which defaults to false if it isn't in the JSON)? – Bart van Ingen Schenau Sep 10 '16 at 7:42
  • Favour composition over inheritance. You might want to take a look at the component based approach that's popular in game engines. – CodesInChaos Sep 10 '16 at 14:07
9

Don't confuse data with datatype.

If the type field isn't constrained to the datatypes you already have it's not a field for datatype. It's just data.

Further, why do you care that it's a Camry? What different behavior would you be polymorphically dispatching? You shove Camry data into a Car datatype because you want Car behavior with this data. If there is such a thing as Camry behavior you're not going to get it from this json. You'd get it by defining a Camry class.

As for your options, I don't care what the server thinks it's handing me. I care what I'm trying to do with it.

If my job is to display a little indicator light when in hybrid mode I don't give a crap about Camaro, Car, or Joe. I care about if we're inHybridMode. If I call that method only to find it doesn't exist I'm going to be in an exception throwing mood, so it better exist.

If my job is to display the name of the vehicle I don't care if it's a truck. I care that it has a name.

Of options 1, 2, 3, and 4, I can imagine reasons to do all of them. Those reasons center around what you're really trying to do with what the server is handing you. The things you're doing decide what's a good idea here. The data doesn't dictate your use cases. It only dictates what data you can act on.

You're trying to build up your classes from the data. That's just backwards. You build from your needs and grab the data that is useful.

The only needs I've identified here I've guessed at from the classes, which is also backwards. If you want a better answer we need a better question. What is any of this supposed to do?

  • Thanks for the response. I've updated my question based on your questions. – tir38 Sep 12 '16 at 16:01
1

It seems your server side and your client side are not developed in tandem. If they were, throwing an exception would be appropriate. But you are trying to map your model to the model of the server developers that you do not know.

This is programming to a moving target. That is moving in the dark. Good luck with that.

If inHybridMode is the only member of a hybrid vehicle I would not make an interface for it. It is just a property, not behavior, so an extra boolean property on the Vehicle class would be a lot easier and robust. It would be somewhat "flattening" to your model but it would solve your problem, which is you want to be ready for the unexpected. You want a universal vehicle that will fit anything the server throws at you, a one-size-fits-all class that can survive a little tweaking at the server side.

  • Lots of server APIs are developed by vendor X, and clients or other web applications which use that API, or only parts of it, might be developed by vendor Y. That causes the life cycle of both ends not always be in sync, and thus is a iften a good idea to handle unexpected data in a tolerant manner. That has nothing to do with "moving in the dark", it is just a way of creating robust software. – Doc Brown Sep 10 '16 at 7:46
  • I acknowledge that but I read between the lines the OP and the server side development team are not on speaking terms. If they were, they could share the object model or OP would get a headsup about changes which he obviously is not expecting. Please correct me if I am wrong, tir38. – Martin Maat Sep 10 '16 at 8:33
  • 1
    Often times OPs resort to vague, infortunate examples like Vehicle and the like in order not to disclose the real domain of the problem, thus thwarting the uselfulness of the possible answers. – Tulains Córdova Sep 10 '16 at 8:35
  • @MartinMaat: yes, I think also they are not on speaking terms - but that isn't always something one can change, or something really bad. For example, if I am going to use a server API from Microsoft, Oracle, SAP or IBM from any of their standard products, I can only hope that their APIs will deliver only the data mentioned in the documentation, but I would not blindly rely on this, and I also would not expect someone of their dev team explaining me the blind spots in their docs. Instead, I would probably try make make my client software robust enough to deal with unexpected data. – Doc Brown Sep 10 '16 at 9:44
1

Perhaps, you should take a step back from the concrete problem and think about OOAD in general.

In general terms: In your program you are building a model of something. The things you model and how you model these things is completely up to you. It's like inventing a game, where you set up the rules. Whether this game makes "any sense", i.e. whether it serves any practical purpose and how good you achieved this purpose with your software is one of the facets, which makes the quality of our software.

And one (popular) way to build models is OOP. One strength of OOP as such is building abstract groups of objects via inheritance.

If one object inherits from another it has somehow a relationship or similarity which it shares with other members of the group.

This is the so called is-a-relationship, where you form a group of objects. Languages like Java, C# etc. allow two types of inheritance: class inheritance and interface inheritance, i.e. one thing is like another or behaves like another object.

If I have similar objects, e.g. car it would make sense to model them as class inheritance. If I have objects, which share a common ability - able to fly -, it makes sense, to model them as interface inheritance.

What makes OOP so useful is to group heterogenous objects together and treat them the same. For Example: You could tell a collection of Objects, which are able to fly, just to do that .fly() and independendly how they manage to do that, you are able to speak to each of them.


From what was said above you are able to understand, that your question sounds a bit strange:

Inheritence structure to allow unknown types

This somehow contradicts what you are trying to achive with OOP. Of course, if you are dealing with heterogenous objects you can not exactly determine what it does, but you know in what respect this behaviour would be known, since you (hopefully) modeled the groups like that.

My question is, how can I handle the situation where the server returns me a vehicle that I don't (yet) have a concrete class for?

If you would ever end up in such a situation, you know, that your model is just wrong.

If I take your example of Trucks and Cars and being hybrid - independently, whether I think you modeled it correctly:

Whatever your front-end is able to model has to be modeled in the backend as well. Say your frontend models a hybrid Truck, the backend has to translate this to a call to a factory, which knows how to deal with that.

If there is no possibility to "build a car" like it was "ordered", the result is exceptional behaviour.

Answer: Fix your model of the world.

My options are: ...

The fix would be to build a better abstraction. According to your model, it would make sense having an abstract factory, returning you a concrete factory, returning you a concrete object; i.e. a vehicle factory producing a truckfactory or carfactory, which in turn produce concrete (non-)hybrid vehicles of their kind.

To give a more concrete advice, I need more detailed information, about what you want to achieve.

1

Can you do something like this? Just one class, no inheritance at all.

class Vehicle
    String name;
    String type;
    Optional<Boolean> isInHybridMode;

Here, Optional is your language's option type.

1

Class hierarchy & inheritance is a limited modeling system (that, btw, varies among languages) meant for specifically for modeling and crafting code, not necessarily for modeling any and all real world objects.

So, don't attempt to create a class for each & every real-world entity. Also, don't model what you don't need.

Use instances more and classes less.

Use a new class only when you need differentiated behaviors. Otherwise use an already existing class.

Consider adding state to the class to customize an instance, especially if this state would apply to all instances, before considering adding a new subclass. You can't possibly use a subclass for every possible attribute, take just the model year for example, or the location of manufacture.

Refactor so that your class hierarchy is simple and allows you to create instances that meet your (business computing) needs.

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.