OOP / Pattern: Customizing layout based on environment
I have an app that has subtle differences depending on开发者_如何学C 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.
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.
This is what the Abstract Factory pattern was made for.
How about creating an abstract class with methods signatures such as:
public static string DisplayedLabel(Label label, Environment environment)
{
//do checks here, return empty string if it shouldn't be drawn.
}
And then whenever you need to check just do:
string labelText=DrawLabel(Label label, Environment environment);
if (labelText !=String.Empty)
//draw
Notice: this can be improved by changing the static method's signature to:
public static bool DrawLabel(Label label, Environment environment,out Label displayedLabel)
{
//do checks here, return empty string if it shouldn't be drawn.
}
You can also inherit from each object you want to draw, and change their constructors to build the object according to the Environment they receive.
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:
- 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();
}
}
}
}
精彩评论