0

I have recently encountered this problematic method (minimal reproducible sample in C++ but this question aims to be language agnostic past the syntax):

void MyObject::twice_bind_cycle() {
    _bind1();
    _bind2();
    _activate();
    _unbind2();
    _unbind1();
}

This entire method interacts with an API as do the binding/unbinding methods themselves. It is OpenGL, but since this question is more about design decisions, I think it belongs on here at Software Engineering SE. I have generalized the problem for those unfamiliar with its usage.

I find there is tight coupling between the pairs of binding and unbinding methods. In order to perform activate() correctly, _bind1() must run before _bind2() and _unbind2() before _unbind1(), which forms an implicit interdependence of state within twice_bind_cycle() between the binds and unbinds. This would be acceptable in many cases---in fact it appears I could merge the functions if I were not reusing them elsewhere in say, bind_cycle_1() and bind_cycle_2().

A problem arises when I want to move the inner _bind2() and _unbind2() functions from the MyObject class and into their own. Their implementations, which aren't shown here for generalization, strongly suggest to me they ought to belong to their own object. They do not rely on any object state, but they do control a critical part of the API and their semantics with MyObject are becoming increasingly incompatible as the project grows. Further I would like to write several new methods that call _bind2() and _unbind2() that should not belong to MyObject. Finally, I predict several issues that could occur if I do not decouple them soon and continue work as normal.

I am now concerned about how the twice_bind_cycle() is supposed to be re-implemented and still function correctly in the provided order, while attempting to preserve its approach to encapsulation.

Some approaches I have considered:

  1. Create the other object as proposed and use an intermediate class that contains both objects via composition. Move twice_bind_cycle() to this class. Okay, but activate() requires access to private variables inside MyObject and the binding/unbinding methods are private. I am willing to break encapsulation here and provide the getters necessary for activate() because I find the attributes it requires to be insignificant to other objects. But making the binding functions public is a no-go because they are a major part of the objects' internal implementation.

  2. Use a command pattern. From the brief research I made, it appears like making the methods public just with extra steps. I have little experience with this pattern so I understand this is a limited view.

  3. Make the binding/unbinding methods protected and inherit from both of the new classes (C++). Inheritance has its own problems and especially multiple inheritance. Although I am open to suggestions, I'd prefer to avoid that route in favor of composition. I am fine with interfaces, though.

  4. Use the friend keyword (C++ only AFAIK). Make an intermediate class and make it a friend in the MyObject and MyOtherObject classes. However, there is still transitive coupling via the intermediary and if I wanted to use either object independently, that association with it will be there.

What should I do? Are there any other options? It seems inevitable that a compromise must be made somewhere. Also, am I being pedantic about the level of encapsulation and coupling within my program? Do I have enough reason to be?

Thanks

13
  • 1
    What about a solution that lets you write like this: bond1(bond2(activateMe))? Commented Apr 20, 2023 at 14:51
  • I can't. These are API calls. Although the method does behave like a stack.
    – goober
    Commented Apr 20, 2023 at 15:05
  • Unless you mean wrapping a function around a bind pair and accepting an arbitrary function as an argument and calling it in the middle? Interesting approach
    – goober
    Commented Apr 20, 2023 at 15:13
  • 1
    @soung I do. It's called the "hole in the middle" pattern. It's a specific version of the decorator pattern. It lets you keep your binds and unbinds paired while applying them where ever. Commented Apr 20, 2023 at 15:21
  • 1
    Your unbind operation sounds like a perfect fit for RAII/context managers/try-with-resource/defer or whatever it's called in your language.
    – amon
    Commented Apr 20, 2023 at 15:33

2 Answers 2

1

You are using C++. You can use lambdas like so:

template<typename Middle>
void MyObject::bind1(Middle m) {
    _bind1();
    m();
    _unbind1();
}

template<typename Middle>
void MyObject::bind2(Middle m) {
    _bind2();
    m();
    _unbind2();
}

void MyObject::twice_bind_cycle() {
    bind1([&]() {
        bind2([&]() {
            _activate();
        });
    });
}

It is expected that the compiler inlines everything and reduces it to the you have shown in the question. This is a reasonable expectation, especially since the function templates must be defined in the header file so the compiler can see their definitions everywhere they are called.

1
  • I would accept this answer as it achieves the refactoring I was looking for but I asked for additional discussion that doesn't make this a StackOverflow question
    – goober
    Commented Apr 22, 2023 at 8:57
1

Specifically for OpenGL 4.5 (2014) or later, you can use Direct State Access functions which do not require binding.

Note: by now this should be supported on all graphics cards anyone cares about. It is also available as an extension which is supported on some very old cards such as GTX 260 and GeForce 8300 (with updated drivers), a card from 2006 that I'm sure you don't care about by now.

Your Answer

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

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