The Artima Developer Community
Sponsored Link

Weblogs Forum
An exercise in compromise in class interface refinement.

4 replies on 1 page. Most recent reply: Jun 1, 2007 12:07 AM by Matthew Wilson

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 4 replies on 1 page
Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

An exercise in compromise in class interface refinement. (View in Weblogs)
Posted: Jun 1, 2007 12:07 AM
Reply to this message Reply
Summary
Attempting to find a compromise between the constraints of a facade that wraps a system API, the limitations of a limited namespace naming scheme, and a user wanting more expressiveness, revealed an interesting compromise in design.
Advertisement
A recent post from an STLSoft user expressing dissatisfaction with the STLSoft Windows Registry Library highlighted an interesting conundrum. The user wished to create a registry key in a single statement, something like:
  winstl::reg_key  key(HKEY_CURRENT_USER, "SOFTWARE\\MyCompany\\MyApp");
This uses the winstl::reg_key class, which is actually a typedef to a specialisation of winstl::basic_reg_key. (Like std::basic_string, winstl::basic_reg_key can support different character encodings. Thus, winstl::reg_key is a typedef of winstl::basic_reg_key<TCHAR>. See here for more details.)

The problem with the above statement, from the perspective of our user, is that it does not create a key. It only opens the key SOFTWARE\MyCompany\MyApp if it already exists. If it does exist, then an instance of winstl::registry_exception is thrown, carrying the result code (ERROR_FILE_NOT_FOUND) returned by the underlying Registry API call, RegOpenKeyEx().

