3

I'm trying to put together some basic models in OOP (C#), and apparently I've got the wrong idea.

If I have a Workman's Comp case, it is applied to a Patient. It also has one or more PatientVisits applied to the case. Each PatientVisit though, also has a Patient associated with it, and of course a Patient can have any number of Visits associated with it.

My original thought was to create basic, real-world reflections of the business models by doing something like:

class Patient {
        int ID;
        string FirstName;
        string LastName;

        List<Visit> Visits;
    }

    class Visit {
        int ID;
        DateTime VisitDate;
        Patient Patient;

        List<DxCode> DxCodes;
    }

    class WCCase {
        int ID;
        Patient Patient;
        DateTime DateEntered;

        List<Visit> Visits;
    }

I naively thought that'll be nice, I'll just load up my model and have everything at hand, very semantic and representative of the domain/real-world, right?

So, I can go the opposite route and fill the parent object and store lists of IDs for related models:

class Patient {
    int ID;
    string FirstName;
    string LastName;

    List<int> VisitIDs;

    public static Patient GetPatient(int id) {}
}

class Visit {
    int ID;
    DateTime VisitDate;
    int PatientID;

    List<DxCode> DxCodes;

    public Static Visit GetVisit(int id) {}
}

class WCCase {
    int ID;
    int PatientID;
    DateTime DateEntered;

    List<int> VisitIDs;

    public static case GetCase(int id) {}
}

This of course allows me to load everything on demand (get an ID and grab the object through it's Get*() method), but seems a bit of a step away from the idea of a nicely representative real-world model and more like a mirror of a database table representation.

I'm guessing there must be some middle ground, but I'd sure like some advice on finding it... Can anyone give some basic examples of how we'd model this without going nuts with references and circulars, etc?

Thanks in advance.

  • For any given Worker's Comp case, is there ever more than one patient? – Robert Harvey Nov 22 '15 at 3:51
  • No, but as a general concept, I suppose there's apt to be the equivalent of a M:M relationship in there somewhere (thinking outside the scope of WC cases). A patient can have any number of visits, a subset of which may be applied to the WC Case. – jleach Nov 22 '15 at 3:52
  • When this happens, 1) you are missing a class, to make the composition work, 2) or your rules for making the classes are wrong. – Andy Nov 22 '15 at 6:37
2

Addressing the Basic Question

if I load a business entity into an object, and it has child objects as well, should I "fill out" all of the child models as well, or leave them null until needed, or...? [reference]

It depends. (thanks Bob?)

Whatever. This is an implementation detail. It should not drive domain design. I am not saying either of the (as of this writing) two answers is wrong. I'm saying this aspect should be better incapsulated and then exposed in domain terms.


Encapsulating Data Fetching

... but seems a bit of a step away from the idea of a nicely representative real-world model and more like a mirror of a database table representation.

I'm guessing there must be some middle ground ...

Domain Analysis

As with any good Gummit bureaucracy you are just a number to them. And so a workers comp thing is who you are, what you do, what they do (to you), etc. This thing sounds like a class and this thing is a number. But it is not an int, it is a "Workers Comp Case File" let's say.

Further, we have all these circular references. Nothing wrong with these per-se from an OO design perspective.


public class WorkersCompCaseFile {
    public int ID { get; protected set; }
    public Patient { get; protected set; }
    public Visits { get; protected set; }
    public WCCase { get; protected set; }

    // here we can load things as desired, and change that
    // implementation isolated from the domain entities involved.

    //Case-wide functionalities
    public bool SuspectFraud() { 
        // lots of object instantiation for complex analysis
    }
}

// CLIENT
WorkersCompCaseFile myHardCase = new WorkersCompCaseFile();
if( myHardCase.HasVisits ) 
    myHardCase.SuspectFraud();  // yeah, everyone is guilty of something.

