OO design

OO design

Old forum URL: forums.lhotka.net/forums/t/232.aspx


jlazanowski posted on Wednesday, May 31, 2006

Hello all,
I am quite new to the OO world, I have been working with .NET for about a year and most of the work I have done with it has been procedural in nature. I got the Expert Business Objects book and am trying to change my thinking to fit OO programming.


I have a rather general question with a mock application I'm trying to develop with CSLA.NET. I have come up with a mock use case based on the company I work for. In trying to layout the object design I've come across a problem and want to make sure I'm going down the right path.

My design has several root objects.
   Vendor, Customer, Driver, Plant, Contact

I also have a class called Address. Each one of the above objects exhibits a has-a relationship to the Address class. Further Vendor, Customer, Plant also have a has-a relationship to the Contact Class.
In the book Rocky talks about normalizing behavior, so my thought is that the way I have this setup is correct, however he also advises against overly complex relationships so this gives me pause. The mock application isn't going to be used for anything, but I would like to understand and make sure I am learning the design process correctly.
 
Anyone out there have a similar problem/solution? Am I heading down the right path? Any advice would be greatly appreciated.

Thanks,

Justin

RockfordLhotka replied on Wednesday, May 31, 2006

On the surface it sounds like you are analyzing your objects based on data relationships - which is something to avoid.

As a specific example, you note that Plant has-a Contact. While that is most likely a valid data relationship, it is only meaningful in your object model if you have a use case where that matters.

In other words, objects come into being through a use case that requires them, not just because there's some data that exists in the database.

So you might have an "Edit Plant Profile" use case. And in that use case the user may be editing the plant information, and as part of that information there's a contact for the plant.

But the use case also needs to define how the user associates a contact with the plant. Do they enter the contact info directly? Or do they choose the contact from a list of existing contacts? Or both? And if they can choose the contact from a list, do they need the ability to add a contact if it isn't in the list? And if THAT is the case, then you have the scenario where one use case links to another - because you can be sure that "Add a Contact" is its own use case, with its own objects.

What I would expect to see out of this are objects like this:

"Edit Plant Profile"
Plant<-BusinessBase (root)
PlantContacts<-BusinessListBase (child)
PlantContact<-BusinessBase (child, probably display-only)

"Add a Contact"
Contact<-BusinessBase (root)

The responsibilty of Contact is to enter/edit valid contact data. The responsibility of PlantContact is to indicate an association between a plant and a contact. Different use cases, different responsibilities and thus different objects/classes.

jlazanowski replied on Wednesday, May 31, 2006

Rocky,

