2

I have a series of classes that represent widgets in a layout system. The base class for all of these concrete widget types is Widget. If I have a Button and a Text Box, these both derive from Widget.

The layout is structured as a tree. Each widget can have only 1 parent and zero or more children, which are also of type Widget.

Some of these concrete widget types need to share state. As a contrived example, let's say that each widget determines its own background color from a list of predetermined colors, configured by the user called Theme. The overall theme of the application allows you to define colors for broad categories of widgets. When a widget needs to know what its background color should be, it needs to actively have access to the same instance of Theme to do the lookup. So what I end up with is some widgets in the system (those that can be colored, such as the button and text box) needing access to a Theme instance but other widgets do not care about color.

Option 1 is to make Widget's constructor take a reference to the Theme, which subclasses can choose to use temporarily, store permanently (for continued use), or not use at all. The downside to this is that not every widget subclass cares about themes, so this is like having an "Ostrich" class derive from a "Bird" class with a function in it called "Fly()" (i.e. not all birds fly).

Option 2 is to use visitor pattern. After construction of all widgets, use visitor pattern with overrides for Button and TextBox to obtain state from the visitor object to do the thing it needs to do. Other widget types would be "defaulted" to no-op in the visitor class by having a 3rd overload that takes the base type Widget.

Is there an option 3 that is better? Any thoughts about the 2 options above? What's the right approach here?

3
  • WPF solves this problem by using "Attached Properties." You might want to have a look at that concept. Nov 8, 2018 at 21:00
  • Is there any chance that there will be two or more Themes active at the same time, with some Widgets referencing one Theme and other Widgets referencing a different one?
    – rwong
    Nov 8, 2018 at 21:13
  • @rwong No, in this case there will never be more than 1 instance of Theme. The widgets that care about themes all share a reference to that same, singular instance. Think of this in a shared_ptr concept: Shared ownership, but single instance. Nov 9, 2018 at 1:09

4 Answers 4

2

Your Option 1 is probably your best bet here. Try not to think of it as a Widget which doesn't use the Theme as much as it is as having a Widget which is "themed" but doesn't actually change appearance as a consequence.

Perhaps it is not as "pretty" as you'd like it to be, but consider what this means from a maintenance point of view. If you later decide that a particular Widget instance actually does require some theming afterall, you won't have to do anything more than directly fix the Widget to use the Theme instance. Adding new Theme configurations is simply a matter of changing Theme and then using the new properties in the Widget.

This is in addition to the already necessary act of carrying the instance to be used by children of your widget, which if nothing else, you must hold it because you should never presume the children of your Widget won't require a Theme instance.

If you feel this isn't adequate, you can always create a ThemedWidget interface and have all classes which use Widget to derive from that. But at this point, extending directly from Widget and not ThemedWidget means it cannot hold children which use the theme. It may be worthwhile from a purist perspective, if you don't feel it will likely change in the near future.

2
  • The issue you mentioned in the last paragraph about ThemedWidget not being held in the collection of widgets can be remedied by making ThemedWidget derive from Widget I think. Nov 9, 2018 at 15:00
  • 1
    @void.pointer Yes, this is what I meant to imply. My apologies if it was unclear. My point was to give you the possibility of having classes exclusively derive from Widget which don't deal with the theme instance.
    – Neil
    Nov 9, 2018 at 15:28
2

The problem with option 1 is that you are coupling the "theme" to the widget. Application themes can get very complicated and messy as new features are added to the system, so it might be useful to decouple the "theme" from the widget. For this you'll want to utilize the Factory Pattern:

  1. Remove all code references to the Theme from all of your Widget classes

  2. Create a new class called WidgetFactory

  3. Pass a Theme in to the constructor of the WidgetFactory to decouple the construction of the factory from the theme

  4. Expose 1 public method per widget that simply creates a new widget of that type

    Internally, these public methods know how to construct a new widget and pass the proper theme settings in to configure the widget.

  5. Whatever "owns" the widget factory should also "own" the theme so memory doesn't leak.

I'm a little light on C++ experience, and this next bit should be verified by someone more versed than me, but whatever is calling the widget factory to generate a UI element should then take ownership or responsibility for managing that memory, not the widget factory.

The responsibility of the widget factory should be the creation of widgets and marrying them with settings in the theme without the widgets knowing about the theme. This gives you flexibility to refactor the theme to match changing business needs without refactoring an entire class hierarchy of UI widgets in the process — and it allows you to refactor the widget classes without also touching the theme.

4
  • So this would basically be like, the factory class calls a static Create method implemented in each subclass of Widget. Each Create method takes a reference to Theme, and the Create method does the color lookup and passes it to the constructed subclass of that Widget as a post-initialization step? So, construct TextBox, do color lookup, call TextBox::SetColor() with the result, return the constructed TextBox as a Widget? Nov 9, 2018 at 14:57
  • @void.pointer: Not at all. The widgets should be completely devoid of code references to the Theme. If you need to set a background color, then pass some sort of Color struct to the widget constructor, or call a method on the widget to set the background color. Nov 9, 2018 at 15:31
  • 1
    And who does the color lookups post-construction? The idea with passing theme to each widget was so that they could do further lookups later on in their lifecycle. Nov 9, 2018 at 20:27
  • @void.pointer: Passing the Theme won't help you with runtime theme changes either though. Something needs to know the theme changed, and it needs a handle on all the UI elements in order to change things like the background color. But good point. A factory object just solves creating the objects (which makes sense, because the factory design pattern is an object creational pattern). Nov 9, 2018 at 20:45
0

My inclination would be to create a Widget descendant ThemedWidget that cares about its appearance. This could reference a static Theme object. It would be appropriate for Theme to be static because there is only one for all at any particular time.

That would leave the normal widgets unaware of and independent from the cosmetic stuff and allow the ones that need to do some tweaking to have their way with the theme data.

Your Widget factory would have to know about the new widget type and create it as appropriate. It could also pass the theme data at that point, at construction time. Then you would not need a static Theme, you could just pass an ITheme interface to ThemedWidget's constructor.

0

You could think about using an Observer or Notifier scheme. The widgets that need to know about Themes could subscribe to theme changes (the observable object). Whenever the theme changes, the observer is informed about the change, possibly sending the new theme along. In order to make it general (it might not be possible to send a Theme object), you can think about a configuration format, not only for themes, maybe a JSON object (essentially just a string), that could also be used to notify other objects about changes they might need to take into account.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.