Data Points

  • We created the proper context to ensure proper global state.
    • Lazy/Eager load as needed
    • Components do not have to construct pieces of, nor continuously examine, the larger context state.
  • With this simple encapsulation - framework - there are so many design possibilities.
  • IDataFetch interface that all classes implement. There could be delegates or event handlers so WorkersCompCaseFile transparently does it for it's components.
  • protected vice public properties
    • the case file totally controls component construction
    • protected int ID - do not expose the ID period. It's an internal unifying concept.
  • Patient, Visit, all have a reference to their containing WorkersCompCaseFile
    • Patient.Visits is a pass-through reference to the case file object.
    • Components might not have an ID property at all.
  • Least Knowledge Principle: The case file exposes components on their behalf. e.g.
    • MyHardCase.Name vice MyHardCase.Patient.Name
    • The client is blissfully ignorant of internal object instantiation and nullness.
    • Internal components can be equally, mutually ignorant.
  • This most definitely should be the answer. The only improvement I might suggest is using one of the OR/M's for .NET, such as Entity Framework or NHibernate. An OR/M adds a level of complexity and should be employed when the domain model gets complicated enough. To the OP: you're there. – Greg Burghardt Nov 22 '15 at 18:36
2

What you are describing is a very common dilemma: On the one hand you want to keep your domain model simple and ignore the technical details of data storage as well as common access patterns. On the other hand this approach is usually not practical because it tends to be very inefficient. It often happens that accessing one entity requires to load the whole object graph.

A simple more extreme example: Let's say a social network would model the friends of a user as a list of user objects.

class User {
  List<User> friends;
}

Loading one user would required to load the whole network! It would not even be easy to set up because you have circular references.

Option A: Use ids

One way to deal with this problem is to use ids everywhere and fetch the users for the ids when you need them. Although this solves the problem of having to load the whole graph, it is not very beautiful from a modeling point of view.

Option B: Use explicit references

Another alternative is to use reference objects that are responsible for returning the referenced objects on demand. Be it from a cache or the database.

interface UserRef {
   User get();
}

class User {
  List<UserRef> friends;
}

If you are not using a persistence framework you will kind of have to write your own. For performance reasons you will usually want to specify which references are resolved immediately when the user is loaded and which will be loaded lazily.

Option C: Stay with the naive approach if its appropriate

Finally, there are some cases where it makes sense to modal a relationship like you tried it. In case of composition , i.e. an object consists of other objects and there are no circular references, and a low and limited cardinality it can make sense. Let's say you model a car that consists, among other things, of a list of seats. Even if you don't always want to access the seats, you could trade efficiency for simplicity here.

  • Fiendship relationship can be easily solved by creating a Friendship class containing two affected Users, and a User containing a collection of Friendships. When a friendship is created or removed, collections in both affected User objects are updated. – Andy Nov 22 '15 at 10:51
  • Of course. But it would not solved the problem I described. If you load a user you load the friendships. The friendships point to other users... You would still have to load the whole network. – lex82 Nov 22 '15 at 11:17
  • Thanks lex, you've understood my issue perfectly. IDs across the board does seem a bit database-ish, and some mixture of B and C is where I expected things would fall. Is there no commonly accepted practice for differentiating between "default graphed objects" and "explicit graphed objects" (annotations for something?) No matter, I'll work it out from here, just wanted some confirmation. – jleach Nov 22 '15 at 12:39
  • Well, there are a lot of frameworks to handle persistence for you and allow you to write your classes in a style similar to what you proposed in your question. Usually you then configure how the objects are mapped to the database via annotations or separte configuration files. This is commonly known as object-relational mapping: en.wikipedia.org/wiki/Object-relational_mapping For C# you could use NHibernate. Is this what you are looking for? – lex82 Nov 22 '15 at 14:01
  • I'm familiar with ORMs (EF, myself), but was looking for some way in the generic model to handle it for consumers without having to know what the persistence layer is doing. I'll post another answer here in a moment with what I've come up with. – jleach Nov 22 '15 at 14:30

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.