Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee

A Sub-Class Should Not Access Private Variables In Its Super-Class

By
Published in Comments (6)

A few years ago, I read the Fundamentals of Object-Oriented Design In UML by Meilir Page-Jones. It was an interesting book, but definitely went over my head in many ways. A couple of weeks ago, with a few more years of experience under my belt, I wanted to give it another read. A lot of the book still goes over my head, but something stuck out this time - object sub-classing and private variable access. In the book, Page-Jones states that a sub-class should not make any attempt to access private variables in its super-class; instead, it should only use the methods and properties that the super class has already exposed publicly.

When I think about "coupling" and "connascence", I typically think about separate objects that have dependencies on each other. When it comes to object inheritance, however, I don't believe I ever applied the concept of coupling to the "inheritance chain;" I sort of always looked at inheritance as a "single" object. But, when Page-Jones pointed it out, it made a lot of sense. If a sub-class directly references private variables in the super-class, the sub-class is now tightly coupled to the actual implementation of the super-class. And, if the super-class changes its own internal storage, the sub-class will also have to change.

In essence, when a sub-class accesses the private variables in its super-class, the sub-class is breaking the encapsulation provided by the super-class. In order to keep the objects in an inheritance chain [more] loosely coupled, the sub-class should only access the methods and properties in the super-class's public interface. In doing so, you can think of the sub-class as more of a "consumer" of the super-class, and less so as an "extension" to the super-class.

To experiment with this concept, I created a super-class "Greeter" and a sub-class "RudeGreeter", both of which expose a public method, "greet." Both of them require the use of a private variable in order to computer their output; however, since the RudeGreeter cannot access the private variable in its super-class, it must rely on the super-class's public greet() method.

Greeter.cfc - Our Super-Class

<!--- NOTE: CFScript tags in place for Gist color coding only. --->
<cfscript>

component
	output = false
	hint = "I model an entity that greets you!"
	{


	// I create a greeter that uses the given default greeting.
	public any function init( required string defaultGreeting ) {

		// Store the greeting as a private variable. Once the object
		// is created, no one else needs to know about this.
		greeting = defaultGreeting;

		return( this );

	}


	// I return a greeting string targeted at the given name.
	public string function greet( required string name ) {

		return( "#greeting#, #name#." );

	}


}

</cfscript>

Notice that when the Greeter.cfc goes to implement the greet() method, it makes use of the private variable, "greeting." Now, let's take a look at RudeGreeter, our sub-class:

RudeGreeter.cfc - Our Sub-Class

<!--- NOTE: CFScript tags in place for Gist color coding only. --->
<cfscript>

component
	extends = "Greeter"
	output = false
	hint = "I model an entity that greets you!"
	{


	// I return a greeting string targeted at the given name.
	public string function greet( required string name ) {

		// Since "greeting" is a private variable in the super class,
		// we don't have access to it directly. Instead, we can only
		// access the interface that the SUPER class has already made
		// public. In this case, that is the existing greet() method.
		return(
			super.greet( name ) & " ... whatever, I'm outta here!"
		);

	}


}

</cfscript>

The RudeGreeter.cfc simply overrides the greet() method and augments the return value. Of course, it would have been easy to recreate the return value locally and return it like this:

return( "#greeting#, #name#. ... whatever, I'm outta here!" );

... but, that would require the RudeGreeter to access the private variable, "greeting," located within its super-class. Again, that would break encapsulation and create very tight coupling. As such, the RudeGreeter has no choice but defer the core greeting construction to the super-class and then augment the returned value.

To make sure this was all working, I instantiated and ran each class:

<cfscript>


	greeter = new Greeter( "Nice to meet you" );

	writeOutput( greeter.greet( "Sarah" ) );


	// ------------------------------------------------------ //
	// ------------------------------------------------------ //
	writeOutput( "<br /></br />" );
	// ------------------------------------------------------ //
	// ------------------------------------------------------ //


	rudeGreeter = new RudeGreeter( "Good morning" );

	writeOutput( rudeGreeter.greet( "Joanna" ) );


</cfscript>

When we run the above code, we get the following output:

Nice to meet you, Sarah.

Good morning, Joanna. ... whatever, I'm outta here!

The Fundamentals of Object-Oriented Design spends a great deal of time talking about object inheritance and about how input and output constraints needs to be configured at different points in the inheritance chain. It's definitely a fascinating read that makes you think very deeply about how objects are related to one another, and more importantly how objects are coupled to each other. As I try desperately to wrap my head around "object thinking," picking up these little tips is invaluable.

Want to use code from this post? Check out the license.

Reader Comments

10 Comments

Sounds like a good book, gonna have to pick it up.
I have always thought that even though the super is related through inheritance, that I want to treat the super in a very black box way.
This is why I get my panties in a bunch when told to use a global request collection inside my CFCs. If I haven't explicitly passed data to a CFC, I don't want it to know anything about that variable for the same reason you bring up in this post. Not that I haven't done it, but when I do, I always feel a little dirty!

11 Comments

As the saying goes, that's more of a guideline than a rule.

One can extend an object to add additional functionality, or in order to alter existing functionality by overriding existing methods.

If I override a factory method in order to implement an object pool, I can certainly call the method in the superclass to get the objects needed for the pool.

If, however, I wanted to fetch the objects or object data from another server, I'm keeping the interface that everyone knows about, but changing the implementation underneath. (Check out the Facade pattern.)

And some objects have methods specifically designed to be "hooks" into the objects internal processing. There, you may definitely need access to private data like, say, a datasource, that's not exposed publicly.

Actually, that's supposed to be the major distinction between the concepts of public, private, and protected data. All "public" access should be through accessors and "private" data should be private to the implementation. But "protected" data, like the aforementioned datasource, is there specifically so that it's available to subclasses but not to the world in general.

That's also why some languages and platforms like Objective-C and Cocoa promote concepts like delegation or events. You get to "override" how an object works by working to a specific interface, but you're not subclassing and the implementation is free to change as needed.

31 Comments

Does ColdFusion have the concept of a protected method yet? I remember you could restrict a method's access to package-level, but not protected. I also stopped using CF a long time ago.

Anyway, no- a child object shouldn't be able to access any private method/property in the parent. Public, protected, and internal, yes. Not private.

15,848 Comments

@Mark,

Oh man, I remember working on a project where *every single* CFC made *multiple* references to the Request scope for all kinds of configuration settings. I kept wanting to go in and factor it out; but, it always felt like such a Herculean task (it was a Legacy app).

The more I learn about object design, the more and more I want to pass in as much as possible!

15,848 Comments

@Michael,

Certainly, all things in moderation - it's all trade-offs. I'm just trying to bring some thought to something that I have never really thought about in the past.

I was just reading about the "hook method" the other day in Sandi Metz' book on OOP in Ruby. I thought that was a really cool idea as well - leaving a method intended to be overridden as part of a larger algorithm. So much cool stuff in well thought-out OOP.

15,848 Comments

@Matt,

That's a really good question. I had to look it up. As of ColdFusion 9, it looks like there is still only:

* Public
* Private
* Package
* Remote (for remote API calls).

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel