blogRevisions for Private features

Comparing two revisions:

Mon, 05/02/2011 - 23:41 by David Le BansaisMon, 05/02/2011 - 23:42 by David Le Bansais
Changes to Body
-
In Eiffel (to the best of my knowledge), a class B that inherits from a parent class A has access to all features of A. So for this hasn't bothered me at all, to the point that I often change my '''private''' functions in C# to '''protected'''.
+
In Eiffel (to the best of my knowledge), a class B that inherits from a parent class A has access to all features of A. So far this hasn't bothered me at all, to the point that I often change my '''private''' functions in C# to '''protected'''.
 
 
 
But recently, as I was writing Eiffel programs to practice SCOOP, it occurred to me that I must now write a lot more features than I would before. Why is that? The reason comes from SCOOP controlled objects: what used to be simple qualified feature calls must now be turned into unqualified calls to 'private' routines. Let's see that with a quick example:
 
But recently, as I was writing Eiffel programs to practice SCOOP, it occurred to me that I must now write a lot more features than I would before. Why is that? The reason comes from SCOOP controlled objects: what used to be simple qualified feature calls must now be turned into unqualified calls to 'private' routines. Let's see that with a quick example:
Revision of Mon, 05/02/2011 - 23:42:

blog Private features

dlebansais's picture

In Eiffel (to the best of my knowledge), a class B that inherits from a parent class A has access to all features of A. So far this hasn't bothered me at all, to the point that I often change my private functions in C# to protected.

But recently, as I was writing Eiffel programs to practice SCOOP, it occurred to me that I must now write a lot more features than I would before. Why is that? The reason comes from SCOOP controlled objects: what used to be simple qualified feature calls must now be turned into unqualified calls to 'private' routines. Let's see that with a quick example:

	value_of_object_resource (some_object: SOME_CLASS): INTEGER
	local
		object_resource: RESOURCE
	do
		object_resource := some_object.get_resource
		result := object_resource.value
	end

With SCOOP, if some_object is separate, then object_resource has to be separate as well, which means it must be controlled before we can get its value:

	value_of_object_resource (some_object: separate SOME_CLASS): INTEGER
	local
		object_resource: separate RESOURCE
	do
		object_resource := some_object.get_resource
		result := value_of_resource(object_resource)
	end
 
	value_of_resource (a_resource: separate RESOURCE): INTEGER
	do
		result := a_resource.value
	end

So far, so good. However, the requirement that objects must be controlled has the unintended effect of sometimes turning classes into a list of small, atomic features. This change does more than doubling the size of the source code, it also changes the design of the class by making a lot of internal calls available to descendants.

Let's take a look at {ENCODING}.convert_to:

feature -- Conversion
 
	convert_to (a_to_encoding: ENCODING; a_string: READABLE_STRING_GENERAL)
			-- Convert `a_string' from current encoding to `a_to_encoding'.
			-- If either current or `a_to_encoding' is not `is_valid', or an error occurs during conversion,
			-- `last_conversion_successful' is unset.
			-- Conversion result can be retrieved via `last_converted_string' or `last_converted_stream'.
		require
			a_to_encoding_not_void: a_to_encoding /= Void
			a_string_not_void: a_string /= Void
		local
			l_is_unicode_conversion: BOOLEAN
			l_unicode_conversion: like unicode_conversion
		do
			l_unicode_conversion := unicode_conversion
			if
				l_unicode_conversion.is_code_page_convertable (code_page, a_to_encoding.code_page)
			then
				encoding_i := l_unicode_conversion
				l_is_unicode_conversion := True
			else
				encoding_i := regular_encoding_imp
			end
 
			encoding_i.reset
			if l_is_unicode_conversion then
				encoding_i.convert_to (code_page, a_string, a_to_encoding.code_page)
			elseif a_to_encoding.is_valid and then is_valid and then is_conversion_possible (a_to_encoding) then
				encoding_i.convert_to (code_page, a_string, a_to_encoding.code_page)
			end
		end

If a_to_encoding is separate, then a new feature must be added to make a qualified call to a_to_encoding.code_page

...
				l_unicode_conversion.is_code_page_convertable (code_page, to_encoding_code_page(a_to_encoding))
...
 
	to_encoding_code_page (a_to_encoding: separate ENCODING): like code_page
		do
			result := a_to_encoding.code_page
		end

My point is that, since the original version of ENCODING did not need this extra feature, there is probably no good reason to add it, from a design perspective. Therefore, SCOOP sort of forces you to design classes differently than what you would like to.

That would not be much of a problem (other than the obvious readability issue) if the new feature was private to the class, a possibility offered by several other languages but not in Eiffel. This sparked my curiosity, and I went through Object Oriented Software Construction, Eiffel, the language and Touch of Class. Alas, I couldn't find a good explanation of why not allow features to be hidden to descendants. Can anyone come up with one?

Before SCOOP, if anyone wanted to expose a simple interface to clients but also to descendants, the option to write features made of big do...end blocks was there. This is no longer the case in the world of separate object. It looks like this consequence was overlooked, and only the readability issue was acknowledged.