Jump to content
IGNORED

Encapsulation (in Pascal, in any language)


Rossman

Recommended Posts

I've separated the card logic, game logic and UI logic into different units. I wrote last night that I see the code differently as a result of doing this. I see it differently because I'm trying to get better encapsulation of my code.


By way of example, I have named suits of cards in an array: Hearts, Diamonds, Clubs, Spades. I had this in my CARDCODE logic, but although it (familialy) has to do with cards, the only time it matters is in the UI, so it's really UI code. I've moved code to do with suits from the card unit to the UI unit.


Ideally, I'd implement this by having


function getSuit(index : integer) : string;


that returned a suit name based on an index value. But to implement that, my choices are to repopulate an array of values every time this is invoked (time consuming), define a global variable (ugh, please, no) with population procedures and functions in the host program, constants (already fought that battle and lost, see prior post), or I have to pass a lot of variables (I did this for the win, but it's cumbersome).


What I've done instead is a hybrid, at least from a logical perspective. I'm not sure I'm thrilled with it, but given the nature of Pascal circa 1979 or so, I think this makes sense.


Everything in the INTERFACE block of a unit is accessible by both the unit as well as the calling program. This includes procedures, types and ... variables. A variable declared in the INTERFACE block of a unit is a global (at leas, as far as the USES UNIT level).


This is a convenient middle ground, but IMHO it's still a cheat. I have a host program (CDF) that invokes different units (CARDCODE, CDFTUI). I can define


UNIT cdftui


INTERFACE


TYPE

suit = array[0..3] of string;


VAR

suits : suit;


procedure loadsuits;

[Load the array]


...


...


(some procedure later)

write('Suit: ', suits[cardstack[x],1]);



I can invoke the procedure loadsuits from the calling program CDF, without needing the type or variable defined in CDF. I can retrieve values from the array suits[] from procedures or functions at the unit level without needing to define the type or array.


I'm being disciplined about referencing these - specifically, the array values, only at the unit level. But of course, I could choose not to be. Then I have the slop of global variables.


The point is, as I wrote earlier, this is a bit of a cheat. It ends up being a global that is locally defined at a unit level that I'm being careful to only reference at a local (e.g., unit) level. But it's how I think to best encapsulate the code in Pascal.


I know the Pascal community isn't large in number, so I'm wondering how all of you - Pascal, XB, RXB, Forth, Assembler, C, etc. - structure your code with encapsulation in mind.


Best regards,



R.


Link to comment
Share on other sites

I have always thought that shying away from global variables because the are global was a red herring.

If all globals are bad all the time we should never use a file. It''s accessible to the entire program too.

 

If the program requires a global variable that records a specific state for the program, what's the harm if it is used for the designed purpose?

 

In Pascal it is even nicer in that you can encapsulate access to that global variable to a specific function or subprogram that provides appropriate protections no?

Link to comment
Share on other sites

Just an observation on different language paradigms. Not to derail your topic.

 

I managed a relatively large Turbo Pascal program back in the late nineties. I love the language from a theoretical perspective. When your code is done well and complete it just looks ... right. I remember a computer magazine years ago having a "centre fold" in the Apr 1 issue. The picture was a red satin sheet, with a Pascal program listing on paper spread out on the satin. It's like that. :-)

 

But as I consider all your thoughts on these details II am wondering are you fighting to fit the program to the rigid Pascal compiler.

 

This kind of thought process has become less palatable to me now that I play with Forth. The compiler does so little so that you can /must make it what you need. This so shameful from the Wirth perspective and yet it is quite liberating for the programmer. The caveat being you have to think more like a language writer before you start writing the actual program code.

 

However Pascal program structure is quite similar (forward references were not preferred by Wirth) so maybe it's not that different.

 

Morning coffee thoughts.

 

Wish I had more to say about your specific question. Guess my answer at a high level is I try to encapsulate based on the way I would talk about the program. i.e. the subprogram/functions try to mirror my spoken language. After that I don't have a proscribed prescribed methodology.

 

(Correction courtesy professor Stewart)

Edited by TheBF
Link to comment
Share on other sites

The essence of encapsulation is that everything is done via interface specification. If your code in one spot 'knows' anything about how code in another spot does something, you are not doing it right.

 

For instance, your getsuit call is a mapper. It maps an integer onto a symbolic value expressed as a string. It may or may not use an array internally to do a lookup, but as long as external code doesn't have to know about that detail, your encapsulation is good. You could swap out the lookup array with a read from a file, and you wouldn't have to change anything else in the program. The simple act of putting it behind a function instead of leaving it as a bare global array is doing it right. Your TYPE declaration is extra points.

 

You also seem to be thinking about exactly how to represent the thing that holds the keys (integer) and values(string suit name). An array is the natural way to do this, but there are some options. I am rusty on my Pascal, but there is an Enumeration type, which you could use (http://wiki.lazarus.freepascal.org/Enumerated_types) that comes with the ability to get the string form of the enum value...but I don't know if the pascal you are using has that.

 

Anyway, so you have to init this array somewhere, and that's just a fact. If your pascal supports it, that's what the implementation and initialization sections of a unit is for:

 

unit advancedunit;
interface

implementation

initialization
begin
(* here may be placed code that is *)
(* executed as the unit gets loaded *)
end

finalization
(* code executed at program end *)

end.

 

That makes a 'private' unit level var and a chance to run code against at startup. In your case you could just put your array in the implementation section and use an array initialization literal.

 

If your pascal does not support this, then my advice is to not worry about it being 'global'. You have the concept down, so whether or not you leave a global in your code or not doesn't matter unless you expect teams of programmers using your code in the future to create large systems. In this case, they will make fun of you.

 

The other option is to use a function level declaration to 're init it every time'. It's probably not that much impact on runspeed unless you call getsuit constantly. Many compilers are smart enough to realize that they can make that kind of completely static initialization something that just happens once.

Link to comment
Share on other sites

The only thing I would say about encapsulation is this: it tempts you to write more code in order to get the architecture "right". But "right" is often a moving goalpost. Try to avoid the temptation to write more code/encapsulate just for the sake of it. Code should be able to justify its existence. If it can't then it is gratuitous and should be removed, otherwise performance may degrade, more memory may be required, and, contrary to popular belief, it will be harder to debug. Maybe not tomorrow but I guarantee it will be in six months as you battle against yourself as you struggle to pick apart the beautifully crafted but highly intricate code.

 

There be demons :-)

Link to comment
Share on other sites

Are you using UCSD Pascal on the 99/4A, or something else?

 

Yes, of course you are, you've written about that before. I just didn't realize that until now.

 

Myself, I like splitting things up into units I find logical. Whether they are "units" or called something else just depends on the system.

 

I wrote a Pascal program once that started with seven "uses". The units used were these purposes:

  • Global data definitions
  • Data entry
  • Show data on screen
  • Data I/O (to/from disk)
  • Change data
  • Print data
  • Various support things

The main program contained only the main menu system, used to select what to do, which more or less determined which of the referenced units were called.

 

This program handled entry, calculations and data reporting for systems used for dust evacuation from saw mills and similar plants. But what it did isn't too important. I did at least find the separation into these different parts to work well.

Having the global definitions in one separate unit made it possible for all other units to reference only that one, when they were compiled, just like the main program did. If it's worth it depends on the size of the program, but this was a 4000+ lines program that did run on the 99/4A, so keeping it in manageable pieces was important. The global unit also contained the main calculation logic.

Edited by apersson850
Link to comment
Share on other sites

The only thing I would say about encapsulation is this: it tempts you to write more code in order to get the architecture "right". But "right" is often a moving goalpost. Try to avoid the temptation to write more code/encapsulate just for the sake of it. Code should be able to justify its existence. If it can't then it is gratuitous and should be removed, otherwise performance may degrade, more memory may be required, and, contrary to popular belief, it will be harder to debug. Maybe not tomorrow but I guarantee it will be in six months as you battle against yourself as you struggle to pick apart the beautifully crafted but highly intricate code.

 

There be demons :-)

 

