Thursday, 18 May 2006
Just because an application is developed in .NET doesn't make it object oriented.

It might have classes.

It might have interfaces.

It might have methods.

It still is a mass of spaghetti code, with some objects thrown in to act as meatballs.

It never ceases to amaze me what lengths developers will go to to turn .NET into a overwrought scripting language, instead of harnessing some simple OOP principles to make their code and their lives simpler.

So, today we are going to talk about Encapsulation. Yes, I have spent the last two weeks ripping through an app that had objects, but frequently didn't use them as much more than fancy structures.  And, yes, I will rant a little bit (more).

Encapsulation is probably the simplest OOP concept to grasp, and it should be the easiest to implement.  A developer does not need to understand class factories, inheritance or polymorphism to develop encapsulated classes.  A desire for clean interfaces coupled with an abhorrence for writing the same code twice will lead naturally to encapsulation.

Yet, this well-known tool of code reuse, is often left unloved and dusty, next to the once-cracked Gang of Four OOP bible.

I like to think of Encapsulation as empowering an object.  It allows the object to say, "This is what I do. This is what I expect.  Don't tell me how to do my job, just give me what I need and let me do it."

Simple, no?  You'd think so, but it is clearly not a universal practice.

Here is an object.  What it does really doesn't matter for this discussion.  It has two ways of being populated -- one when it is first created, another for when it is  reconstituted from the database. It has two consctructors. This is a very common situation. 

public class SomeEntity{

    //Constructor for new instance
     public SomeEntity(string Name, string ANeededValue, int AnotherNeededValue){...}
    
    //Constructor for a retrieved instance
     public SomeEntity(int ID, string Name, XmlDocument TheReasonForTheObject){...}

    //Members
    private string mName;
    //...

    //Properties, Methods, Etc.

    public void DoSomethingImportant(){...}

    public string Name{
       { get{return mName;}
    }
    //...
}

Now, I don't have a problem with the first constructor -- at least there is one, this actually demonstrates an important bit of encapsulation: controlling how the object is instantiated.  The constructor gives a way to tell the world,  "This is what I need to start properly!"

For the free-for-all that can result without encapsulated instantiation, here's the same object sans a defined constructor...

public class SomeEntity{

       //default constructor
       public SomeEntity(){}
  
    //Members
    public string Name;
    public string ANeededValue;
    public int AnotherNeededValue;
   
   
//Properties, Methods, Etc....
    public void DoSomethingImportant(){...}

}

What's required to make sure an instance of this object works properly?  The developer's dilligence, memory and typing skills.  Everywhere the object is created, the developer must remember to type four lines of code -- just to get the object in a state where it is minimally functional.

SomeEntity AnObjectINeed = new SomeEntity();
AnObjectINeed.Name = "Fred";
AnObjectINeed.ANeededValue = "Tuesday";
AnObjectINeed.AnotherNeededValue = 789;

Forget to supply a value -- oops, error. What's missing?  Hope the exception message is clear and go hunting. It is bad enough that this code will be cut-and-pasted willy-nilly every time the object is needed, but what is worse is the maintenance implication.  Things change, the object needs an additonal value to work properly....

public class SomeEntity{

  
    //Members
    public string Name;
    public string ANeededValue;
    public int AnotherNeededValue;
    public DateTime OoopsForgotThis;
   
    //Properties, Methods, Etc....
    public void DoSomethingImportant(){...}

}

Hmmm, what happens now? The object won't work without the new value, but the application still compiles.  The developer must hunt for EVERY creation of the object and add another line of code, and then clean up the inevitable bugs when he misses a few.

Using a defined constructor avoids this problem.

    //Constructor for new instance
     public SomeEntity(string Name, string ANeededValue, int AnotherNeededValue, DateTime OoopsForgotThis){...}

The application won't compile now. All the places the code needs to be changed will be listed, they don't have to be hunted for.  Once the changes have been made and the app compiles, the object will always have what it needs to do its job.

Pardon the long aside, and let me finally return to what bothered me about the second constructor in the example above...

//Constructor for a retrieved instance
  public SomeEntity(int ID, string Name, XmlDocument TheReasonForTheObject){...}

What bothers me here is a subtle, but more troubling, lack of encapsulation related to this constructor. 

In order to call this constructor, three parameters must be supplied, an ID, a name and an XMLDocument. Now, where are these values stored? In a database record.  They are retrieved via a stored procedure call using the entity's ID, which is a unique integer value. 

A UNIQUE value.

The ID is unique and possesses all the object needs to know to reconstitute itself.  Why then, are the other two values needed in the constructor?

They're NOT!

Why are they there? 

I have no friggin' clue.  But this is part of the foo I've been fighting for the last few weeks. There are 5 or 6 lines of database-related code that precede every use of this constructor, along with the creation of an XmlDocument object from its string respresentation.  That's 8 lines of code repeated each time the object is recreated from the database, all of which SHOULD have been placed in the object, so it could have populated itself.

But that's not all?  What is controlling whether or not:
1) The name and XML document supplied are actually related to the supplied ID?
2) The XMLDocument has the proper schema the object expects?

The memory, diligence and typing skills of the developer.  In other words, NO ONE! 

