views:

269

answers:

4
+1  Q: 

Refactor to n-tier

I am a self taught vb6 programmer who uses DAO. Below is an example of a typical piece of code that I could churn out:

Sub cmdMultiplier_Click()  'Button on form, user interface ' 
  dim Rec1 as recordset
  dim strSQL as string

  strSQL = "select * from tblCustomers where ID = " & CurrentCustomerID  'inline SQL '
  set rec1 = GlobalDataBase.openrecordset(strSQL)    ' Data access '

  if rec1.bof <> true or rec1.eof <> true then
    if rec1.fields("Category").value = 1 then
      PriceMultiplier = 0.9         ' Business Logic ' 
    else
      priceMultiplier = 1
    end if
 end if
End Sub

Please pretend that the above is the entire source code of a CRUD application. I know this design is bad, everything is mixed up together. Ideally it should have three distinct layers, user interface, business logic and data access. I sort-of get why this is desirable but I don't know how it's done and I suspect that's why I don't fully get why such a separation is good. I think I'd be a lot further down the road if someone could refactor the above ridiculously trivial example into 3 tiers.

+1  A: 

What is the purpose of the button?

My first steps would be:

  • extract the part accessing the database. (warning: air code ahead)

function getCustomer(CurrentCustomerID as Long)

strSQL = "select * from tblCustomers where ID = " & CurrentCustomerID set rec1 = GlobalDataBase.openrecordset(strSQL) result = 1

if rec1.recordcount >0 then getCustomer = rec1 else getCustomer = false endif end function

  • compose the business logic function:

function getCustomerDiscount(customerID as Long)

customer = getCustomer(customerID)

res = 1 if customer then if customer("category")=1) then res = .9 endif endif

getcustomerdiscount = res

end function

  • then, change the button:

Sub cmdMultiplier_Click() pricemultiplier = getcustomerdiscount(currentcustomerid) end sub

I forgot to stick in a purpose for the button. Maybe to display the multiplier on a label. Thanks for your answer, Stephen A. Lowe's answer fleshed things out a bit more which is more understandable to me.
kjack
+1  A: 

Typically you will have your UI code responding to the events raised by the user, in this case the Button Click.

After that it really depends on how your program is designed, the most basic design would be to reference a Customer instance and it would contain a multiplier property. Your customer object is populated from data in your DAL.

Validation for UI would go in UI layer, business validation rules could go into your business object, and then your DAL is your persistence layer.

Here is a very basic pseudo-code example:

btnClick
    Dim Cust as New Customer(ID)
    multplr = Cust.DiscountMultiplier
End Click

Class Customer
    Sub New(ID)
     Data = DAL.GetCustomerData(ID)
     Me.Name = Data("Name")
     Me.Address = Data("Address")
     Me.DiscountMultiplier = Data("DiscountMultiplier")
    End Sub
    Property ID
    Property Name
    Property Address
    Property DiscountMultiplier
     Return _discountMultiplier
    End
End Class


Class DAL
    Function GetCustomerData(ID)
     SQL = "Paramaterized SQL"
     Return Data
    End Function
End Class
Brian Schmitt
Thanks, I accepted Stephen A Lowe's answer because I understood it a bit better (although objectively I'm not in a position to judge whose answer is better).
kjack
+2  A: 

a trivial example, yes, but with all the basic elements - they just belong in 3 different classes (see below). The main reason for this is the "separation of concerns" principle, i.e. the GUI is only concerned with GUI things, the Biz Logic layer is only concerned with the business rules, and the data-access layer is only concerned with data representations. This allows each layer to be maintained independently and reused across applications:

'in Form class - button handler
Sub cmdMultiplier_Click()
    PriceMultiplier = ComputePriceMultiplier(CurrentCustomerId)
End Sub

'in Biz Logic class
Function ComputePriceMultiplier(custId as Integer) as Double
    Dim cust as Customer = GetCustomer(custId)
    if cust.Category = 1 then   'please ignore magic number, real code uses enums
        return 0.9
    end if
    return 1
End Function

'in Data Access Layer class
Function GetCustomer(custId as Integer) as Customer
    Dim cust as Customer = New Customer    'all fields/properties to default values
 Dim strSQL as String = "select * from tblCustomers where ID = " & custId
    set rec1 = GlobalDataBase.openrecordset(strSQL)    ' Data access '
    if rec1.bof <> true or rec1.eof <> true then
        cust.SetPropertiesFromRecord(rec1)
 end if
 return cust
End Function

[a 'real' application would cache the current customer, have constants or stored procedures for the customer query, etc.; ignored for brevity]

Contrast this with your original everything-in-the-button-handler example (which is appallingly common in VB code because it is so easy to do it that way) - if you needed the price-multiplier rule in another application, you'd have to copy, paste, and edit the code into that application's button-handler. Now there would be two places to maintain the same business rule, and two places where the same customer query was executed.

Steven A. Lowe
Great answer! Preserving the original code by redistributing it really helped with comprehension.
kjack
Might it be in that in the data access layer that there would be a single function to actually get the data?So that just a single line like this in the entire application:set rec1 = GlobalDataBase.openrecordset(strSQL)
kjack
@kjack There is no easy answer to your last question. Some people with say yes, some will say no. It depends on what the rest of your architecture looks like.
Darrel Miller
That's OK. If some say yes and some say no then at least it's an arguable option. It surely would simplify ultimately switching from DAO to ADO down the line I think
kjack
+1  A: 

Knowing how to refactor is a good thing. From now you will know how to separate layers.
However, I think your time will be better spend to upgrade the tools you are using at the same time. Do you have consider to do it with VB.Net ?

A way to do it will preserving your existing code base is to code the Data layer and BR in VB.Net. Then to expose the BR through COM Interface (this is a check box option in the project). You can then use the new BR from your current interface.

Once all BR and DAL done, you will be a step away to a complete new platform.

Hapkido
That's a good point. I am thinking about porting everything to .net in the future, but I figure vb6 still has a few years left. I think it might be easier to both maintain, enhance, and refactor the existing application and learn OOP etc in an environment in which I am already comfortable.
kjack