Converting from Business Objects to User Interface Objects
Peter Vogel wraps up coverage of object-oriented programming for the single-tier developer by handling the difference between the data required by users and the object used by the business layer.
To support ASP.NET developers moving from single-tier to multi-tier development, in earlier columns (see Supporting the ObjectDataSource ) I covered the basics of the Factory Method pattern, which cleanly separates database-related code from data-related code. As I pointed out a couple weeks ago (Object-Oriented Programming for the ASP.NET Developer), it's an unusual Web site where the user interface matches the objects that simplify your database updates and business logic. At that time I suggested that one solution was to create more complex business objects that combined data from several tables, which isn't a bad idea.
The problem with that solution is that there is a danger that you'll have to develop those more complex business objects multiple times -- potentially once for every page on your site. In practice, that doesn't turn out to be the case. This is one situation where re-use does kick in and you'll find that a business object built for one page can be recycled on another page, either by adding a few more data properties or by ignoring some properties already on the object.
The real problem with my suggested solution is in implementing business logic. As you multiply the number of objects that represent, for instance, customer data, you can find yourself duplicating code and trying to manage the same business logic across multiple classes. A better solution is to create a set of Data Transfer Objects (DTOs) and implement the Facade pattern.
Rather than create a new data class for each page, you instead create a DTO. As before, typically you'll find that a single DTO will support multiple pages. Code in your page, rather than interact with multiple data classes, interacts with a facade object that hides the complexity of the data classes from the user interface. If you want, you can also think of this class as a Factory Method class that produces DTOs.
In my example in a previous column, I suggested that a page that displayed sales order lines would need to combine information from the SalesOrderLine table and the Products table (the SalesOrderLine table would contain a ProductId while the Products table would have the ProductName). I'm going to use the new .NET 4 syntax for auto-implemented properties to save myself some space here:
Public Class SalesOrderLineDTO
Public Property SalesOrderId As String
Public Property SalesOrderLineNumber As Integer
Public Property ProductId As String
Public Property ProductName As String
In my facade class, I would assemble the data for the DTO by using the objects I'd created to support my data class:
Public Class SalesFacade
Public Function SOLinesWithProdName(SoId As String) _
As List(Of SalesOrderLineDTO)
Dim Sols As List(Of SalesOrderLine)
Dim SolDto As SalesOrderLineDTO
Dim SolDtos As List(Of SalesOrderLineDTO)
Dim prd As Product
Sols = SOLineFactory.GetSalesOrderLines(SoId)
For Each so As SalesOrderLine in Sols
SolDto = New SalesOrderLineDTO()
SolDto.SalesOrderId = SoId
SolDto.SalesOrderLineNumber = SolDto.SalesOrderLineNumber
SolDto.ProductId = SolDto.ProductId
prd = ProductFactory.GetProductById(SolDto.ProductId)
SolDto.ProductName = prd.ProductName
You'll also need to handle updates so your facade will need method to do updates. Again, though, you'll handle that by delegating the work to the data objects that you created. Here's an example of an update method that, on receiving a SalesOrderLineDTO, hands work off to the SalesOrderLine and Product objects. Here, I've assumed that the only fields that the page will allow to be updated are the ProductId and ProductName:
Public Function UpdateSOLinesWithProdName(SolDto As SalesOrderLineDTO)
Dim sol As SalesOrderLine
sol = SalesOrderLineFactory.GetSalesOrderLine(SolDto.SalesOrderId, _
sol.ProductId = SolDto.ProductId
Dim prd As Product
prd = ProductFactory.GetProductById(sol.ProductId)
prd.ProductName = SolDto.ProductName
As a single-tier developer, you might be starting to think, "This is a lot of overhead to incur when I could meet all of my goals with the SqlDataSource." Well, you may not be wrong. As I said in the column that started this series (In Defense of Single-Tier Applications), this is only worth doing if your application meets at least one of the three preconditions that I listed.
Of course, if you are considering a move from single-tier to multi-tier development, you might want to consider skipping writing your own objects and move straight to using the Entity Framework. As I mentioned in an earlier column (Object-Oriented Programming for the ASP.NET Developer) much of the code required to support the ObjectDataSource is repetitive -- the Entity Framework will take care of generating that code for you. With the Entity Framework, single-tier developers might be well positioned to leap straight into the world of Object Relational Mapping!
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.