I think you're exactly right, looking back at it I am looking at my data relationships and trying to base objects off of them, I'm trying to get myself to stop doing this (it's a lot harder than it sounds)

Looking at the object model you describe, I would then look at Vendor, Customer the same way even though those contact objects are identical? So I would have a Contact class and then in Vendor for instance I would have

"Edit Vendor Profile"

Vendor<-BusinessBase (root)
VendorContacts<-BusinessListBase (child)
VendorContact<-BusinessBase (child, probably display-only) so on and so forth with all of my objects that have a has-a relationship to the Contact class?

Then there is the question of Address class. I suppose the same would hold true but since the circumstance is a bit different I want to make sure I'm doing this right.

The use case says that all Entities (Vendor, Driver, Plant, Contact etc.) will have an address that is unique to that Entity. Further on most entities (Driver is the exception here) will have at least one contact, the address of a contact may or may not be the same as the entity address.

So I would have another use case "Add a Address"

Address <- Business Base (root) then in the case of a Vendor

I would modify the above by adding VendorAddress class.

Again working to normalize the behavior here since an Address is always the same set of variables Street, City, State etc. or should I look at this like normalization in the database where you can end up overdoing it and add too much complexity? I could for instance just add the Street, City, State properties to the Vendor since there isn't a collection of addresses.

Again I appreciate the input

Justin

 

 

LayeredDev replied on Wednesday, May 31, 2006

Justin,

Here is my 2 cents. If you don't have a collection of Addresses to maintain for a Vendor, then you are better off adding the Street, City, State etc properties to the Vendor object. Same holds true for other objects like Driver, Plant etc.

Cosmic Ovungal

malloc1024 replied on Wednesday, May 31, 2006

If Vendor and Customer are the same class, you only have one class.  Address is a separate class.  Do not place the address data into the Vendor or Customer class.  A class should only encapsulate one set of cohesive behaviors.  If you placed the Address information into the Vendor class, the Vendor class would be responsible for the behavior of a Vendor and an Address.  Splitting them up will make your code more cohesive and flexible.

RockfordLhotka replied on Wednesday, May 31, 2006

For a scenario where the use case dictates a 1:1 relationship - as in a Vendor has ONE Address - you might consider (and I think someone else suggested this too) just including the address properties as part of your main object. Or having Address be a direct child of the root (no collection involved).
 
You can't generalize all object relationships into a single pattern - the design and implementation vary based on the specific use cases and what is required to fulfill the business requirements.
 
You might really have an Address child object that is a child of various types of editable root - that's just fine. The complexity there comes into the data access (potentially). If you have just one Addresses table in your database it isn't so much of an issue - assuming you have a common key type for all Vendor/Customer/etc - which is kind of a requirement for a common Addresses table.
 
But if you have a different Addresses table for each Vendor/Customer/etc. then you need to get the data access out of the Address class because it will be different on a per-type basis.
 
Again, normalize the behavior so one object has only one behavior, and no behavior is duplicated (as much as possible anyway).
 
Rocky

malloc1024 replied on Wednesday, May 31, 2006

IMHO, your database structure shouldn’t really dictate you object structure.  This makes your design tightly-coupled with the database.  Deciding whether you should include the address properties in the Vendor class based on the number of address tables you have, doesn’t seem like a good idea to me.  All addresses will share some behavior and that behavior doesn’t belong in the Vendor class.  Each class should only encapsulate one set of cohesive behaviors.  Adding the address properties to the Vendor class violates this.  Further, this also violates the single-responsibility principle.  The Vendor class would have two reasons to change.

jlazanowski replied on Wednesday, May 31, 2006

To be honest, I can see it both ways.

The Address (in this mock design) is a 1:1 relationship with any other object, a Vendor can have 1 and only 1 address, a Contact can have 1 and only one address. It I wanted to do some specific validation on the address I could see having it in another class, however I can also see it as just a simple property of an object.

All of my objects Vendor, Customer, Plant etc. Have a Name property, I don't try and break this out into a separate class because it's simple property of an object, so I can see it both ways.

 

I would like to say thank you to everyone who responded. I appreciate all the input.

BTW Rocky, Thanks for the book it's an incredible resource even if it does take a little while to get through my thick skull

Thanks again to all,

Justin

RockfordLhotka replied on Wednesday, May 31, 2006

In general I agree, but if the concept of a "vendor" has one and exactly one address then it may be the case that the Vendor object has properties for Addr1, Addr2, City, etc. Arguably this fits within the responsibility to "enter/edit valid vendor data" that such an object might fulfill.
 
Now my guess is that typically other objects will also have an address concept, and this is the case in the original example, and so it probably makes sense to normalize the address behaviors into their own class. HOWEVER, if all you are normalizing are the fields/properties, then you are mostly just normalizing data, not behavior. It is only the RULES pertaining to the fields that are behaviors, not the fields themselves.
 
So I see two possible approaches.
 
The obvious one is to create an Address object (editable child) - and that's typically the right answer I think.
 
The second is to create an AddressRules class that contains the behaviors for address fields, and then any use of an address field/property in any object would link that property to these rules. The behavior remains normalized.
 
This latter approach can be useful in some applications where only a zip code or city name is collected for a Customer, while a complete address is collected for a Vendor. Given recent privacy concerns, this isn't all that rare - and yet you still want the same behaviors for the ZipCode property on Customer as you do for Vendor.Address.ZipCode - so you must normalize that behavior in some manner.
 
Rocky


From: malloc1024 [mailto:cslanet@lhotka.net] 
 
 IMHO, your database structure shouldnbt really dictate you object structure.  This makes your design tightly-coupled with the database.  Deciding whether you should include the address properties in the Vendor class based on the number of address tables you have, doesnbt seem like a good idea to me.  All addresses will share some behavior and that behavior doesnbt belong in the Vendor class.  Each class should only encapsulate one set of cohesive behaviors.  Adding the address properties to the Vendor class violates this.  Further, this also violates the single-responsibility principle.  The Vendor class would have two reasons to change.

MichaelBosch replied on Wednesday, May 31, 2006

I am interested in how this will affect the data access portion of it.  If we encapsulate the rules for this Address object, will the Parent object be in charge of saving the address information, whether that be in an global Address table or not?  If all addresses are saved in the same table, then the update/insert data access would be in the Address object.   If all addresses aren't saved in a specific table, then would the Address object have to do something like :

If Me.Parent.GetType = Vendor
then save here,
else
save there...

I guess what I'm asking is whether the parent is in charge of saving the address information or the address itself because this address looks like a 1/2 business object 1/2 data structure mutant.  I know objects should not be defined by data, but.. uhmm.. how do I save it?

RockfordLhotka replied on Wednesday, May 31, 2006

You wouldn't want the parent to be responsible for saving the Address child data, no. That would imply some very strong coupling between those objects and would reduce the maintainability of your code by quite a lot.
 
As with all object graphs in CSLA .NET, it is the parent's sole job to ask the child to save itself - typically by calling a Friend/internal method like Insert or Update. The most the parent should do is pass a reference to the parent into the Insert/Update method.
 
Inside the Address class your Update() method might look something like this:
 
Friend Sub Update(ByVal parent As IHaveAddress)
 
  Dim sprocName As String
  If TypeOf parent Is Vendor Then
    sprocName = "updateVendorAddress"
  ElseIf TypeOf parent Is Customer Then
    sprocName = "updateCustomerAddress"
  ' ...
  End If
  ' ...
    Using cm As SqlCommand = cn.CreateCommand()
      cm.CommandText = sprocName
      ' ...
      cm.Parameters.AddWithValue("@id", parent.Id)
      ' ...
    End Using
 
End Sub
 
IHaveAddress is a custom interface you might use - it just defines how to get the unique Id value from the any/all parent objects.
 
Rocky
 


From: MichaelBosch [mailto:cslanet@lhotka.net]
Sent: Wednesday, May 31, 2006 5:22 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: OO design

I am interested in how this will affect the data access portion of it.  If we encapsulate the rules for this Address object, will the Parent object be in charge of saving the address information, whether that be in an global Address table or not?  If all addresses are saved in the same table, then the update/insert data access would be in the Address object.   If all addresses aren't saved in a specific table, then would the Address object have to do something like :

If Me.Parent.GetType = Vendor
then save here,
else
save there...

I guess what I'm asking is whether the parent is in charge of saving the address information or the address itself because this address looks like a 1/2 business object 1/2 data structure mutant.  I know objects should not be defined by data, but.. uhmm.. how do I save it?

Troncho replied on Monday, January 06, 2014

Hi Rocky. I've just found this old post and one question comes to my mind. In the case of embedding the [Address] BO as a property of the [Vendor] BO, how do you bind the [Vendor].[Address].[..properties..] in the UI?

Troncho

malloc1024 replied on Wednesday, May 31, 2006

I agree.  If you are only normalizing data, you could place the address properties in the vendor class.  However, addresses usually contain address specific behavior.  This is why I suggested using an address class, to separate behavior.  Using an AddressRule class is a good idea because it encapsulates the address specific behavior.  I have used similar classes in the past and they have worked out well.  As long as the address behavior is encapsulated in its own class, you should have a good design.

hurcane replied on Wednesday, May 31, 2006

I'm no OO expert, but I fall into the camp of having an Address class. Our address class contains two "codes" for state and country. These codes are associated with a NameValueList. The Address class has a method that concatenates the individual fields to a single string. This concatenation uses the country's value from the NVL, and has logic based on the country to determine whether the state uses the key or the description. For USA and Candadian addresses, we use the key. For Mexican addresses, we use the value.

Our address objects are sortable, which is used when generating mailing lists. These are the two main behaviors in our address object. Oh, I almost forgot -- read/write access is usually controlled through the Address object because we have no use cases where the city can be maintained but the state cannot.

To me, this is clearly behavior that I would not want to duplicate across objects. My Invoice object has multiple addresses (Ship-to, Bill-to, Sold-From) that the user has the ability to maintain in a free-form manner.

As Rocky pointed out, data access can be problematic. We don't have a normalized Address table, but we have a naming convention for the columns in the table. For tables with multiple addresses, we use a prefix. Because Address is always a child, the constuctors take a datareader and a prefix as parameters. The data is loaded from the data reader without the business object programmer having to write extra lines.

Saving data is also handled by the parent. Our objects track original values, and we have an Updater object that generates parameterized SQL Update statements based on the fields that have changed. The Parent passes the Updater to the Address and the Address adds its fields to the Updater.

Rocky stated that this kind of design is a very tight cohesion between objects. This tight cohesion can hinder flexibility and maintenance. That is potentially a problem. I would counter that argument with the point that Addresses are unlikely to require major changes. We also don't have any need for the Address object to be used outside of this project. Tight cohesion is a risk, but it is not to be avoided like the plague. For our needs, it made sense.

There are certainly other ways to deal with this behavior. In all cases, you should consider your needs and determine if the behavior is likely to change. Also consider that even the experts don't get the object model perfect on the first try. Martin Fowler is considered an expert in OOD, and he wrote a book (Refactoring) that is focused solely on changing the design of existing applications.

Copyright (c) Marimer LLC