Rivenfall
Rivenfall

Reputation: 1263

How can I organize and load packages based configurable properties?

The system I'm working on can be configurated with different boolean properties. The theorical maximum of different configurations is 2^n where n is the number of such properties.

There is a configuration tool separated from the production code, which uses boolean expressions to select which code packages are loaded. This tool contains a class that defines methods like isWithX or more complex ones like isWithXwithoutYwithZ. A package is basically a list of class extensions that define or redefine some method definitions. Today the packages are named like myPackageWithAWithoutBwithCwithDwithoutE.

Since there is an evolving number of boolean configuration properties, the number of different packages and the size of their names is getting ridiculous and we can't see their names without scrolling all the time. There is also a lot of code duplication.

EDIT: since production code has no access to the configurated properties the question is now limited to those issues:

Right now the list of package names is basically all different combinations of names like this one: myPackageWithAWithoutBwithCwithDwithoutE except those where there is no behaviour specific to that configuration that we need to implement.

Right now for each package and each functionality: 1 method per package with the name of the functionality.

Upvotes: 1

Views: 63

Answers (2)

Max Leske
Max Leske

Reputation: 5125

I don't fully understand all of the aspects of your problem but maybe you could use a dynamic approach. For example, you could override #doesNotUnderstand: to parse the selector sent to the configuration and extract the package names:

doesNotUnderstand: aMessage
| stream included excluded  |
"parse selectors like #isWithXwithoutYwithoutZ"
stream := (#isWithXwithoutYwithoutZ allButFirst: 6) asLowercase readStream.
included := Set new.
excluded := Set new.
[ stream atEnd ] whileFalse: [
    (stream peek: 3) = 'out'
        ifTrue: [
            stream next: 3.
            excluded add: (stream upToAll: 'with') ]
        ifFalse: [ included add: (stream upToAll: 'with') ] ].

Then, all you need is a bit more code to generate the list of packages from that (I hope).

Upvotes: 0

Leandro Caniglia
Leandro Caniglia

Reputation: 14858

I cannot tell you how to solve your specific problem, so I will share with you some hints that might help you get started in finding the design that will actually work for you.

Consider a hierarchy of configuration objects:

ConfigurationSettings
  ApplicationSettings
  UserSettings
  DisplaySettings
  ...

The abstract class ConfigurationSettings provides basic services to read/write settings that belong to any specific section. The hierarchy allows for simpler naming conventions because selectors can be reimplemented in different subclasses.

The ApplicationSettings subclass plays a different role: it registers all the sections in its registry instance variable, a Dictionary where the keys are section names and the values the corresponding subclass instances:

ApplicationSettings current register: anEmailSettings under: `Email`

The abstract class provides the basic services for reading and writing settings:

settingsFor: section 
settingAt: key for: section
settingAt: key for: section put: value

Subclasses use these services to access individual settings and implement the logic required by the client to test whether the current configuration is, has, supports, etc., a particular feature or combination. These more specific methods are implemented in terms of the more basic settingAt:for:.

When a new package registers its own subclass its testing methods become available as follows:

self <section>Settings isThisButNotThat

where, for instance,

emailSettings
  ^(ApplicationSettings current for: 'Email') isThisButNotThat

and similarly for any other section. As I mentioned above, the division in subclasses will allow you to have simpler selectors that implicitly refer to the section (#isThisButNotThat instead of #isEmailThisButNotThat).

One other feature that is important to support Apply/Cancel dialogs for the user to modify settings is provided by two methods:

 ConfigurationSettings >> #readFrom:

and

 ConfigurationSettings >> #writeOn:

So, when you open the GUI that displays settings you don't open it on the current instance but on a copy of them

settings := ApplicationSettings new readFrom: ApplicationSettings current.

Then you present in the GUI this copy to the user. If the user cancels the dialog, you simply forget the copy. Otherwise you apply the changes this way:

settings writeOn: ApplicationSettings current

The implementation of these two services follows a simple pattern:

ApplicationSettings >> readFrom: anApplicationSettings
  registry keysAndValuesDo: [:area :settings | | section |
    section := anApplicationSettings for: area.
    settings readFrom: section]

and

ApplicationSettings >> writeOn: anApplicationSettings
  registry keysAndValuesDo: [:area :settings | | settings |
    section := anApplicationSettings for: area.
    settings writeOn: section]

Upvotes: 3

Related Questions