Let's talk about how to design a Class

Having Classes in v18R3 is very cool and will dramatically shift the way a modern 4D database can be programmed. A lot of us in the 4D community may not have very much (or any) experience writing code that uses Classes. 4D makes it easy to extend what we know already and this makes it easy to get going - but what do I actually use a Class for? And how should I build one?

This discussion on Hacker News yesterday goes to those questions:
https://news.ycombinator.com/item?id=23607673

The slides in the article are good and I was inspired to order a copy of the book.

The very first heuristic: all data should be hidden within the class hit a point I’ve been wondering about - what exactly is the difference between a Class and an object with some functions? The slide brings up the idea of public and private data which is something we don’t have in 4D classes. That omission makes it easy for us proto-class developers to fuzz the distinction even more. The point being made throughout is, a class is not an object. Write accessors to retrieve data from the class or put the data in an object.

Heuristic A.2 also drives at something 4D devs frequently do - blend the UI with the structure. It could be tempting, for example, to create a Class that does things with the inputs from a UI form. The class could be defined in Form

Form.myClass:=cs.myClass.new()

and various inputs on the form could reference the class accessors:

Form.myClass.getValue() // .getValue returns the value of a property, let's say ".myValue"

The “problem” with that approach is the user can’t edit the value. The “solution” would seem to be to simply use the class data directly:

Form.myClass.myValue

This violates the very first heuristic that all data should be private. What to do? Knowing what I know now I would look at my overall design and change the UI to work with an object instead of a class. The form or object method can react to the UI and interact with the class.

From a 4D perspective what does this say about calling project methods? it would be tempting to have a function within a class that calls a time-tested lookup method. Because it’s time tested it returns a current selection so the class would call it and then have to perform some Selection to entity selection operation. To me this seems unwise. But it’s easy, leverages existing code and 4D probably doesn’t care. So…?

Some other heuristics I found particularly interesting coming from my 4D perspective:

  • A.11: roles vs. classes: Be sure the abstractions that you model are classes and not simply the roles objects play.
  • B.13: Do not create God classes: Be very suspicious of a class whose name contains DRIVER, MANGER, SYSTEM, SUBSYSTEM, etc. Main event loop?
  • B.20: Do not turn an operation into a class. Be suspicious of any class whose name is a verb or is derived from a verb, especially those which have only one piece of meaningful behavior.
  • C.28: Classes should not contain more objects than a developer can fit in his or her short-term memory. A favorite value for this number is six.
  • D.46: Explicit case analysis on the type of an object is usually an error. The designer should use polymorphism in most of these cases.
  • G.58: Do not use global data or functions to perform bookkeeping information on the objects of a class.
    Does this mean we should avoid a Class in Storage? And forget about IP vars. Actually, just forget about IP vars altogether.
  • N.60: Do not change the state of an object without going through its public interface.

I suspect there’s been a lot of thought given these points already in designing 4D. I also suspect this design anticipates some of these heuristics and favors some solutions over others. It would be great to hear about those ideas from 4D as we go about learning these new tools.

BTW - object and class property names should (must?) conform to ECMA 16 standards which basically says a property must:

  1. Not begin with a number
  2. Use characters a-z, 0-9, _ or $. no spaces, dots, etc.

I have seen examples in other code where the data properties of a class begin with an underscore or $. This is one way to indicate private data vs public which wouldn’t have any leading char.

2 Likes

I had too not much practice with classes.

4D just start with classes
and much additional class features coming
with next versions.

Only one simple example from javascript
(it can be much more complex than this primitiv one example).
But i can not explain all js-features what all can do with it.
So only this one simple,
In JS you have Getters and Setters,
with this one you can define one name
for getting and for setting a value.
(but in 4D maybe getters and setters come with any version in future, or i am not find it)

As you can see,
with expression/name “myPerson.nam”
you can get and set this.name.
myPerson.nam can be reference for displaying
and at same time for entering new values in input field.
myPerson.nam is written not with brackets, it is not a function.
myPerson.nam has a defined auto getter and a setter function.
(more Infos can found in handbooks for js)

