开发者

Architectural/Pattern question - problem when hiding implementation details behind interface

开发者 https://www.devze.com 2023-01-31 19:10 出处:网络
I have a couple of classes BitMask & BitMaskLarge that both implement an interface IBitMask that exposes functionality to perform bitwise logic operations, etc.The BitMask class contains the a pri

I have a couple of classes BitMask & BitMaskLarge that both implement an interface IBitMask that exposes functionality to perform bitwise logic operations, etc. The BitMask class contains the a private _mask field of type long and the BitMaskLarge class contains a private _masks array of longs.

The end user works with IBitMask and the implementation details of each of the classes should be transparent to them, such as how the bitwise logic is implemented when both inputs are of one type as opposed to of one type each.

When creating an IBitMask a BitMaskFactory is used that returns the appropr开发者_JS百科iate type, based on the bitwidth passed into the constructor.

The problem occurs when setting a bit in the bit mask using the SetBitIndex method. If the bit is outside the bounds of the current mask, BitMaskLarge simply adds another element to _masks and sets the appropriate bit. However BitMask cannot go beyond 64 bits and so need to convert to a BitMaskLarge. Obviously this is impossible when calling bitmask.SetBitIndex(100) on the BitMask class, which should set the bit at index 100.

One possible solution I can think of would be to make SetBitIndex internal, and to create a static method on the BitMaskFactory that returned the new or updated IBitMask. This is not ideal as it is not natural to use. If anyone has a better solution to this problem, I’d like to hear it. All ideas are welcome, no matter how fundamental the changes required.


You can make types implementing IBitMask immutable, and have mutating operations return a new instance. This would run like this:

IBitMask mask = BitMaskFactory(...); // assume mask is now a BitMask
mask = mask.SetBitIndex(100); // now mask is a BitMaskLarge

Making the instances immutable also offers thread safety "for free".

If you find this inconvenient, you can solve it by adding another layer of abstraction:

class BitMaskImplementation : IBitMask {
    private IBitMask mask;

    // BitMaskImplementation defers all operations to mask,
    // and you can change the object stored inside mask without
    // your callers knowing anything about what happened.
}

This loses you the thread safety though.

Update:

You shouldn't really go to all this trouble unless a BitMaskLarge is much more expensive for you than a BitMask, and you also expect nearly all masks to be simple BitMasks.


The only difference between BitMask and BitMaskLarge that has been described is the use of the _mask vs _masks field. If this is truly the only difference how about always returning a BitMaskLarge from the factory? In the case were you don't need more then one long then you will only have one long in the _masks array.


How about a frontend and a backend class? The frontend class would provide the actual facade for client applications to use (a role currently covered by your IBitMask interface). The backend class would provide the actual implementation. The frontend would then simply delegate all calls from the client application to its internal backend instance, and could transparently replace the implementation, if the bitset grows to large for the "small" version (or becomes small enough again to be represented by the "small" version):

 class BitMask {
     private IBitMaskData actual;
     public void SetBitIndex(int index) {
          actual = actual.SetBitIndex(index);
     }
 }

 // Stuff below is an implementation detail of class BitMask
 // and private

 interface IBitMaskData {
      ...
      IBitMaskData SetBitIndex(int index);
 }

 class SmallBitMask {
      IBitMaskData SetBitIndex(int index) {
          if( index < 64 ) { 
              // Set the bit actually...
              return this;
          } else {
              LargeBitMask newMask = new LargeBitMask(this);
              return newMask.SetBitIndex(index);
          }
      }
 }

 class LargeBitMask {
     ...
 }

(If you care enough about performance and memory requirements to make the distinction between large and small bit masks, you might, however, dislike this solution purely because of the additional overhead introduced)


You have to decide if you want dynamically growing bitmasks or not. If you want it, then create a single BitMask class which supports growing to arbitrary sizes. It can still perform good if you have <= 64 bits, don't worry. Otherwise you have to tell the factory how large bitmask you want, and then you should disallow setting higher indexes than what the class instance was intended for.


Personally I would say this situation needs to be defended against - as it is invalid input. If you want to be able to swap types, then you could expose ToBitMaskLarge and ToBitMask methods on the interface to force the implementing class to manage conversions.

Of course, this won't happen automatically, but it would then allow your caller to change their mind without revisiting the factory.


Two things to consider:

First, why can't you just use the BitMaskLarge 100% of the time? If you say "performace" I would submit to you that the difference is negligible.

Second, why is "upgrading" to a different class bitmask part of the spec? You should either limit the user to the IBitMask returned, or always use the "Large" implementation.

0

精彩评论

暂无评论...
验证码 换一张
取 消