Private class members visibility

Could someone clarify why all encapsulated class members in JUCE are private? Why is it not protected? (This practice is not exclusive to JUCE; it appears to be a somekind of last trends in development.)
There is an existing fully functional component in JUCE and I am unable to extend. I require a simple additional function to be added, but I am unable to do so.
I am required to copy almost 1000 lines of existing code into my project and add three new lines.
Why? What is the point?

It could be that my question is not new, please share a link if there is already an explanation.

Thank you.

From an API developers pov protected variables might as well be public, because you can easily expose it in a subclass.
This would limit the chance for improvements in the implementation, because users might depend on that variable now, that wasn’t meant to be accessed.

I can’t believe that copying tons of code is a much better idea…
Instagram, with its bunches of the same videos, is coming into development…

If I’m extending the class, it is my responsibility to support my implementation and to update it when the framework is updated. This is a normal workflow…
Instead, I need to copy tons of code, and later, when the framework receives updates, I will need to analyze all copied lines to understand why it doesn’t work and what is different now in the vendor code…

Can you use composition, or an Adapter pattern instead of copying code - which is generally a bad idea, yo.

I asked ChatGPT for this Adapter pattern example, specifically for JUCE components, and it gave a pretty interesting answer … applicable in your case?

#include <JuceHeader.h>

// Target interface that the client expects
class CustomButtonInterface
{
public:
    virtual ~CustomButtonInterface() = default;
    virtual void setLabelText(const juce::String& text) = 0;
    virtual void setButtonColor(juce::Colour color) = 0;
};

// Adaptee: A specific JUCE component that doesn't match the target interface
class StyledButton : public juce::TextButton
{
public:
    StyledButton()
    {
        setColour(juce::TextButton::buttonColourId, juce::Colours::blue);
        setButtonText("Styled Button");
    }
    
    void setCustomStyle(const juce::String& text, juce::Colour color)
    {
        setButtonText(text);
        setColour(juce::TextButton::buttonColourId, color);
    }
};

// Adapter: Makes StyledButton conform to CustomButtonInterface
class StyledButtonAdapter : public juce::Component, public CustomButtonInterface
{
public:
    StyledButtonAdapter()
    {
        addAndMakeVisible(styledButton);
    }

    void setLabelText(const juce::String& text) override
    {
        styledButton.setCustomStyle(text, styledButton.findColour(juce::TextButton::buttonColourId));
    }

    void setButtonColor(juce::Colour color) override
    {
        styledButton.setCustomStyle(styledButton.getButtonText(), color);
    }

    void resized() override
    {
        styledButton.setBounds(getLocalBounds());
    }

private:
    StyledButton styledButton;
};

// Main Component to demonstrate usage
class MainComponent : public juce::Component
{
public:
    MainComponent()
    {
        addAndMakeVisible(buttonAdapter);

        // Use the adapter through the interface
        buttonAdapter.setLabelText("Click Me");
        buttonAdapter.setButtonColor(juce::Colours::green);

        setSize(400, 200);
    }

    void resized() override
    {
        buttonAdapter.setBounds(getLocalBounds());
    }

private:
    StyledButtonAdapter buttonAdapter;
};

// Main application entry point
class AdapterPatternApplication : public juce::JUCEApplication
{
public:
    const juce::String getApplicationName() override { return "Adapter Pattern Example"; }
    const juce::String getApplicationVersion() override { return "1.0"; }

    void initialise(const juce::String&) override
    {
        mainWindow.reset(new MainWindow(getApplicationName()));
    }

    void shutdown() override
    {
        mainWindow = nullptr;
    }

private:
    class MainWindow : public juce::DocumentWindow
    {
    public:
        MainWindow(const juce::String& name) 
            : DocumentWindow(name, juce::Colours::lightgrey, DocumentWindow::allButtons)
        {
            setUsingNativeTitleBar(true);
            setContentOwned(new MainComponent(), true);
            setResizable(true, true);
            centreWithSize(getWidth(), getHeight());
            setVisible(true);
        }

        void closeButtonPressed() override
        {
            JUCEApplication::getInstance()->systemRequestedQuit();
        }
    };

    std::unique_ptr<MainWindow> mainWindow;
};

// Start the application
START_JUCE_APPLICATION(AdapterPatternApplication)

Yeah, thanks for the suggestion @ibisum
I’ve already tried that, the issue here is that I’m able to use only public functionality there, which again leads me to the same issue. Encapsulated code inside the class is impossible to extend.

Anyway, after a few days of struggling, I’ve come to the decision to make my own private repo with a forked JUCE framework and add the required modifications for me there.
I’m changing components that I need to use to mark their private methods and fields as protected and then in my project code, I’m able to extend it.

Later, when there is a new version of JUCE, I’ll update my private repo to get the latest changes.

If you tell a bit more about your use case and what you try to achieve people could suggest solutions or in some cases the juce team might decide to expose functionality to allow you to achieve your goals.

My current tasks is to add additional columns to juce::PluginListComponent
and also I need to modify AudioDeviceSelectorComponent to change the type of the MidiOut selector.
But this is true for any class. There was a moment when I wanted to reuse AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox but it’s private.
Anyway, now I think I’ve got the idea of framework. All existing ready-to-use components and forms are like examples. If you need something, you need to develop it from scratch…

The table is driven by the juce::TableListBoxModel which you can replace using setTableModel()

Have a look at where the model is set up:

Maybe you don’t need to copy everything…

1 Like