class person {
  constructor(name) {
    this.name = name;
  }
  get nam() {
    return this.name;
  }
  set nam(name) {
    this.name = name;
  }
  nameAlert() {
    alert(this.name);
  }
}

/* e.g. define myPerson and set this.name to default "Max" */
myPerson = new person("Max");

/* e.g. change/set this.name to "John" */
myPerson.nam = 'John';

/* e.g. get this.name and give its value to myTxt */
var myTxt = myPerson.nam

/* e.g. any classFunction() call */
myPerson.nameAlert()

Hi Kirk,

Very interesting subject. Thanks for starting the discussion.

I have talked to java developers about how they write code. I heard the general rule you are mentioning. Class properties (i.e. data) are private and accessed through “setters” and “getters”.
The thing is, this code is boring to write and often the IDE will generate the code from a class description/definition (or using annotations if I remember).

The reasoning behind it is like it is safer to access data through and API compared to a direct access to the data (SQL, ODBC, whatever). It is an abstraction layer similar to an API to your class and you can control the data which is set in the class…

It is lots of “boilerplate” code (i.e. not fun/boring to write) unless you get paid by the LoC of course :rofl:

I have to admit I’ve never written a class with “Manger” in the name. I’ve probably done a “Manager” or two.

I also don’t agree with the “setters and getters only” rule. Setters and getters have their place, but in many languages the getter and setter functions are indistinguishable from direct property access. In other words, when you write

$person.department = Department.shipping

You shouldn’t need to know that you’re actually calling a setter that is also setting the “entryDoor” property. What happens when a department is set is up to the Person class, not the caller.

A primary use of classes for me will be form controllers. That appears to violate some of the principles you listed (mixing UI with classes), but MVC (Model-View-Controller) has been a part of environments using object oriented languages for a very long time.

re: C:28: Classes should not contain more objects than a developer can fit in his or her short-term memory. A favorite value for this number is six.

That is kind of small. I also find their assertion that “Most of the methods on the class should be using most of the data members most of the time” kind of strange. This is also going to lead to proliferation of small pointless classes, which is warned about earlier.

They also mention 2 slides later (D:40) that “Inheritance hierarchies should be no deeper than a person can keep in their short-term memory. A popular value for this is six.” An inheritance hierarchy 6 levels deep is much more complicated than classes with 6-7 data properties. I can’t even think of a realistic example where you’d be 6+ levels deep in inheritance hierarchy. Maybe something like:

Organism > Mammal > Vertebrate > Primate > Chimpanzee

Although I could only get to 5 there, and if you’re making classes to model every species of animal you’re probably doing something wrong (see class proliferation warning).

I don’t think G:58 is refering to storing a class instance in Storage. It seems to be referring to e.g. using a project method to do things the class should be doing. So for example if you wanted to do something on a list of employees, rather than having a project method loop through the employees and do some logic, you should create an Employee class method that takes a collection of employees and does the things.

I tend to agree, I like using “classes” (or just objects with custom methods) in my user interface. I especially like to use them to add asynchronous methods that does some time consuming task in a spawned worker, or do some processing on the server. The option to pass Formula as a parameter (i.e. a function pointer) makes it easier to create an abstraction layer to implement a non-blocking user interface.

But that is not really a comment on how to design classes, I suppose it is more about MVC and delegation protocols.

1 Like

I have mixed feelings about the getter & setter approach too. If you imagine a point class ( {x:0, y: 0}) to say you include .getX() and .setX(n) seems overly verbose. On the other hand #60 says “Don’t change the state of an object without going through the public interface.” Which is to say, “if changing X causes the state to change you should use the setter.” So it’s about maintaining internal integrity.

The option is to have setters for somethings (require calculation, change state) while simply leaving others open. This is pretty common in 4Dland, for better or worse.