By writing the object so it controls its own data retrieval, these problems are avoided. The object saved (or should have saved -- don't get me started) its own data and can be fairly confident it is getting the same information back -- in proper form. 

//Constructor for a retrieved instance
public SomeEntity(int ID){...}
The object is empowered, it controls its data, it works properly.  It can be used in code without requiring 8 supporting lines of code to be written each time. 

Why? 

It's encapsulated!

Want to make the app work when its disconnected and need to store the object in a local Access database?  No problem, change the storage and retrieval code in the object.  The rest of the app doesn't know and doesn't care how or where the object data is stored.  All it knows and cares about is that if it saved a SomeEntity object with a unique ID = 1776, that when this code is called...

    SomeEntity AnObjectINeed = new SomeEntity(1776);

It will get back a SomeEntity object with an ID = 1776 and the proper XMLDocument.

An object born ready to do what it is supposed to do.

What's hard about that?

Really?

Thursday, 18 May 2006 22:35:28 (Eastern Standard Time, UTC-05:00)   #     Comments [0]  | 
Thursday, 11 May 2006
I have been doing a little exploration of Reporting Services.  It is a strange beast, especially after focusing on web development for the past 6 or so years.  It straddles the desktop and web, and sometimes I need to forcibly pull my head out of its web-first rut.

It has some very nice features when used in conjunction with the ReportViewer control on a web page -- paging and export to Excel and PDF are built-in.  No code to write, nothing to configure. It is an excellent replacement for "dumb" report pages that just show a lot of data, but don't need to do much else.

The hardest mental hoop to jump through so far is that there are no CSS settings.  I don't want to hard code fonts, colors, etc. for each textbox on the report, but I have yet to discover a way around this.  I'm so used to CSS classes, which make it easy to globably change how whole sites work with a few key strokes. To get alternatiing colors in each row I set an expression for the background property like so...

=If(RowNumber(Nothing) mod 2 = 0, "#80C0FF", "White")

Which is simple enough, and I can select all the text boxes in a report and set the property for all of them, but if I have more than 2 reports and want to change the alternating color or font, etc. for all of them -- it will get very dull, very fast.

 I'm thinking the way to do this is to set a code call for each textbox with a parameter.  There is a facility to call functions for most (if not all) the property settings.  So unless I can find a better "native" way, what I'll do is write a method that returns the

static string GetBackgroundColorFromClassName(string ClassName)
{
    string BackgroundColor ="White"; //or some default color
    if(ClassList.ContainsKey(ClassName))
       BackgroundColor = ClassList[ClassName];

    return BackgroundColor;
}

The ClassList in this case would be a NameValue collection. Would build  it from a config file. Or maybe even rip it from the  css file.  Hmmm. Something to investigate.

Then in the N TextBox Background color properties I'd change the code to something like (i'm not sure of the exact syntax for referencing an external mehtod):

=If(RowNumber(Nothing) mod 2 = 0, GetBackgroundColorFromClassName("AltItem"), GetBackgroundColorFromClassName("Item"))

Speaking of properties...

There is some minor foo going on in the report designer.  Select a text box in a report, right-click and select Properties, a tabbed properties dialog box appears.  Hit  the <F4> key instead and the ubiquitous properties window appears.  There is some overlap, but there are also differences in what properties are available from each view and in how they are organized. At least the tabbed dialog can be accessed from the properties window by clicking on the last button on the top the window (the icon does resemble a tabbed dialog box with a bit of squinting).

It is confusing the first couple of times searching for where to set something. I've trained myself to hit <F4> because the properties window displays everything that is available in one view.  Definitely beats hunting through the tabs.

And Now To Hyperlinks...

One of the glorious things about the web -- actually the foundation of its very usefulness -- is the hyperlink.  The ability to dash off to something else of interest with a simple click is so common, I think we take for granted how revolutionary the idea was.  Using hyperlinks in straight html reports is done without thought.  And fortunately, Reporting Services does in fact allow for their existence.  The Action Property of the TextBox is where the ability to add a hyperlink is hidden.  Selecting "Jump To URL" and adding a URL does just what you'd want it to do.  You'll have to add some custom code to use app paths, etc.  But for static URLs it is very straightforward.

There is one trifling annoyance, though the hyperlink is created easily, it is not styled as a hyperlink when the report renders.  I manually did so with the color and text decoration properties.  I want my CSS.  Didn't realize how much I would miss it, stepping away from it ever so slightly.

Firefox's One Character Columns...

While Reporting Services does render reports in Firefox without too much kicking and screaming, it does collapse all the columns to one character width.  Very efficient use of horizontal space, but annoying for everything but sodoku output.  There is a workaround in the forum linked to above -- a hidden textbox with a fixed width across the entire report keeps it from collapsing in Firefox.

Finally, Data Sources and Temp Tables...

I always work with stored procedures, and Reporting Services supports using them as Data Sources.  Visual Studio even has a wizard for creating a table adapter to wrap around a stored procedure. This works very well and makes it easy drag the output columns into the report. However, if the stored procedure is selecting from a temp table, then the table adpater won't work.  The error returned is that #SomeTempTable is undefined.  Which is perfectly true.  It dies when the stored procedure completes, but the output ifrom the procedure is still there!  Smells like bad encapsualtion to me.  It shouldn't matter how the table is created, something is peeking too deep.  The workaround, of course, is to create a real table that matches the output for the purposes of the data adapter and designer.  At run time, the data table created from the stored procedure is added dynamically as the Data Source and the report does not give a hoot how it was created.

Thursday, 11 May 2006 22:30:15 (Eastern Standard Time, UTC-05:00)   #     Comments [5]  | 

Theme design by Dean Fiala

Pick a theme: