Encapsulating Your Module
Both four-layer module architectures call for a sharp delineation between public and private layers. The distinction between public and private knowledge with controlled access to private knowledge through a well-defined public API is the essence of encapsulation and information hiding, both cornerstones of good object-oriented design.
To encapsulate your module along public and private lines, you must:
Determining How Your Application Uses the Data
As you begin implementing your application, you should always be aware of how the application will use its items and attributes:
The way in which the application uses its data suggests how you should organize it into workspaces and modules, as well as the naming conventions that you use.
Using Data in an End User Interface
An essential part of planning your application is to determine in advance the end user requirements of the application. While this is not always possible to do entirely before you begin the implementation phase, determining end user requirements is a good place to begin the design phase. That way, before you begin development, you already know which objects, attributes, methods, and procedures need to be made visible through the user interface and which do not.
The objects, attributes, methods, and procedures visible through the end user interface are considered public. Examples of public data include:
You organize public data together in an application on user-visible workspaces, and you use a consistent naming convention for all public classes, attributes, methods, and procedures.
Using Data Internally
In addition to public data, applications also require numerous objects, attributes, procedures, and methods that the end user never sees, which are considered private.
Examples of private data include:
For example, you might want to implement a timer by creating a transient object that is related to an object. The application creates and starts the timer when a certain event occurs, computes the duration, and then deletes the timer when the event is over. The timer object and related procedures would be considered private because they are only used in the internal representation.
Determining When to Make an Item Private
To determine if an attribute or class should be public or private, ask yourself these questions:
If you answer "no" to any of these questions, you should:
Defining Private Knowledge for Public Classes
Often, a public class defines private attributes and methods. If the public class can be subclassed and/or defines public attributes and methods, the public class should be located in the public layer.
You have two choices for defining private attributes and methods on public classes:
Using Configurations and User Modes to Enforce Encapsulation
To support encapsulation and information hiding, you should hide private items and attributes from the end user. To do this, you use item and instance configurations, a feature of G2 items and class definitions, respectively, that allows you to determine which attributes and user menu choices are visible for items in various user modes. You can also specify the behavior of the overall KB by specifying item configurations in the KB Configurations system table.
A user mode is a global switch that affects the behavior of items in different windows through their item configurations. Item configurations allow you to define any number of user modes, in addition to the built-in administrator mode, which is unrestricted by default. Using item configurations, you can configure various behaviors in the different user modes, including:
You can use the following item configurations to enforce encapsulation:
Typically, you add item configurations to top-level workspaces so they apply to all the items in the workspace hierarchy.
Note: Unavoidably, private items and attributes are always visible in administrator mode, unless the module is proprietary. Also, private classes and procedures are visible in Inspect and are listed in the prompts of the text editor. Therefore, it is important that all users be aware of the public/private naming convention to avoid inadvertently referencing private items.
For information on implementing item configurations, see Adding User Interface Configurations.
Avoiding User Mode Conflicts in Modules
Changing a window's user mode affects user interface properties of all items visible on the window, independent of their module assignment. Because user modes do not support encapsulation directly through their syntax, you must use them very carefully to prevent architectural conflicts in your application. This is particularly important when designing reusable modules that are designed for inter-operability and configurability within a large organization or as domain-specific toolkits.
Specifically, when different modules support different behaviors in different user modes, you can experience problems when these modules interoperate. For example, if you must be in administrator mode to configure a certain object, keep in mind that all other objects in the entire application will also be subjected to administrator privileges while the configuration is in progress. This is because the user mode applies to the entire window, not just a particular module. If different modules each have different behaviors associated with different user modes, the behavior can quickly become confusing to the end user, who will be required to switch user modes frequently to access different behaviors.
When you are designing reusable modules, you have several options:
Thus, it is only appropriate to add user modes to modules within end user applications and, to a certain extent, in reusable modules within large organizations. If you are creating a module as part of a toolkit, you should leave choices regarding the number, naming, and functionality of user modes to the end user of the toolkit, that is, the developer who is using the module to build an end user application and has detailed information on the deployment environment. This means you should not define any user modes in addition to the built-in administrator mode. Instead, you should design in the flexibility that allows the end user to define user modes. Ideally, the functionalities of a module should be independent of its user mode and should operate the same way in administrator mode as in any other mode.
For example, you might want to provide a mechanism whereby the end user can subclass and introduce item configurations based on the subclasses. In some cases, it might be appropriate to build configurability into the design, without requiring subclassing. The G2 Menu System (GMS), for example, allows the end user to restrict menu entries by simply listing the user modes in which an entry is enabled. Because this information is soft-coded, GMS is ideally suited for "as-late-as-possible" implementation of user modes.
Using Proprietary Restrictions
If you are delivering a module in proprietary form, you can make the restrictions in every user mode, including administrator mode. In addition, you can text-strip private items that contain text such as procedures and methods.
For more information on making a workspace proprietary and text-stripping, see Preparing an Application for Release.
Guidelines for Encapsulating Modules
Follow these guidelines for encapsulating modules.
Use Public and Private Naming Conventions
To encapsulate public and private knowledge within a module, we recommend that you use the following naming conventions:
For more discussion about naming conventions that support encapsulation, as well as an example, see Using Public and Private Naming Conventions.
Avoid Cross-Module References to Private Attributes and Items
If you use public and private naming conventions to encapsulate a module, you should never directly reference any private item or attribute by name except in the module where it is defined.
Avoiding cross-module references to private items or attributes ensures that other modules will never corrupt the core with invalidated data, which is essential to the integrity of the module. Instead, you use public APIs to "get" and "set" attributes in the core, which ensures that arguments are valid before they are passed into the core.
Assume Public Classes are Subclassable
The power of object-oriented programming comes from the ability to extend the properties and behavior of objects through inheritance. You should assume that users can subclass all public classes. If this is not the case, you must explicitly document restrictions on subclassability.
If a class is subclassable, you should assume that users can override any associated public methods for the class, unless otherwise documented. For each such method, you must document the details of how to use the call next method statement to call the superior class's method. Method declarations should always declare whether a call next method statement is required.
Assume Attributes Inherited across Modules to be Public
If you use the public/private naming convention, you need to be aware of what happens when you inherit attributes across module boundaries. If a public attribute is defined in one module and this attribute is inherited via subclassing in a second module, the author of the second module cannot explicitly use the naming convention in the derived class to indicate if the attribute is public or private.
One way of addressing this potential ambiguity is to consider attributes inherited across module boundaries to be public attributes of the derived class, unless they are specifically documented otherwise. Thus, any attribute that does not begin with an underscore should usually be considered public. On the other hand, the Gfr-uuid attribute of the gxl-spreadsheet class, for example, which is inherited from a parent class in GFR into the spreadsheet class defined in GXL, is documented as a private attribute of the spreadsheet class, even though it does not begin with an underscore.
Limit the Scope of Rules and User Menu Choices to the Module that Defines Them
When you create generic rules by referring to higher-level classes in the for prefix of the rule, you should always refer to classes defined in the rule's module. Similarly, if you specify the Focal-classes attribute for a rule such that the rule only invokes for instances of the class, you should also define the focal class in the rule's module.
For example, you should generally not create a rule such as whenever any item is moved or whenever any variable receives a value in your module, because G2 will trigger these rules for items defined in other modules.
Additionally, you should always define user menu choices with the most specific applicable class and/or possible conditions so they cannot leak to inappropriate objects. For example, you should not define user menu choices on the item or kb-workspace classes unless you add conditions that ensure the menu choice will never appear in unintended locations.
Copyright © 1997 Gensym Corporation, Inc.