I decided to jump into R3 with both feet and write a module/repo/library for accessing google sheets, all class-based. The most glaring omissions is public/private, but I would be 100% in favor of omitting them and instead saying “anything starting with underscore is private” (which is what I did with my classes, so hey, 4D, let’s just do that right now. I’m ok with no “end” keyword, and I’m just as ok with underscore prefixes making properties and functions private).
The setters and getters is one that I do, anyway, but it’s because I’m apt to rearrange the furniture without notice to the developer who is using the library, and I don’t want to break their code. Most of the time, the values that the developer cares about are passed in as part of the instantiation, so they’re in the constructor. All of those values have special meaning, and I don’t want the developer changing one (like a URL, for instance, because then before I do something, I need to fetch the new URL and process it) without the class knowing it and acting accordingly. Otherwise the class has to take the value, put it into an internal property, and then every time it uses the internal property check to see if the user tried to change it.
I tend to agree with trying to keep the class self-sustaining and self-isolated, but in the age of repos, I think it’s more useful to make the repo self-sustaining and self-isolated. If you were to submodule this repo into a project, I would expect you to take the entire repo, not a piece of it, and when and if I update the repo, I would expect you to move up the commit graph, not to grab one file from one commit and apply it to your current project.

How are you obtaining permission to read/write to the user’s Google Sheets?

I wrote it using what are called service accounts, so I envisioned it being run on 4d server and having power over the entire domain. There are ways to cut that back when you set up the credentials.

The repo is here, and includes a document for how to set things up.
Special thanks to @Keisuke_Miyako because he had already documented this process once, so I didn’t have to write all of it from scratch. His repo on this process is here.

1 Like

Thanks you for responding, mikey.

Let me see if I understand.

You are using an HTTP API, which is essentially REST.

You created a service account key.

You use the service account key to obtain a session token.

Each API request includes an “Authorization” header whose value is the session token.

Is the above a correct description?

correct. we are using the google rest api. in the repo and in the class documentation there are links to google’s documentation.

Goggle does not produce a “session token”, but an “access token”. the difference is that an access token expires after sixty minutes. Dropbox uses session tokens that can live indefinitely.

If you go into the repo, you will see there is a cGoogleAuth class. That manages obtaining the access token and refreshing it.

The repo is designed to allow simultaneous access to as many spreadsheets as you like. The process works like this:

  1. Create an object of type cGoogleAuth.
  2. Generate an object of type cGoogleSpreadsheet. One of the parameters you pass is the cGoogleAuth object you created in step 1.
  3. Repeat step 2 for as many spreadsheets as you want to have open in your app.
    Because you pass the cGoogleAuth object to each spreadsheet, they all share the single access token and header. Because every spreadsheet shares the access token, whenever any spreadsheet operation is performed, the token is automatically checked, and renewed if necessary, for all spreadsheets.

Miyako

can you give us an example of using a class as a formobject controller?

best
andreas

Alrighty - coming back to this post. Having just finished writing it I will say
TL/DR
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:

  1. its own identity (may only be a memory address)
  2. attributes (aka properties or keys) and values
  3. the behavior of its class (the implementor’s view)
  4. 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:

