Alrighty - coming back to this post. Having just finished writing it I will say
This was very helpful for me to write but I’m not at all sure it’s worth your time to read. So - reader beware.
I received my copy of Object-Oriented Design Heuristics by Arthur Riel on Saturday and spent a few hours yesterday working through it. For a book first published in 1996 it contains a lot of relevant information. Especially for a 4D dev because
we are transitioning from a procedural language (Classic 4D) to an object-oriented language (ORDA). Except that ORDA is not just another OO language a la 1996. (We were on what - v4 in '96?) My motivation for this discussion and this study is to ingrain
good habits for using Classes at the beginning of my work with them. Like so many things in 4D it’s easy to simply
make something work only to find out later on I’ve created a trap it is hard to get out of.
BTW - I wanted something to play with. I have always enjoyed board games and found this site some years ago: https://www.redblobgames.com/grids/hexagons/implementation.html I attempted to use the material here in earlier versions of 4D and found it extremely frustrating. With v18R3 it is pretty cool and so much easier. And Amit does a fantastic job on these pages.
“The object-oriented paradigm uses the concept of class and object as basic building blocks in the formation of a consistent model for the analysis, design and implementation of applications.” He uses the example of an alarm clock. We know what an alarm clock is, what it does and how to use one because they all work more or less the same way - our experience with one is vary much like another even if they are completely different technology internally. “In effect, you now have a concept called ‘alarm clock’ which captures the notion of data and behavior of all alarm clocks in one tidy package. This concept is known as a class.”
So a class is an idea, a scheme, a set of principles which I write out in code. When I create an object with those ideas I instantiate the object with those idea. In this case I create an alarm clock object.
This brings up another shift I have to make from my classic 4D mind set: an object is not just another variable. You recall from early on we have been told “JSON is not an object!” JSON is a document definition. It’s really useful but it’s also really static. I have object variables and I can do anything I want with them. But an object is not just a variable. An object is itself a class and always has been. We haven’t had access to much more than the data store aspect, though. So a C_OBJECT variable has been more like a fancy C_TEXT variable. Unless it was more like an ARRAY TEXT variable… Or a C_BLOB variable. My point is in the context of 4D classic programming an object variable was pretty much like any other variable - only different.
This is not true when you start to think in object oriented terms. Riel sets our 4 “facets” of an object:
- its own identity (may only be a memory address)
- attributes (aka properties or keys) and values
- the behavior of its class (the implementor’s view)
- the published interface of its class (the user’s view)
Typically I am both implementor and user. This is why the concept of hiding data in a class feels so strange. What’s the point? I just put something into this variable why not just use it?
This is such an important, fundamental question Riel makes it the first heuristic:
All data should be hidden within its class.
The violation of this heuristic effectively throws maintenance out the window. The consistent enforcement of information hiding at the design and implementations level is responsible for a large part of the benefits of the object oriented paradigm.
Well, I don’t intuitively agree with that because - well because I’ve been doing it differently since I started programming. But …
I frequently, preferably, break up complex operations into blocks or modules. Ideally these act as a black box with a single, or a few, method used by the rest of the program for implementing the module. Sending an email, printing, importing or exporting and so on. Within that black box are all sorts of variables and details the rest of the program need not worry about. The contrasting approach to modules is to simply start adding methods to do things and rely on IP or process vars created as needed to maintain the state of the operation. This is really horrible I think we pretty much agree. it leads to massive variable tables and great difficulty sorting out which variables are being affected by which methods.
Or what about wrappers? I’ve seen some projects where nearly all the native 4D commands are wrapped in a method. Why? If something changed in the way the command functions, or you need to add some extended error checking, you only need to change the wrapper - not every place the wrapper is invoked.
Both of these approaches are similar to this concept of data hiding within a class in that they are controlling access to the data and maintaining integrity of the model. They differ greatly in their effectiveness.
Now I would create a class instead of a
module. Or I may retain the idea of a module but implement several classes within it rather than dozens of methods. Wrappers are still useful but the number of them required goes way down if the operations are concentrated in a class.
4D doesn’t support the concept of public and private data or functions in a class so the only way to implement it is by convention. I’m using a convention of prefacing
private functions with an underscore and putting
private data into an object named $. I’ll see what I think of it.
This is leading me to think about situations where I need a class for something vs a simple data object. Or include a function to return a defined property:
Yes, it’s more verbose but provides a way to manage case sensitivity, changing definitions, and so on.
OK, this is getting long and I’ve only gotten through the first heuristic. It went a lot faster when I wasn’t writing it all out as I went along… Riel has a part called Implementing Object oriented Designs in Nonobject Oriented Languages near the back I think is worth mentioning because this summarized a lot of the workarounds I’ve employed in 4D.
This topic was of greater interest in the late 1980s when C++ was a moving target. At that time many C programmers decided that they wanted to take advantage of object oriented design but did not trust C++ for implementation.
Reading that I swapped
Classic… what’s old is new, as they say. Anyway Riel (pp 179) lists 5 constructs of note:
4D has never supported multiple methods named the same thing and distinguished by the type of parameter passed. When C_OBJECT came along we could pass a consistent parameter with varying properties and values. Now with C_VARIANT we can handle various parameter types.
A standard method of implementing the notion of class in a nonobject oriented language is to encapsulate a data structure with its associated functions. … using a function module.
Already talked about.
Also talked about already. Interestingly Riel brings up pointers as a means of data hiding. If you have ever worked on a project where pointers are passed more than one level from their source you know this is not so much
data hiding as it is
Inheritance is difficult to fake. The implementation “trick” … introduces accidental complexity in that … we must examine every class (data Structure) that contains a modified class to determine if the modified class is really contained or it is faking inheritance. … Many of the benefits of inheritance are lost in its implementation in a nonobject oriented language.
What’s that mean? Imagine a database where you have
Students -enrolled in >
** Student Class -is part of >
*** Class Section -based on >
**** Master Class.
‘Inheritance’ is like ‘normalization’ in this case: we wouldn’t put the Master Class ID in the Student Class record because that record inherits the Master Class through the Class Section. But if the tables are not linked (eg. related) then each Student Class record would have to maintain the Master Class ID and we would have to write extra code to check and maintain those values.
The fact is I’ve seen exactly that sort of denormalized data in old projects stemming from the time, long ago, when we didn’t entirely trust (understand?) how relations work.
Polymorphism is clearly the most difficult to implement in a nonobject oriented language with all solutions creating maintenance problems larger than the one we originally tried to avoid - explicit case analysis. The traditional approach to getting around polymorphism is to use pointer to functions … to do something that used to be called generic programming. [emphasis mine] …
It is important to note … the bookkeeping normally done by a programming language is required by the programmer. This often produces more maintenance problems than the original case analysis the programmers set out to avoid.
Look back in the nug archives and you will find dozens of threads orbiting around this polymorphic thesis like the rings around Saturn - several spawned by yours truly. The fact is creating truly generic 4D code has been very difficult to actually accomplish. My most successful attempts have been components of relatively simple function libraries and very specialized actions. Projects that employed extensive and deep use of pointers are extremely time consuming to work on because of the time required to trace through the ‘bookkeeping’ code to understand exactly what that code is supposed to be doing.
What this section says to me about writing classes is what started this thread - realizing classes are a --big deal-- and decisions I make now will be with me for a long time. That the preconceptions I bring to working with them need to be carefully examined and to be open to new ways of thinking about the work my code is performing.