2

My application I have two types of customers: individuals and business customers. My users sometimes need to be able to see the full details of one customer, but at other times they only need to see a brief summary with a few fields (e.g. in a list of search results). I currently have three classes: IndividualCustomer, BusinessCustomer, and CustomerSummary. These are plain old Java objects (aka POJOs) with getters and setters as their only methods.

I am contemplating whether to combine them using inheritance or possession. For example, maybe there should be a Customer interface which they all implement. In that case, perhaps the interface requires the methods of CustomerSummary so I can do away with that type. Or perhaps there should be a Customer class with a few fields (like the current implementation of CustomerSummary and it should "have a" BusinessCustomer or IndividualCustomer for the details.

The things that concern me are: if I make a Customer interface and eliminate CustomerSummary, then the types IndividualCustomer/BusinessCustomer will sometimes be only partially populated with details, having lots of null fields, and it would be a disaster if one of those "summaries" ended up hitting a .save() method and overwriting real data with nulls. If I go with the possession approach where a Customer object has a business or individual customer as a private field, then I have to do frequent type checks or type casts in order to be able to use the two types interchangeably.

What's the best design pattern for this kind of case, where an object is sometimes only partially populated (i.e. a "summary"), but can be populated with two different subtypes of details?

2

Objects are not supposed to be bags of data, and your problem comes directly from the object not in control of its data. The object-oriented solution would be to add your specific behavior to your object. One version may look like this:

public interface Customer {
   UIComponent displayFull();

   UIComponent displaySummary();

   ...potentially other stuff...
}

Note: an implementation of Customer may not necessarily hold any data at any particular time, may retrieve data as necessary depending on what functionality is accessed.

This has obvious advantages, one is that you don't have to concern yourself with what "data" is in the object. A second one is that this interface survives data changes. A third one is that it's object-oriented. Fourth, you can optimize each case arbitrarily, to the point of being one sql statement if need be. Fifth, it can be paged or whatever, the interface doesn't care. There are probably more advantages...

One disadvantage is that nobody does it this way. If I convinced you to do it using object-orientation you're on your own. :) You've been warned.

| improve this answer | |
  • "Customer" doesn't seem like a good name for this interface. I would have called it something like "DisplayBehavior" and declared the Customer class as public class Customer implements DisplayBehavior. – Robert Harvey May 28 at 19:56
  • One of the reasons that nobody uses full object-orientation, at least in business programs, is that they don't lend themselves to full object-orientation. Most modern business applications use table-centric data stores using fourth normal form. To get any meaningful behavior from the corresponding objects, it's usually necessary to aggregate them. – Robert Harvey May 28 at 20:02
  • An invoice is an aggregation of Customer, Address and Product objects. You can calculate a total on an invoice, and aggregate invoices and payments in a collection to reconcile an account. About all you can do with a Customer record is look up their name; in a fully-normalized database, even the Contact information is a separate abstraction. It's interesting that you chose a UI operation as an example; UI frameworks are one of the few good use cases for deeply-nested inheritance hierarchies. – Robert Harvey May 28 at 20:03
  • I think "data holders" is a distinct category from "logic capsules", but unfortunately in Java we have to use the same construct, an object, for both. Java has a new construct called "records" coming out on the horizon, meant for simple data holders, but for now I still have to use objects to hold data like this. – workerjoe May 28 at 20:06
  • The enhancements described in JEP 359 seem perfectly reasonable to me. – Robert Harvey May 28 at 20:10
2

First thoughts

Inheritance is meant for generalisation and specialisation. But a CustomerSummary is not some kind of Customer, nor is a Customer some kind of a CustomerSummary. So forget using inheritance here.

Composition would be a better choice: both an IndividualCustomer and BusinessCustomer have a CustomerSummary that can be used for similar purposes. But that's not the end of the story.

OO design vs DB design

If you have a hammer in your hand, every problem looks like a nail.

Similarly, if you have tables and columns in your mind, every class design will look like properties bundled in a POJO. And this might be misleading: redundant columns does not look very normal to an RDBMS expert ;-). And one mental trap leads to another: you'll quickly feel the need to partition theses columns between non-overlapping classes... OUCH! Wait a moment! Didn't we say object-oriented?

Accelerated OO design primer for data oriented developers

To avoid this mental trap, you need to abstract and embrace the law of demeter. The CustomerSummary is no longer a bundle of properties: it's a black box that can contain anything that summarizes a customer. For example a graphical picture such as a QR-Code or a DataMatrix, or a sound to be incorporated in your latest latest voice user interface ("Hey Siri, which customers shall I visit?").

As long as you do not decide anything about its properties, your mind will remain free. So focus on how a CustomerSummary could be used and how it would interact with the other classes. You'll end up with a nicely thought and robust interface.

Then you stay free: you can start with one implementation of the interface (the common fields), you may prefer to specialize the implementation (the legal form really makes sense for a business customer), or you may decide to have different summary implementation based on the kind of interface you're targeting. Dependency injection can even help you to make this dynamic.

At this moment in the thought process, you'd realize that it makes no sense to make the properties of the customer dependent of the content of the black box. This is how you'll get a BusinessCustomer and IndividualCustomer completely decoupled from a CustomerSummary If there is a dependency for populating the object, it would rather be Customer Summary that would be populated from a Customer object and not directly from the DB.

| improve this answer | |
1

Using SwiftUI, my implementation would look like the following:

protocol Customer {
  func showBriefSummary() -> AnyView
  func showFullSummary() -> AnyView
}

final class BusinessCustomer: Customer {
  func showBriefSummary() -> AnyView {
    // return view
  }

  func showFullSummary() -> AnyView {
    // return view
  }
}

let customer: Customer = BusinessCustomer()
customer.showBriefSummary()
| improve this answer | |
0

It is important to distinguish between data structures (which are simply a collection of data fields) and objects (which expose behavior without direct access to fields). They both have their uses and you should pick the right one for the job.

If you're building a simple CRUD application without any significant behavior, then all you need is data structures (three in your case) to transfer data between the database and the UI.

Most applications, though, will have some interesting behavior (such as validation and business rules). In this case, your business layer will be modeled using objects that encapsulate these business rules, and you will use data structures to transfer data to and from the UI. For example, a Customer might be an object (perhaps with two subtypes IndividualCustomer and BusinessCustomer having their own rules) but a CustomerSummary is simply a data structure used to present a subset of customer data on the UI. You could ask any Customer object to give you a CustomerSummary, or if you don't want to load all the customer data, you can directly query the database for the summary.

To close out with your question:

What's the best design pattern for this kind of case, where an object is sometimes only partially populated (i.e. a "summary"), but can be populated with two different subtypes of details?

An object (or the corresponding data in the database) is never partially populated, and the methods in your object should guarantee that. What you can have is a data structure that represents a partial view of an object.

| improve this answer | |

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.