$var:=$myClass.get("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 1980s for 2010s and C++ for ORDA and C for Classic… what’s old is new, as they say. Anyway Riel (pp 179) lists 5 constructs of note:

Overloaded functions
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.

Class/Object

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.

Data Hiding
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 data obfuscation.

Inheritance

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

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.

I won’t be able to use classes for a while.

But if you think about what a form controller does, a class is a good fit. In current versions of 4D, you make a form controller by accepting an “action” parameter, and either a few generic parameters whose value depends on the action, or in v16+ an object as a parameter whose contents vary depending on the action. You have a case statement based on your “action” string. It’s a mess and ugly, but sometimes better than having logic sprinkled throughout the object methods on the form. The controller becomes more necessary if you’re using a Form object and object binding, since the compiler won’t tell you if your first name object method set Form.full_name and your last name object method sets Form.Full_name, and when you test you won’t get any errors.

In a class, you’d have instance methods with their own relevant parameters, instead of a string action parameter and some other stuff passed into a single giant method. The controller class could have a reference to the Form object, and the Form object could hopefully have a reference to the controller so form objects could easily call methods on the controller.

@Brooks.Kirk
This is great. I’m feeling less and less like spending hours and hours doing a couple of youtube videos on R3 because we’ve got you.

The first thing that really helped with Objects in R3 was this - an object in 4d is a pointer, but you don’t have to dereference it because 4d does it for you when you work on its pieces-parts. thus

C_object ($a;$b)
$a:=cs.someObject.new()
$a.someProperty:="lorem"
$b:=$a // reference to $a 
$b.someProperty:="ipsum"
alert ($a.someProperty) // "ipsum" -- because $a and $b reference the same memory.

This makes me happy.

Wrappers
Obviously, if it’s just you, you can do or not do whatever you like. Maybe you’ll trip over the ottoman and maybe you have a shortcut instead.
You obviously can also have a (conceptually) hidden and an unhidden property, depending on how paranoid you want to be about accidentally changing something.

Getters and setters don’t have to be painful.

$var:=$myClass.get("hiddenProperty")

Can also be accomplished with

Function propA
   $0:=This.hiddenProperty

And, really, you don’t have to assign the property, (conceptually) hidden or not, to a variable. If you’re intending to use it, then you should be able to either just grab it in the context of when you need it, or if you need to maniuplate it, maybe you should have a function in the class to do that for you in case you need to do that in other places, too.

Similarly, a setter can look like a regular function.

Public/Private
That is definitely missing and it would be welcome if it was not. I’m avoiding $ because I think I’m going to wind up with a situation where a local variable and a private property get confused, everything looks ok in the ME but it breaks when we run it. So, for now, at least, private functions and private properties are both getting the _ prefix.

Overloaded Functions
Overloading functions, as you know, isn’t an explicit thing in 4D, but by allowing a variable number of parameters to be passed and

$paramCount:=Count Parameters

and

Case
   :($paramCount=1)
      ...
   :($paramCount=2)
      ...
End Case

is, I think, an even better solution than pure overloading functions, because there is less maintenance and less of a cascade called inside of the class.

I think everyone should consider taking advantage of OO when it’s helpful and traditional when that is helpful. There really isn’t any reason why you can’t use both or mix them (ignoring some of the obvious cases where there are issues like threads).

Hi Kirk,

Very interesting to see your thoughts after reading this book.

This is not how I understand inheritance in object oriented programming. For me inheritance is sharing common features/behaviour. For instance you can have a “person” object. You can have a “teacher” object and a “student” object. The “teacher” and “student” can inherit from the “person” because both are persons and have common data “firstname”, “surname”, “gender”, “dateOfBirth”, etc…
But now you mention it we could have :

Students -is a >
** Person

Teacher -is a >
** Person

1 Like

Bruno,

Excellent point and does more clearly describe the inheritance within a class.

Jim,

Don’t forget you can write classes and compile them as a components which can be used by a binary database. I think this is going to lead to a rethink about what we can do with components.

Whole heartedly agree. I was happy to find that despite his rules about separating the UI from a class Riel still allows as how there is a place for ‘controller’ classes. I think in 4DLand we probably want to keep operations involving the UI separate from the data methods. As you probably know I’ve been a proponent of form controllers since v13. In general I find them great but they can get quite complicated and large. I take to heart the heuristics talking about keeping classes as concise as possible.

I agree. Perhaps we should make it a feature request? Frankly I didn’t really see the value previously but I’m coming to see it could be a useful feature.

Oh? That could be interesting.

We generally already should be keeping data methods separate from UI. A method Order_Apply_Discount should not set the color of a form object.

Classes just give us better ways of organizing code with data, and make it more obvious that the concerns should be separated.

I already made a feature request for it.