Yes, he's right. Abstraction has a cost. Taken too far it's often worse than the original problems. Unless you know you will be working with a team of programmers on a large project it's mostly not worth taking it too far.

Link to comment
Share on other sites

Just an observation on different language paradigms. Not to derail your topic.

 

I managed a relatively large Turbo Pascal program back in the late nineties. I love the language from a theoretical perspective. When your code is done well and complete it just looks ... right. I remember a computer magazine years ago having a "centre fold" in the Apr 1 issue. The picture was a red satin sheet, with a Pascal program listing on paper spread out on the satin. It's like that. :-)

 

But as I consider all your thoughts on these details II am wondering are you fighting to fit the program to the rigid Pascal compiler.

 

 

 

Yeah, absolutely - now that the code has better structure it just looks ... nice. Or maybe I'm so used to seeing "bad" I'm enthralled by seeing it "less bad".

 

I appreciate your comment. I was grappling with an architecture decision and am probably fitting my ideas of structure affected by years in Java and other languages, but lost in the fog of decades since last using Pascal and years since being a developer. I won't assign this to the compiler forcing me to do anything, but there is something in my coming to grips with how to structure code in Pascal. I really need to get in the heads of the people who designed the Pascal language. They had a vision of structured programming, and limitations of computing to deal with in the 60s and 70s.

 

