Why are Switchable objects bad?

Why are Switchable objects bad?

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


brettswift posted on Thursday, June 15, 2006

I have a model that shouldn't be uncommon, in fact its probably everywhere.

Animal ---> AnimalTask ---> CompletedTasks

Animals have tasks associated with them (ie shots, see the ferrier, check teeth etc).
An animal task is one that is associated with that animal, and there is a taskID in there that comes from the Main list of tasks that you can select from when adding a task to a particular animal.

Then the completed tasks is a log table storing when each task was completed so you can look at a history of the treatment for the animal.

Animal a = Animal.GetAnimal(id);    returns my animal, and a list of tasks associated with it.

a.Task.CompletedTasks  is empty however, so this is where I need to do one of the following:

a.Task.LoadCompletedTasks(); (instance method)
or
AnimalTask = AnimalTask.GetCompletedTasks(a.Id,a.Task[index].TaskId);  (static method)


I don't have much experience developing OO models or using CSLA - anyone have any suggestions?

skagen00 replied on Thursday, June 15, 2006

So aren't you just saying that an animal may have many tasks, some of which may be completed?

So in some respects, doesn't your animal just have a collection of tasks. And each task may have a boolean flag for whether or not it is completed along with perhaps a completed date?

You shouldn't need some separate log table to move current tasks to "completed tasks", unless I'm vastly missing something.

brettswift replied on Thursday, June 15, 2006

Yes, you are missing something, I didn't explain it fully.

Tasks like... shots, or getting the ferrier to re-shoe the horse, are tasks that recurr.   There is also a field called "IntervalDays" which when the task is completed, a read-only property will check the interval against the last completed date to see if the task is overdue to bring attention to the user.  Thats why I don't have a boolean completed field, because as soon as it's completed, its re-scheduled.


guyroch replied on Thursday, June 15, 2006

Switchable objects are in most cases bad because they usually indicate bad design.  From a best practice perspetive, an object should resolve one use case only.  So having switchable objects theoretically indicates 2 or more use cases for one object.

While this is the rule, there are certainly exceptions to it.  That being said, the need for a switchable object might become a life saver once in a while.

At the end of the day, a switchable object is simply an object that can be instatiated as a root object in one case and as a child object in another case.



brettswift replied on Thursday, June 15, 2006

Thanks guyroch.

What you said is pretty much what the book says as well.  Considering that comment, would I be best using the instance getCompletedTasks method stated in my original post?

What is the most common way that people will drill down to childeren of childeren of childeren etc ? 

This is the main answer that I am looking for.


guyroch replied on Thursday, June 15, 2006

Not sure you got the chance to read my last post, but this is not a case of Parent -> Child -> Grand Child object.  I think this is simply a case of a parent that hase 2 child collections.

brettswift replied on Thursday, June 15, 2006

guyroch I didn't get your 2nd post.  But have a look at my 2nd post way up at the top.  I appreciate your advise on my Object Model as well.  I might be able to clarify why I'm handling things a bit differently.

My design is a bit obscure and maybe not as intuitive because of business rules that I've created. (this is aproject that I'm designing myself - for a friend)

Animals have Tasks such as a need to go to the vet. Think of it as a recurring schedule, rather than a Task I guess.

So,  Horse1, needs to go to the vet every 365 days for a checkup.   There is no "completed" boolean field, hence why it doesn't make sense to delete it from one collection and add it to the other.  I'll try to explain more thoroughly.

Saying a horse needs a checkup every 365, its not a matter of them having to see the vet every June, but rather, 1 year from the previous visit, whenever that was.   So, in my model I have a boolean field called "IsOverDue" instead of "Completed"   This overdue field is a calculated field which looks like this:

_______________________________
        public bool IsOverdue
        {
            get {

                DateTime due = SmartDate.StringToDate(DueDate);
                if((DateTime.Compare(due,DateTime.Today) < 0))
                    return true;
                else
                    return false;
            }
        }

        public string DueDate
        {
            get
            {
                return _lastCompleted.Add(new TimeSpan(_intervalDays, 0, 0, 0)).ToShortDateString();
            }
        }
______________________________


_lastCompleted is a readonly property, that will be populated from the CompletedTasks table.  It will simply be a select top 1, ordered by descending date, for that particular AnimalTask.   Hence, checking to see if the horse is due for a checkup.  

(I think you may be thinking of this as a TODO list, with a history - not the case.. well.. it kind of is, but you don't input Tasks directly to one animal - as the tasks will be common.  There is a Tasks table, where you can input tasks that all animals (or most) will have access to.   The AnimalTask table is a lookup table joining the pk's from the Animal and Task tables. )


Does this help in why I think this is a parent-->child---> grandchild scenario ?


guyroch replied on Thursday, June 15, 2006

We keep posting at the same time :) which make it hard to follow.

I still think that this is not a case of a Parent -> Child -> GrandChild scenario.  You might not have a completed boolean flag but I'm asuming that you do not schedule the a second occurence of the same task before the previous one is done right?

By that I mean if you have a task to bring the horse to the vet every 365 days, that if the horse is late by lets say 30 days, the next task will be 365 days from the actual date he went to the vet and not from the origianl due date.  If that is the case then you do need some mechanism to let the user say yep, this task is done, late but done.

If this is the case then all you would need to do is to transfer the ScehduledTask into the CompletedTask collection (table) and create a new task in the ScehduledTask with a due date 365 days from today, or whatever calculation you may have.

Does this make sense?

brettswift replied on Thursday, June 15, 2006

before I post on this thread I now check the "who's online" and see if you're creating a post or not haha.

I see what you're getting at - I think. 

You are correct in that the 2nd visit to the vet would be 395 days from the original.  However one thing I just noticed is I don't have a set due date, just the last time you completed it, plus the interval.   (I chose this way so it would always be rescheduled without the need to run a "re-schedule completed" method).

The method that the user would input that the task was done, but late, would be:

1)Select the AnimalTask from the list of configured tasks for that animal (horse).
 -this will show a form of the task details, and a gridview of the history of the task -and in the event of an overdue task, a warning saying the task is overdue.
2) when the user takes the horse to the vet, they come back, and see a button called "This task has been completed"  or something along those lines, and they would click it.
 - this would then take the task info, and create a new CompletedTask row in the Completed Task table.

Step 2 is where the user dictates its completed but late.   Because remember in my AnimalTask object, I figure out if it is overdue by adding the interval to the latest completed date.   The new date would be 6/14/2006, instead of the previous one in say 5/1/2005.   

5/1/2005 + 365 days = overdue
6/14/2006 + 365 days = 2007 - so the task is still visible as "upcoming"

Later, in my search criteria I plan to filter these by date, so only show those coming up in the next 2 months for example.

I hope you don't think I'm defending my model too much here, I just think my design and flow of it is a little mis-understood...  maybe not, maybe I'm missing something you're saying?


guyroch replied on Thursday, June 15, 2006

What I think about your design doesn't realy matter, what matters is that you believe in it :)

It more clear now.  Still maintain that this is a clasic case of a Parent object with 2 direct child collections.

brettswift replied on Thursday, June 15, 2006

Ok, I guess what I'm missing then is how you would represent that collection ?

I see that you could return that collection to the parent, although it would be an assortment of tasks.

IE - my horse needs shots monthly, trips to the vet, and needs to see the ferrier every 6 months.

my completed tasks list would bring up a conglomerate of all those tasks.   So what you're saying is thats ok, but when I click on one AnimalTask - or as you're calling them a ScheduledTask - then I would input search criteria to only return completed tasks for that taskID, on that one Animal.  Correct?

I can see that being another way of doing it, forcing a bit of  filtering instead of just getting everything for one grandchild, but it would give me the ability to view ALL completed tasks for one animal all on one page, and see in which order they were completed in.   I think your point made it through.. I will consider it.

Do you have a quick answer on how you would normally access the Grandchild if I were to use this scenario (for future reference as well).

child.getChilderen  instance method, or static method?   

Would a static method imply that the object would be switchable.. is that not the case?


SonOfPirate replied on Friday, June 16, 2006

If you guys don't mind, I'll throw my two cents in at this point.  I think the biggest gap between you may be semantical.

First, the name "Scheduled Tasks" I beleive is causing confusion.  Perhaps this list could be renamed "Assigned Tasks"???  If I understand the design correctly, that is essentially what you are dealing with - a list of tasks assigned to that animal from a master list of tasks.

Each of these Task items maintains its status information which includes, as explained, information about the next time the task is due, its overdue state, etc.  These values are based on calculations using the most recent occurance of that task being completed.

While I agree that the Parent with 2 Child Collections approach is viable and legitimate, let me offer that the model can also be looked at from another perspective.  The completion information for a task is a property/attribute of that TASK, not the animal.  As such, it would make more sense (to me at least) if the collection of completed tasks is a child of the task itself.  This would also make the performing the lookups and calculations necessary to maintain the task's status easier and more logical.

As a result, I would have:

So, to access the date of the most recent occurance of a task, you could use the following code:

= myAnimal.AssignedTasks[i1].History[i2].CompletedDate;

Note that the History property is an arbitrary name I elected because the CompleteTaskCollection does serve as a history for that task.

The object model would be more like:

Animal
    AssignedTasks (AssignedTaskCollection)
        AssignedTask
            History (CompletedTaskCollection)
                CompletedTask

From the AssignedTask, you would use the following code to determine the task's status:

public System.Boolean IsOverDue
{
    get
    {
        return (System.DateTime.Today.Compare(_history[_history.Count - 1].CompletedDate.Add(_interval)) > 0);
    }
}

Providing a complete history for the Animal at the Animal level can be done by creating a read-only AnimalCompletedTasks collection of AnimalCompletedTaskInfo objects that essentially loads all the same records as the combined AssignedTasks->CompletedTasks collections hold.  This way, the object appears to contain 2 collections, per the Parent-2 Child Collection model, but this second collection is for viewing/reference purposes only and the model described above actually manages the data.

Hope that helps!

 

brettswift replied on Friday, June 16, 2006

Aye mate.  Thank you.

You are on the same page as me, however to explore guyroch's idea I've built it the way he suggested.

I may switch, we'll see.   For now the LastCompleted property will come from the DB on a join in the select statement when the object is loaded.

I could probably ask this in another thread but seeing as both of you have mentioned 2 collections in the parent, maybe you could help me out.  It could be that I just don't know the datareader well enough, but I think there's more to it. 

____________________________________

using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))
                    {
                        dr.Read();
                        _id = dr.GetGuid("Id");
                        _name = dr.GetString("Name");
                        _birthday = dr.GetSmartDate("Birthday", _birthday.EmptyIsMin);
                        _photoPath = dr.GetString("PhotoPath");
                        dr.GetBytes("LastChanged", 0, _timestamp, 0, 8);

------>               //// load child objects
                        dr.NextResult();
                         _tasks = AnimalTasks.GetAnimalTasks(dr);

------>             /// load completed tasks   
                        dr.NextResult();
                         _completedTasks = CompletedTasks.GetCompletedTasks(dr);
                    }
___________________________________

If you look at the code above, you will see I am retrieving 2 collections.  This should work if both resultsets exist right?

If one of those collections is empty (ie - no resultset returned from the DB)  how can you skip that ? I'm not sure what the code would be..  I've tried if(dr.read()) but that fails too...

What am I doing wrong here?



hurcane replied on Friday, June 16, 2006

I can answer the question about no records in a result because I just finished working on a read-only object which has a half-dozen collections that may or may not have individual records.

dr.NextResult will always work. You still get a result set, even if the results have no records. The result set still contains the column information.

In your child object, you have a "While dr.Read". This will return False when there are no records.

brettswift replied on Friday, June 16, 2006

ah thanks.

I just had a typo in my stored procedure.

Anyways, thanks for all the input.


guyroch replied on Monday, June 19, 2006

Brett,

I think your CompletedTasks child collection should be a ReadOnlyList and not an editable child collection.  You could achieve this by following the lazy loading example I've provide last week.

The reason I'm saying this is because I'm pretty sure that the user will not be allowed to modify the content of this collection, right?  Only _your_ code will add stuff to this collection.

guyroch replied on Thursday, June 15, 2006

In you case, the completed tasks would be a ReadOnlyCollection because I'm assuming that you can't (or should not)  be allowed to modify a completed task.

With that said you could laze load your ScheduleTasks read only collection.

public class Animal()
{
    private TaskList  _scheduledTask = null;  // this is a ReadOnlyCollection, not a collection of BusinessBase objects

    public TaskList
    {
       get
       {
          if (this._scheduledTask  == null)
          {
             this._scheduledTask = TaskList.GetList(<< some animal id criteria here >>);
          }

          return (this._scheduledTask );
       }
    }



    DataPortal_Update(object Criteria)
    {
       // to update here

       // Before you commit, do this if you have transfered a task from ScheduledTasks
        // to CompletedTasks.  It will force the lazy loding to reload the new list on completed tasks.
       this._scheduledTask = null;

       // do commit here

    }
}

Hope this helps.  BTW, I wrote this code here, not in VS, so obviously it won't compile but you get the picture

brettswift replied on Thursday, June 15, 2006

seems like our posts are overlapping.

There may be a better way of designing this.  If so, I'd like to figure out both, the better design, and the solution to this design (ie parent-child-grandchild scenario)

guyroch replied on Thursday, June 15, 2006

I also think that your design is somewhat flawed as it is, here is what I would suggest...

Animal (Root BusinessBase)
    - ScheduledTasks (CollectionBase)
       - Task (Child BusinessBase)
    - CompletedTasks (CollectionBase)   
       - Task (Child BusinessBase)

So you have a root business base object called Animal that has 2 collections of tasks, ScheduledTasks and Completed tasks. 

In your DataPortal_Update method, I would simply deleted tasks from the ScheduledTasks collection that have been marks as completed and then add this task into your CompletedTasks collection.

Hope this helps.

Copyright (c) Marimer LLC