The corresponding constructor for winstl::basic_reg_key has three parameters, as follows:
  // In namespace winstl
  template <. . .>
  class basic_reg_key
  {
    . . .
  public: // Construction
    basic_reg_key(HKEY keyParent, char_type const* subkeyName, REGSAM accessMask = KEY_ALL_ACCESS);
    . . .
  public: // Sub-key operations
    basic_reg_key create_sub_key(char_type const* subKeyName, REGSAM accessMask = KEY_ALL_ACCESS);
REGSAM is a Windows API typedef from ACCESS_MASK, itself a typedef from DWORD. KEY_ALL_ACCESS is defined as follows:
  #define KEY_ALL_ACCESS ((STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & (~SYNCHRONIZE))
In other words, it's a whole lot of flags covering the bit-width of a DWORD. The consequence of this is that the third constructor parameter, carrying all possible (now and future) values that may be passed to RegOpenKeyEx(), cannot also carry an additional flag to enable creation by constructor, as in:
  winstl::reg_key  key(HKEY_CURRENT_USER, "SOFTWARE\\MyCompany\\MyApp", KEY_ALL_ACCESS | winstl::reg_key::createKey);
The reason is simple. Whatever value we might select for a member constant/enumerator winstl::reg_key::createKey cannot be guaranteed not to clash with Windows API constants to be used with the Windows Registry API functions. (If you're interested, I talk about this issue at more length in Chapter 17 of my new book, Extended STL, volume 1: Collections and Iterators, which is published in three weeks' time. Coincidentally.)

Given this restriction, we must look for an alternative. Our user (rightly) observed that the following single line form, which does have the desired semantics, is syntactically unappealing:
  winstl::reg_key  key = winstl::reg_key(HKEY_CURRENT_USER, "SOFTWARE").create_sub_key("MyCompany\\MyApp");
and instead suggested adding a function to support syntax of the form:
  winstl::reg_key  key = winstl::create_reg_key(HKEY_CURRENT_USER, "SOFTWARE\\MyCompany\\MyApp");
The problem here is that, for good or ill, the use of namespaces within the STLSoft libraries is restricted to a minimum. Specifically, elements from each of the sub-projects (COMSTL for COM components: namespace comstl; UNIXSTL for UNIX components: unixstl; WinSTL for Windows components: winstl; and so on) are defined within their single, top-level namespace. (If you're interested, this is an artefact of the early history of STLSoft, wherein compilers that did not (properly) support namespaces were supported by the libraries. If there is ever an STLSoft 2.x - we're currently at 1.9.1 - I might seek to address this and employ library-specific namespaces. But for now it is what it is.)

Hence, as shown above, a function inserted into the winstl namespace to create a key could not simply be named create(), or even create_key(), because that is not specific to the registry. So, it'd have to be reg_create_key() or, as the user suggested create_reg_key(). This option, with either name, does not appeal to me, as the function will be ligging around the winstl namespace with other free functions. But it's not really a free function, is it? It's actually a non-member function that is part of the winstl::basic_reg_key interface.

The answer to this conundrum is to declare a static creator method, as in:
  // In namespace winstl
  template <. . .>
  class basic_reg_key
  {
    . . .
  public: // Construction
    basic_reg_key(HKEY keyParent, char_type const* subkeyName, REGSAM accessMask = KEY_ALL_ACCESS);
    static basic_reg_key create_key(HKEY keyParent, char_type const* subkeyName, REGSAM accessMask = KEY_ALL_ACCESS);
    . . .
  public: // Sub-key operations
    basic_reg_key create_sub_key(char_type const* subKeyName, REGSAM accessMask = KEY_ALL_ACCESS);
This affords the following, relatively expressive, syntax:
  winstl::reg_key  key = winstl::reg_key::create_key(HKEY_CURRENT_USER, "SOFTWARE\\MyCompany\\MyApp");
To be sure, it's not as expressive as the straight (opening, not creating) constructor call. But that's actually a good thing, because it is actually doing something different and significant: creating a key rather than opening it. Positively, it remains closely associated with the STLSoft Windows Registry Library despite there not being a distinct namespace for it.

For your interest, the implementation of the method is very simple:
  template <. . .>
  basic_reg_key<. . .> basic_reg_key<. . .>::create_key(HKEY keyParent, char_type const* subkeyName, REGSAM accessMask)
  {
    return basic_reg_key(hkey, NULL, KEY_CREATE_SUB_KEY).create_sub_key(subKeyName, accessMask);
  }
The only noteworthy point is that the accessMask is used only for the call to create_sub_key(), whereas the constructor permission is KEY_CREATE_SUB_KEY. no more, no less.

The updated basic_reg_key class will be included in STLSoft 1.9.2, to be released in June.


Michael Feathers

Posts: 448
Nickname: mfeathers
Registered: Jul, 2003

Re: An exercise in compromise in class interface refinement. Posted: Jun 1, 2007 8:03 AM
Reply to this message Reply
It does look like it was an interesting conundrum. As I was reading through it, though, I was wondering what the facade really brought to the table. It looks like a rather thin veneer over the registry API.

Often when I work with teams, I suggest to them that there is a good chance that they will never use the full breadth of an API like that because it wasn't designed to cover their use case; it was designed to cover everyone's use case. What they are better off doing is creating some application specific abstraction that wraps the API. In the case of the registry, it often looks like a map. It's simple and the details of registry access are hidden from the rest of the application.

One of the benefits is better encapsulation. If you ever want to change the mechanism you use for registry-like information, you're in trouble if the API is used all over the place. Better to put it behind some application-specific API that presents only the capability you need. Today it can use the registry. Tomorrow it can persist anyplace.

Testing is another area where application-specific facades win. I don't know whether this API lets you make an in-memory registries (fakes) for unit testing classes that use it, but that's a handy feature and many APIs are quite set up to do that yet. If you make an application-specific API, it's easy to make your own.

Colin Caughie

Posts: 2
Nickname: ccaughie
Registered: Jun, 2007

Re: An exercise in compromise in class interface refinement. Posted: Jun 4, 2007 1:51 AM
Reply to this message Reply
This looks great, I didn't expect a response so quickly, much less a fix. Thank you!

I'm interested that you chose to use a public static method rather than a free functions. Your reasoning does make sense, and the resulting syntax is nice and clean, but it goes slightly against what Sutter says in "C++ Coding Standards" item 44, which is "Prefer writing nonmember nonfriend functions". He argues that if a function can be implemented without knowledge of the internals of a class, it should be, and this one definitely seems like it can be. I'm sure I've also read somewhere, possibly in one of Sutter's other books, that free functions that operate on a class can be considered part of its interface, even though they're not actual members.

On the other hand, in this case the static method has the advantage that you don't have to specify the basic_reg_key specialization when calling it. Looking more closely at my suggested solution, for it to work with any specialization it would have to be a function template which would be called like:

winstl::reg_key_w myKey = winstl::create_reg_key< winstl::reg_key_w >( HKEY_CURRENT_USER, ... );

Obviously you couldn't just deduce the specialization from the string argument type as you also need a way of specifying the traits and allocator parameters.

So I guess it maybe comes down to a choice between theoretically ideal and practically useful, and having read some of your book I'm not too surprised you opted for the latter. :)

Colin Caughie

Posts: 2
Nickname: ccaughie
Registered: Jun, 2007

Re: An exercise in compromise in class interface refinement. Posted: Jun 4, 2007 1:56 AM
Reply to this message Reply
I don't know, I quite like the fact that winstl offers this thin veneer over the Win32 registry functions. The value they provide is to help with all of the handle and memory management that makes raw Win32 code so ugly, while still offering all the functionality you have with the raw API.

Application-specific interfaces are good too, but there's no reason why these couldn't be implemented on top of winstl -- this would undoubtedly make them easier to maintain than if they were built on raw Win32.

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: An exercise in compromise in class interface refinement. Posted: Jun 4, 2007 2:59 AM
Reply to this message Reply
> I don't know, I quite like the fact that winstl offers
> this thin veneer over the Win32 registry functions. The
> value they provide is to help with all of the handle and
> memory management that makes raw Win32 code so ugly, while
> still offering all the functionality you have with the raw
> API.


Well put. The message I've consistently failed to get over wrt STLSoft over the years is that it is a deliberately thin level of abstraction. One of the primary purposes of STLSoft is to support the creation of other libraries, rather than attempt to be all answers to all needs.

> Application-specific interfaces are good too, but there's
> no reason why these couldn't be implemented on top of
> winstl -- this would undoubtedly make them easier to
> maintain than if they were built on raw Win32.

Again, well put. (Can I hire you as my marketing guru? <g>).

This is a very important aspect of STLSoft and, in my opinion, a much under-discussed factor in the design tradeoffs in the facade/adaptor design. In fact, I have been bold enough to coin a principle: The Principle of Intersecting Conformance (see http://www.stlsoft.org/doc-1.9/group__group____principle____conformance.html). This will be one of the feature concepts/principles in my next book, Breaking Up The Monolith, which I'm working on now. :-)

Flat View: This topic has 4 replies on 1 page
Topic: An exercise in compromise in class interface refinement. Previous Topic   Next Topic Topic: How Important is Coding Style, and How Do You Enforce It?

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use