Discussion
· Apr 22, 2018

Class vs Routine in ObjectScript. What Do You Use And Why?

Hi, Community!

Have a question for general discussion. 

In ObjectScript we have cls for classes and mac code, which both compile into int code. 

Is there any reason when you use mac instead of cls  for non-persistent classes?

For me the benefits for cls are:

1. Inheritance and other OOP features

2. Auto-documented code 

For mac one visible benefit is easier call in terminal:

do method^Utils(p1,p2)

vs

do ##class(Package.Utils).method(p1,p2)

What is your choice and why?

Discussion (43)9
Log in or sign up to continue

Classes and Methods forever!

#1) for documentation
#2) for all the possibilities and structural controls of  OO development.
 

#3)
.mac & .int is a left over from a previous millennium,
a (failing) attempt to mimic OO with the mindset of procedural methodology.

I'm personally disappointed that Atelier still supports mac.

It was a historical requirement. Accepted. For last millennium. Eventually still for some internals.
Definitely not for public use.

I think it really depends on your coding preferences. There are benefits to writing using Objects (CLS) and benefits to writing in (MAC) and which you use may simply depend on what your coding preferences are.

Do you like to code using objects, then CLS is the way to go. Are you a procedural programmer, then (MAC) is the way to go.

Using CLS, you program using objects with properties and methods. You gain the benefit of being able to access your object data via three different mechanisms, direct global access, object access, or sql. You implement methods and typically your methods are logically organized into the classes that they pertain to. You gain inheritance, XML support, JSON support, and so much more.

All that said, if you come from a background of procedural programming and don't have a need or desire to work in objects, there's nothing wrong with using MAC routines.

I often see user classes that extend either %RegisteredObject or %Persistent that implement only class methods with no properties at all. This produces larger-than-needed runtimes.

For classes that implement only classmethods (static) there is no reason to inherit an instantiable interface such as that provided by %RegisteredObject.

It isn't necessary but it is good practice to define such classes to be abstract.

Using a simple helper class such as the one used by the various $system.<class> classes implemented by ISC it is possible to provide help at the command prompt for all methods implemented in the class.

I tend to lean toward classes-only but I do have requirements that can only be met by using routines. I'm not a fan of the ##class() syntax and, outside of instantiation, there aren't very good alternatives to that syntax. 

Still can be so:

Class dc.test Abstract ]
{

ClassMethod Test("#")
{
  "Test_",s
}

ClassMethod mac() [ ProcedureBlock = 0 ]
{
sub1(s=1)
  "sub1_",s
  q
sub2(s=2)
  "sub2_",s
  q
procPrivate(s=3) {
  "procPrivate_",s
}
procPublic(s=3) public {
  "procPublic_",s
}
}

}

Result:

USER>zTest^dc.test.1(1)
Test_1
USER>sub1^dc.test.1
sub1_1
USER>sub2^dc.test.1
sub2_2
USER>sub1^dc.test.1(10)
sub1_10
USER>sub2^dc.test.1(10)
sub2_10
USER>procPrivate^dc.test.1(10)
 
D procPrivate^dc.test.1(10)
^

USER>procPublic^dc.test.1(10)
procPublic_10
USER>

Also, Classes lend themselves much better to programmatic access and manipulation of their content (you can do this with routines too but it is harder to do so due to their unstructured nature).

For example, the server-side Source Control hooks we use can programmatically insert an RCS Keyword as a class parameter ("SrcVer") into any class which doesn't already have it defined.   This is extremely powerful because it allows source control to create an automatic 'watermark' in every class created for our internal applications which can be programmatically access or from terminal:

SSO>write ##class(AppS.WebClient).#SrcVer
$Id: //custom_ccrs/us/ISCX/SSO/BASE/cls/AppS/WebClient.xml#6 $

This would require much more plumbing to do automatically with a routine!

Yes, that is true, however, that doesnt allow you to execute a class method from namespace A in namespace B.

When you :

do ["b"]tag^routine

Cache actually executes tag^routine in namespace b vs the namespace you are currently in.

