views:

59

answers:

4

I have an app that has subtle differences depending on where it's being viewed.

Variations to business logic & view styles are fine - this is all handled through dependency injection & CSS respectively.

However, where I'm coming unstuck is with small variations on view layout / elements.

For example - if a user is running our application in an in-store kiosk, we use subtly different navigation options, than if they are running it in a desktop environment, or via a web browser. We may choose to hide a button, or a navigation bar.

Currently, I'm doing stuff like:

[Inject]
public var environment:Environment;

public function get labelVisible():Boolean
{
    switch (environment.channel)
    {
        case Environment.KIOSK :
            return false;
        case Envirnoment.WEB : 
        case Envirnoment.DESKTOP : 
            return true;
     }
 }

However, I'm concerned about the Environment class leaking all over the place.

I don't want to over-engineer something, but I'm wondering if there's a suitable design pattern that I'm missing here that will keep me from having long switch...case or if...then's all over the place.

+2  A: 

If you design your view(s) in terms of interfaces, you can handle those differences in the implementations. For example, let's assume the labelVisible method is in a view called LabelView. It would have a method labelVisible() and then you might have a KioskLabelView, WebLabelView and DesktopLabelView. The correct view class would be injected based on the environment. Because the differences are subtle, I suspect that most of your view class(es) will be implemented in an abstract implementation with just these subtle details left to the subclass implementation.

Jeff Storey
A: 
Oren A
A: 

Depending on the programming language a web programming environment I would choose an attribute-based approach with rendering pre-processing driven by dependency injection or some kind of rendering adapter.

For example, in C# / ASP.NET I would use this approach:

  1. declare properties for all controls within a page or custom control and use an attribute to mark inclusion or exclusion based on environment;
    • inherit all page and custom control classes from a common ancestor that processes all properties on a suitable page-life-cycle event, evaluate visibility conditions and alter the control's visibility and/or other properties.

Pseudocode:

public abstract class InEnvironment : Attribute 
{ 
     [Inject]
     public Environment environment;

     public bool abstract IsAvailable();
}

public class IncludeInEnvironment : InEnvironment { … }

public class ExcludeInEnvironment : InEnvironment { … }

public class MyControl : BaseControl
{
     // Large label is visible only in Kiosk mode
     [IncludeInEnvironment(Environment.KIOSK)]
     public Label LargeLabel { get; set; }

     / small label is visible anywhere but in Kiosk mode
     [ExcludeInEnvironment(Environment.KIOSK)]
     public Label SmallLabel { get; set; }

     …
}

public class BaseControl : Control
{
     // resolve declarative visibility of control before rendering takes place
     public override void OnPreRender()
     {
          // inspect all properties of this instance's class
          foreach (PropertInfo property in this.GetType().GetProperties())
          {
               // check if the property has at attribute influencing rendering
               InEvironment attribute = property.GetAttribute(typeof(InEnvironment));
               if (attribute != null)
               {
                    // adjust the control's visibility
                    ((Control)property.GetValue(this)).Visible = attribute.IsAvailable();
               }
          }
     }
}
Ondrej Tucny
+1  A: 

This is what the Abstract Factory pattern was made for.

Don Roby