5

I have a class called D2Array which represents a fixed-size 2D array. It's meant to be generic and it comes with quite its lot of methods: getting an element, setting an element, extract a whole row, column, etc.

Now I want to write MyClass, which contains a D2Array, and in order to use MyClass properly I'll need to manipulate the state of the inner array. A straightforward solution would be having a getter to the array, manipulating it outside MyClass, and then putting it back into it through a setter.

(Instead of the usual Template<Type> notation, I used Template-Type to describe template parameters, which is a bit of a contraption, but StarUML wouldn't let me do otherwise.)

Diagram describing the above

This way however, any D2Array could be fed to the setter, which would then need some sort of validation to prevent invalid states from happening. This can quickly become expensive in my case and I find it horribly counter-intuitive that the setter may throw, so I think this is clearly not a suitable solution.


Another straightforward solution which also has the benefit of strongly enforcing encapsulation: in MyClass, write public methods to manipulate the inner array.

Diagram describing the above

This is actually fine when you need to allow manipulation of the array only in a way that is not trivial. However, when you just want to expose part or whole of the existing methods on the array, it translates to literally writing one-liners which simply forward arguments to the appropriate methods. It already feels dull having to do it for one class, let alone doing it for several classes which all use an inner array in a similar yet different fashion.

Retrospectively, I think this is the essence of my problem: I want to expose only a subset of the interface of my inner array, exactly as-is, having to write the least amount of forwarding methods to do so, and the contents of this subset also depends on the needs of the class which uses an inner array.


I came up with the following solution, which seems to fit my requirements nicely, but I'm wondering if it's not kind of an anti-pattern:

  • Write a new class D2ArrayWrapper (dumb name for the sake of the example), which contains the inner array as a protected member, and publicly declares and defines forwarding methods to all of the inner D2Array methods.
  • Have MyClass inherit from D2ArrayWrapper. This way, it contains an inner 2D array and already has all of the forwarding methods implemented, saving me the pain of writing them myself. This gain is doubled if I want to write MyClass2 which also manages a D2Array: just have it inherit from D2ArrayWrapper!
  • If I want any array method not to be accessible from outside MyClass, I just need to re-declare it from MyClass, as a protected or private member.

Diagram describing the above

Reasons I like it:

  • It kind of achieves what I wanted.

Reasons I dislike it:

  • Instead of re-declaring and re-defining the array methods that I want to be accessible from MyClass, I re-declare the array methods that I don't want to be accessible from MyClass. My DRY problem is not gone, simply reversed. (Also in C++, a simple using statement does the trick, but in Java, a whole re-definition is needed).
  • It only works in languages which handle multiple inheritance. It would work in languages which only support single inheritance too, but then it simply wastes inheritance possibilities for that class, which I find to be a huge unwanted side-effect.
  • It feels overkill when compared against the simplicity of the original goal.

I'm kind of at loss right now as to which solution I should go with.
Solution 1 is a no-go because of the complexity a validation would incur.
Solution 2 is fine, but feels dumb and somewhat inelegant.
Solution 3 feels like an overcomplication of things that got slightly out of hand, and after having given it some thought, I'm pretty sure that's not something I want to go with.

As a result I'm considering going with solution 2, but is there not another way?
Thanks.

5

If I'm understanding correctly, you want to support some operations on the inner array, but not others. If this set of supported operations is general enough for a D2ArrayWrapper to make sense, then I would suggest using an interface.

For example, let's say you want people to be able to read cells and rows from the array, but not write them:

interface IReadOnlyD2Array<T>
{
   T getCell(int row, int col);
   IReadOnlyList<T> getRow(int row);
   IReadOnlyList<T> getColumn(int col);
}

That's in C#, but the same principle applies in any OO language. Then you have your D2Array implement that interface, and return it via that instead.

private D2Array<T> _internalArray;
public IReadOnlyD2Array<T> Array => _internalArray;

If you're implementing a library for public consumption, you may want to create a wrapper that enforces the read-only nature so people can't force-cast it back to D2Array, but otherwise just don't do that.

-

An alternate approach, if you don't have the ability to change the class involved, or are using C++ and don't want the overhead of virtual calls, is to make a "view" class that encapulates D2Array and only exposes the methods you want:

template <typename T>
class D2ArrayView 
{
   public:
      D2ArrayView(const D2Array<T> &srcArray) : array(srcArray) { }

      T getCell(int row, int col) const { return array.getCell(row,col); }
      // etc ...

   private:
      const D2Array<T> &array;
}
| improve this answer | |
  • 1
    That may very well be the solution I was looking for but I got so lost in writing up an abstract enough question that I can't remember how and why the original problem arose, let me check – qreon Jan 7 at 23:48
  • 1
    Your first solution was indeed what I needed! Also I think I never really understood why interfaces where said to be such a powerful concept but your example is also a brilliant demonstration as to why that is. Would upvote again, thank you sir 👍 – qreon Jan 8 at 0:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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