views:

67

answers:

2

Hi all, Looking for some guidance on how to structure/architect an app and I'm having some difficulties. Please feel free to NOT limit answers to tech details, as I'm not sure I'm doing things the most efficient way. I'm giving examples in VB.NET, but please feel free to use c#, as that's our official target language. I just know vb better. Also, we're using Visual Studio 2008.

So , the scenario: We're writing a web based app that (hopefully) will be customizable for several customers. For the sake of brevity and to not give away the farm, let's say it's a Personnel app.

So, I am going to have a Person base class. This will have very general methods only. It has a public property Name in class Class1.

Just for arguments sake, I will next create a NEW project/assembly Person.Employee

Now, since I might want to sell this app to Modeling agencies or Construction companies, maybe I want to add some very odd properties. CheekStructure or CanOperateHeavyMachinery

So, I will create New Projects in studio called Person.Employee.TinasModeling & Person.Employee.TomsConstructionCompany that will contain code for a specific company only.

You may assume that each of these assemblies will ONLY contain Business Logic appropriate to the level of the assembly within the hierarchy.

Remember, this is targeted to be a CUSTOM app for many different companies, but all dealing with Personnel. So this is why I came up with this model. If we find a bug in a lower level, I just want to be able to fix it and ship a dll or 2. This structure is targeted very heavily at us possibly having 20 or more Customized Applications. Distribution and Maintenance are the PARAMOUNT concerns here. There are not many developers. More than me, but less than 3 -grin-.

Now, the problem.

For simplicity' sake, I have Class1 in Person.

Up in my web app, I have a reference to Person.Employee.TinasModeling This web app project is just for Tinas Modeling.

If I have
Imports Person.Employee.TinasModeling
...

Dim this As New Class1
this.Name = "Some Value"

This will not compile unless I have a reference to Person also.

Question 1. Do I really need a ref to the base class Person( and Probably to Person.Employee also ), or is there some way of doing without it? I don't want anyone to accidentally create classes at lower levels of the inheritance chain. I'm not sure I want them creating them at lower levels at all, but maybe my concern is moot?

Question 2. Is this the best way of architecting this? Please remember, Maint and Distribution are the Primary concerns here. Picture finding a bug at a low level and having to open up 20 solutions, build each one, test ( even if automated ), build and distribute. I really want the ability to send out 1 Dll ( or maybe the one that got updated, and the dependant ones also? ) and be done, if that lower level dll passes it's tests

If I have to have refs to all lower levels of the inheritance tree, maybe I ought to just put all of this into one assembly under different namespaces?

Cripes, hope this is clear enough. I've been searching google all day for inheritance samples trying to find something useful. Hopefully someone here can clear the fog from my brain.

Thanks in advance, Bob

A: 

As a general principle, the classes at the root of the tree should all be abstract, because they are all incomplete.

When only the types specific to the individual customer application are made concrete, your problem of inadvertently creating other classes goes away.

Steve Gilham
+1  A: 

Sounds like a good candidate for using Dependency Injection - To get you started take a look at this example on the Spring.NET site. Apologies for the brief reply but I can elaborate tonight when I have some more time to sit down and code a quick example more relevant to your post.

EDIT: Okay first lets introduce two child objects, TinasModeling.Model and BobTheBuilder.Builder

public class Model : IPerson
{
    private bool canAct;
    public bool CanAct
    {
        get { return canAct; }
        set { canAct = value; }
    }

    private double weight;
    public double Weight
    {
        get { return weight; }
        set { weight = value; }
    }

    public Model()
    {
    }

    public bool PayPerson(double Amount)
    {
        if (canAct)
            RequestFilmBoxOfficeRevenue();
        Pay();
    }

    public bool NewJobRequest()
    {
        SendPhotoPortfolioToClient();
        SendJobLocationToModel();
        if (canAct)
            NotifyFilmCrew();
        if (weight < 40.00)
            ContactNutritionist();
    }
}

public class Builder : IPerson
{
    private bool canOperateDigger;
    public bool CanOperateDigger
    {
        get { return canOperateDigger; }
        set { canOperateDigger = value; }
    }

    private double certifiedBuilder;
    public double CertifiedBuilder
    {
        get { return certifiedBuilder; }
        set { certifiedBuilder = value; }
    }

    public Builder()
    {
    }

    public bool PayPerson(double Amount)
    {
        ReuqestTimeSheet();
        Pay();
    }

    public bool NewJobRequest()
    {
        SendBluePrintsToBuilder();
        if (!certifiedBuilder)
            RequestLegalAdvice();
        if (canOperateDigger)
            RequestDrivingPermit();
    }

Both classes are implemented in separate projects - they don't have to be but they are for reasons that I will explain later. As you can see they both do things differently but have one thing in common and that is that they implement the IPerson interface.

public interface IPerson
{
    bool PayPerson(double Amount);
    bool NewJobRequest();
}

This interface is implemented, again, in a separate assembly than both the above mentioned objects and hence our child objects references this assembly, lets call it BOL (Business Object Layer). In the BOL we implement another class called PersonController

public class PersonController
{
    private IPerson thePerson;
    public IPerson ThePerson
    {
        get { return this.thePerson; }
        set { this.thePerson = value; }
    }

    public PersonController()
    {
    }

    public bool PayPerson(double Amount)
    {
        return this.thePerson.PayPerson(Amount);
    }

    public bool NewJob()
    {
        return this.thePerson.NewJobRequest();
    }
}

The most important thing to notice about PersonController is the ThePerson property. This is going to be our dependency injection point. It is worth pointing out at this stage that the BOL layer is completely decoupled from any child objects. This is an important feature that is going to give a lot of flexibility come deployment and maintenance time.

Now we use and configure what we have built so far. Our application only references the BOL layer (decoupling also our main app from any child objects). In our main application we use the Spring.NET Spring.Core assembly and in a app.config file we add the following (for readability I have omitted the configSections tag)

  <spring>
<context>
  <resource uri="config://spring/objects"/>
</context>
<objects xmlns="http://www.springframework.net"&gt;
  <description>An  example that demonstrates simple IoC features.</description>
  <object name="Person"  type="BOL.BOL.PersonController, BOL.BOL">
    <property name="ThePerson" ref="Person"/>
  </object>
  <object name="Person" type="BobTheBuilder.BobTheBuilder.Builder, BobTheBuilder.BobTheBuilder">
  </object>
</objects>

Okay hope everyone is with me so far - the first object element refers to the class that requires the dependency injection, in our case the PersonController. The child property element refers to the dependency injection point in PersonController, or in our case the ThePerson property. The ref attribute of this element refers to our second object element.

The second object element we set what child object we wish to inject at run time (i.e. TinasModeling.Model or BobTheBuilder.Builder).

Finally the code that puts it all together

    static void Main(string[] args)
    {
        IApplicationContext ctx = ContextRegistry.GetContext();
        PersonController person = (PersonController)ctx.GetObject("Person");

        // Ready to use person with implementation specified in config
        person.NewJob();
        person.PayPerson();
    }

So this will instantiate PersonController based on how we have configured "Person" in our app.config.

So the answer to question one is no (well at least as designed above) - you separate your base implementations completely.

The answer to question two (Is this the best way of architecting this) is probably a little bit subjective - but with distribution and maintenance being your main concern in my opinion the above design meets your needs because the assemblies that are actually doing to heavy lifting is completely decoupled.

Hope I have explained myself well enough.

Dieter G