...
OOP really isn't that difficult. Once you learn the basics of object-oriented programming you probably won't look back on the experience and think you've come a very long way. It's actually a short trip. But it is a challenging one because it requires a change in how you think about programming. And that change will open up vast new programming possibilities which certainly can be daunting.
One way to get started with OOP is the DevRoadmaps Clarion Library (DCL), our open source repository of Clarion code. There's a lot of terrifically useful code in the DCL, and you'll need some basic OOP concepts under your belt to make full use.
Core concepts
So, what does a class look like? Here's a cut-down version of the DCL's string class (originally written by Rick Martin) for illustration purposes (the actual class has a more complicated declaration with many more methods):
...
As you can probably tell by looking at the code, classes (usually) contain both code and data. In order to use a class, you first have to create an instance of the class (if you want, you can have your Clarion program do this automatically for you - more later). And when you create an instance of a class you also have the opportunity for that instance to have its own data. In the above example there's a class property called Value - this is a reference to a string (it could also be a simple string but for a variety of reasons a reference works better here). The Value property continues to exist as long as the class exists. In contrast, the Assign method also has a variable, but that variable only exists for the duration of the call to Assign. As soon as the Assign method completes, the stringLength variable goes away.
Because class properties exist as long as the class exists, the different methods can each operate on the class properties. In the case of the string class, the Value property contains the value of the string, and the different methods perform actions on that string such as setting the value, appending a string, determining if the string begins with a specified value, etc.
I think the DCL_System_String class is a great example of the benefits of object-oriented programming. I recently had a requirement to find out how many times a comma occurred in a specified string. In hand code, that would look something like this:
Code Block |
---|
! Declare some variables in the data section
StringPosition Long
StartPosition Long
Count Long
! In the code section
Count = 0
StartPosition = 1
Loop
StringPosition = Instring(',',theString,1,StartPosition)
If StringPosition > 0
Count += 1
StartPosition = StringPosition + 1
Else
Break
End
End |
I haven't debugged that code so I don't know if it's exactly right. And that's kind of the point - I'd have to remember how to write the code, and I'd have to test it to make sure it worked.
Here's how I solved the problem with the string class:
Code Block |
---|
! In the data section
Str DCL_System_String
Count Long
! In the code
str.Assign(theString)
Count = str.Count(',') |
The object-oriented code has at least five huge advantages over the previous hand code:
- It's much shorter
- It took way less time to write
- It's much easier to understand
- It doesn't need testing because the class has already been tested
- It's much easier to use
The string class illustrates what I think are the two main benefits of object-oriented code: testability and reusability.
Testability
Most Clarion developers write code in embed points.
Encapsulation
Briefly, encapsulation means that a class contains code and data. You may think hey, this is no big deal, everything I do is code and data. And you're not far off the mark. But there's more to this, as you'll see.
Inheritance
You've probably heard enough about OOP to have a bit of an idea of what inheritance is about already. Using inheritance So do procedures, of course. But there's a key difference here: in a class you can have data at the method (procedure) level, and you can also have data at the class level.
Classes: like DLLs, only way better
In that respect classes are a lot like small DLLs - they are their own little bundles of procedures with (optionally) shared data, and that shared data can be public or private.
So why not just use DLLs instead of classes? Because DLLs really can't do all that much; they only give you a tiny fraction of what a class can do.
So what can you do with classes that you can't do with DLLs? Here are a few things:
- Create classes that contain other classes (composition)
- Supplement existing classes with your own code (inheritance)
- Insert your own code in place of existing code (virtual methods)
- Create plugin architectures (interfaces)
- Take a component- or building block-based approach to software development
Yes, you can sort of do some of these things with DLLs. Virtual methods are somewhat like callback procedures. And you can use TYPEd procedures to do something almost like interfaces.
But after you've been doing OOP for a while you'll find that those procedural techniques are shadows of what you can do with objects. There's a reason the programming world has so fully embraced OOP (which is about half a century old now): objects are incredibly flexible and useful.
Info | ||
---|---|---|
| ||
As good as object-oriented programming is, it isn't the solution to every problem. For instance, in recent years there's been a surge in interest in aspect-oriented programming, which addresses some of OOP's shortcomings. AOP isn't a replacement for OOP, however; it's generally used in conjunction with OOP. |
Creating an instance of a class
In order to use a class, you first have to create an instance of the class (if you want, you can have your Clarion program do this automatically for you - more on that later). And when you create an instance of a class you also have the opportunity for that instance to have its own data. In the above example there's a class property called Value - this is a reference to a string (it could also be a simple string but for a variety of reasons a reference works better here). The Value property continues to exist as long as the class exists. In contrast, the Assign method also has a variable, but that variable only exists for the duration of the call to Assign. As soon as the Assign method completes, the stringLength variable goes away.
Because class properties exist as long as the class exists, the different methods can each operate on the class properties. In the case of the string class, the Value property contains the value of the string, and the different methods perform actions on that string such as setting the value, appending a string, determining if the string begins with a specified value, etc.
An example
Here's some sample code that checks for a string that ends with the string 'xyz':
Code Block |
---|
AProcedureThatDoesSomething procedure(string s)
str DCL_System_String ! Create an instance of the string class
CODE
str.Assign(s) ! Assign the passed value
if str.EndsWith('xyz')
! do something
else
! do something else
end |
How would you go about writing the EndsWith code? Here's how the class does it:
Code Block |
---|
DCL_System_String.EndsWith procedure(string s)!,byte
thislength long
otherlength long
CODE
s = upper(clip(s))
IF NOT Self.Value &= NULL
if s = ''
return FALSE
end
thislength = len(clip(self.value))
otherlength = len(s)
if otherlength > thislength
return FALSE
end
if SUB(upper(self.value),thislength-otherlength+1,otherlength) = s
return TRUE
end
END
return false |
That really isn't code you'd want to embed somewhere in your app, at least not if you figured on using it more than once.
You could put it in a function library - although the above code assumes the Self.Value property has been set somewhere, there's no particular reason you couldn't also pass in the string.
But for something like a string class, having all the methods in one place makes it easier to do multiple operations on the same string, and code completion shows you all of the available methods for that object so you don't have to go hunting through the docs. When you need to add some new functionality, you know exactly where to go: the class definition. And your changes are automatically available anywhere that class is used.
Here's another example. I recently had a requirement to find out how many times a comma occurred in a specified string. In hand code, that would look something like this:
Code Block |
---|
! Declare some variables in the data section
StringPosition Long
StartPosition Long
Count Long
! In the code section
Count = 0
StartPosition = 1
Loop
StringPosition = Instring(',',theString,1,StartPosition)
If StringPosition > 0
Count += 1
StartPosition = StringPosition + 1
Else
Break
End
End |
I haven't debugged that code so I don't know if it's exactly right. And that's kind of the point - I'd have to remember how to write the code, and I'd have to test it to make sure it worked.
Here's how I solved the problem with the string class:
Code Block |
---|
! In the data section
Str DCL_System_String
Count Long
! In the code
str.Assign(theString)
Count = str.Count(',') |
The object-oriented code has at least five huge advantages over the previous hand code:
- It's much shorter
- It took way less time to write
- It's much easier to understand
- It doesn't need testing because the class has already been tested
- It's much easier to use
The string class illustrates what I think are the two main benefits of object-oriented code: testability and reusability.
Testability
Most Clarion developers write code in embed points. That is, after all, the point of embeds, right?
Maybe that's was once the case, but not any more.
The point of embeds is to give you a place to hook in your code. They are absolutely not there to contain your code. At least not the vast majority of your code.
In particular, when you put business logic inside an embed point, then the only way you can test that logic is to execute the program up to the point where your logic is exercised. And usually that means someone has to actually run the program, navigate to a particular screen, and probably enter some data and click a button.
That is a terrible way to test business logic.
On the other hand, business logic in classes can be tested much more easily. I use ClarionTest for this (included in the DCL) and I'll have some docs up soon under the DCL page.
Reusability
Embed code isn't reusable - it just sits at that embed point. (Okay, if your embed code is a routine or a local class, it's reusable within that procedure. But not elsewhere.)
Classes, however, can be reused in all kinds of interesting ways, and often in ways you never imagined when you first wrote the class
Info | ||
---|---|---|
| ||
No discussion of OOP is complete without at least a mention of the following terms: EncapsulationBriefly, encapsulation means that a class contains code and data. InheritanceWith inheritance you can create some code (a derived class) which automatically contains some existing code (a parent class). It's a tricky way of freeing you from retyping code when you want something that's almost like |
...
Polymorphism
...
something |
...
Composition
The fourth volume of this trilogy (apologies to Douglas Adams) is composition. Although not included in the classic threesome, composition is actually one of the most important features of OO programming as done by Clarion and many other languages. Composition lets you lump several (or many) classes together into a functional unit.
In my own work I find that composition is responsible for up to 80% of the code reuse in my applications. Object-based languages like VB which don't support inheritance have to rely entirely on composition for code reuse. Fortunately Clarion isn't hamstrung in this way, as AppGen-based development would be impossible or impractical without inheritance.
...
you just did, but not quite. When you think of inheritance, think of code reuse. PolymorphismPolymorphism is something Clarion has had in one form or another since its inception. When you use OPEN on a file, or on a window, you're seeing a primitive form of polymorphism. Effectively you have what looks like one procedure (or in the case of classes, method) that operates differently depending on what parameter type it receives. Polymorphism in all its polymorphic glory is beyond the scope of this article and isn't critical to a basic understanding of Clarion OOP. CompositionComposition lets you lump several (or many) classes together into a functional unit. In my own work I find that composition is responsible for the vast majority of the code reuse in my applications. I do use inheritance at times, and virtual methods are often a vital part of that strategy. But composition is my bread and butter. |
In order to understand how OO code works, however, you first need to see how it's structured.
...