Creating a package map for namespace a that maps a particular package from namespace b only makes that class available to namespace a.  When I execute methods within the mapped class those methods are executed in namespace a and not b.

Kenneth, I don't think you are correct here. Apart from invalid syntax you gave, even with the correct syntax the routine is fetched from the specified namespace and executed in the current namespace. See the following example using a simple test routine (YJM) that I created in my USER namespace and then executed from my SAMPLES namespace:

USER>zl YJM zp
YJM      w !,"Runs in ",$namespace,! q
 

USER>d ^YJM

Runs in USER

USER>zn "SAMPLES"

SAMPLES>d ^YJM

D ^YJM
^
<NOROUTINE>^YJM *YJM
SAMPLES>d ^|"USER"|YJM

Runs in SAMPLES

SAMPLES>w $zv
Cache for Windows (x86-64) 2017.2.1 (Build 801U) Wed Dec 6 2017 09:07:51 EST
SAMPLES>d ["USER"]YJM^YJM

D ["USER"]YJM^YJM
^
<SYNTAX>^YJM
SAMPLES>

I disagree with your assertion that you can't call at a tag in a way that you can call from the top:

USER>zp
YJM      w !,"Runs in ",$namespace,! q
         ;
SUB      ; A subroutine tag
         w !,"SUB^YJM runs in ",$namespace,!
         q
 
 
USER>zn "samples"
 
SAMPLES>d SUB^|"USER"|YJM
 
SUB^YJM runs in SAMPLES
 
SAMPLES>

I find it confusing that you talk about calling a routine in a namespace. As I see it you're fetching it from another namespace (i.e. the one the routine lives it), but you're running it in your current namespace.

You also need to be aware of what happens if the code you fetch from the other namespace makes its own calls to other code. Here's an illustration:

USER>zp
YJM      w !,"Runs in ",$namespace,! q
         ;
SUB      ; A subroutine tag
         w !,"SUB^YJM runs in ",$namespace,!
         q
         ;
Test1    ;
         w !,"About to call local line label YJM",!
         d YJM
         q
         ;
Test2    ;
         w !,"About to call a line label in a specific routine",!
         d SUB^YJM
         q
 
USER>d Test1^YJM
 
About to call local line label YJM
 
Runs in USER
 
USER>d Test2^YJM
 
About to call a line label in a specific routine
 
SUB^YJM runs in USER
 
USER>zn "SAMPLES"
 
SAMPLES>d Test1^|"USER"|YJM
 
About to call local line label YJM
 
Runs in SAMPLES
 
SAMPLES>d Test2^|"USER"|YJM
 
About to call a line label in a specific routine
 
 d SUB^YJM
 ^
<NOROUTINE>Test2+2^YJM *YJM
SAMPLES 2d0>
 
SAMPLES 2d0>q
 
SAMPLES>

In practice, the majority of artefacts in our Caché instances are .int files.  We have used classes for mapping globals, .NET web development and writing stored procedures. It is nice to be able to call a classmethod from within a .NET application using a tool-generated proxy class. I detest code that jumps all over the place and Xecute statements within globals. Please stop coding this madness! Let's make the world a better place...

I confess some of the .NET software we have calls a classmethod which in turn calls a legacy .int routine with a Do statement and many subsequent Do statements. Not ideal but necessary if you want to avoid re-writing everything on a massive system. You can also use ClassExplorer with your classes and the "comment-style" self-documentation for classes is useful.  Much of the code we support uses the legacy line-based syntax and there is truly no replacement for well-structured, easy-to-read code. It just makes debugging less of a headache. I love line-spacing and curly braces - the simple things you take for granted in other programming languages. I also can't see anyone doing RESTful API development without classes.

Having human-readable, intuitively named class packages rather than hundreds of obscurely named .int routines and globals makes life so much easier. Having a class in a package in a BIO namespace called BIO.Request with CRUD methods reads much better than an interactive programme invoked by do ^REQ. I also like being able to export self-contained class packages rather than trying to work out what .int files and globals I need to export from memory.