I have an academic book for reference, but I haven't been able to read through the authors to understand the philosophy behind the compiler.

 

But, don't think for a moment this isn't great fun.

Link to comment
Share on other sites

The essence of encapsulation is that everything is done via interface specification. If your code in one spot 'knows' anything about how code in another spot does something, you are not doing it right.

 

 

 

Dan, FWIW this is the implementation decision that I took in the end. This was the most straight-forward way to solve for this. Taking your second sentence, I wanted the code in one spot to know about code in its spot and have access to code in its spot. This simplified things greatly, as I didn't end up with a UNIT USED by the calling program and a different UNIT within it. This felt cleaner and, in any event, is a lot fewer lines of code!

 

And that was my goal all along. I started to see code creep, code duplication, code spaghetti, and wanted to contain it.

 

Thanks!

Edited by Rossman
Link to comment
Share on other sites

The only thing I would say about encapsulation is this: it tempts you to write more code in order to get the architecture "right". But "right" is often a moving goalpost. Try to avoid the temptation to write more code/encapsulate just for the sake of it. Code should be able to justify its existence. If it can't then it is gratuitous and should be removed, otherwise performance may degrade, more memory may be required, and, contrary to popular belief, it will be harder to debug. Maybe not tomorrow but I guarantee it will be in six months as you battle against yourself as you struggle to pick apart the beautifully crafted but highly intricate code.

 

There be demons :-)

 

Yes! Completely agree with you. I obviously wasn't there when I wrote this post last night, so I appreciate yours (and everybody else's) feedback on this.

 

One of the core values I have learned to advocated for in engineering and management is Simplicity of Design. If I'm over-engineering my code to fit an architecture that I'm enamored with, I'm solving the wrong problem (architecture, not outcome), I'm creating code that will be harder to test, and harder to maintain.

 

Simplicity won out. Once I realized that (late last night!) I could see how many fewer lines I had, and how much easier it was to follow the code.

Link to comment
Share on other sites

Myself, I like splitting things up into units I find logical. Whether they are "units" or called something else just depends on the system.

 

 

What you describe here was how I ultimately defined my groupings. What is logical in one context may not be logical in another, and this was the question I was wrestling with last night. I could come up with multiple groupings, and multiple mappings, and pretty soon I have units using units to support this overloaded logical definition that I have in my head.

 

Suit names, card names might appear to be card related, but they have no bearing on the card logic, only the UI. So what I had thought of as card logic is really UI code when it comes to function. Imagine for a moment that a text UI, so it might seem that a Trey of Clubs is a Thing. But in a graphical UI, that Trey might take on multiple forms or visualizations.

 

The logical unit in this case was determined by my crude M-V-C implementation.

Link to comment
Share on other sites

Yes, and that's fair enough, I'd say. As a profession, I write software for machines, at least sometimes. Today's production machinery is frequently designed in modules, which the customer may need, or not. I try to make my software reflect that, so that perhaps one process handles a certain option. If the option is there, the process is assigned to a task and runs. If they don't have that option, then it never starts in the mulitprogrammed system, thus never competes for CPU resources.

 

Then there are frequently a few processes that handle logical things in the machine too, things that aren't dependent on whether a certain piece of hardware is available or not.

 

The language capabilites are more or less similar to Pascal in such machines. Sometimes not similar at all. But you can still do similar encapsulations, at least in a way that works for you, even if you sometimes technically very well can poke into some process' private parts from another. Unlike a Pascal unit's implementation part, which you simply can't reach from the outside.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...