Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Introduction to S4 Classes

Save for later
  • 36 min read
  • 22 Oct 2014

article-image

In this article, by Kelly Black, the author of the R Object-oriented Programming book, will examine S4 classes. The approach associated with S3 classes is more flexible, and the approach associated with S4 classes is a more formal and structured definition.

This article is roughly divided into four parts:

  • Class definition: This section gives you an overview of how a class is defined and how the data (slots) associated with the class are specified
  • Class methods: This section gives you an overview of how methods that are associated with a class are defined
  • Inheritance: This section gives you an overview of how child classes that build on the definition of a parent class can be defined
  • Miscellaneous commands: This section explains four commands that can be used to explore a given object or class

(For more resources related to this topic, see here.)

Introducing the Ant class

We will introduce the idea of S4 classes, which is a more formal way to implement classes in R. One of the odd quirks of S4 classes is that you first define the class along with its data, and then, you define the methods separately.

As a result of this separation in the way a class is defined, we will first discuss the general idea of how to define a class and its data. We will then discuss how to add a method to an existing class. Next, we will discuss how inheritance is implemented. Finally, we will provide a few notes about other options that do not fit nicely in the categories mentioned earlier.

The approach associated with an S4 class is less flexible and requires a bit more forethought in terms of how a class is defined. We will take a different approach and create a complete class from the beginning. In this case, we will build on an idea proposed by Cole and Cheshire. The authors proposed a cellular automata simulation to mimic how ants move within a colony.

As part of a simulation, we will assume that we need an Ant class. We will depart from the paper and assume that the ants are not homogeneous. We will then assume that there are male (drones) and female ants, and the females can be either workers or soldiers. We will need an ant base class, which is discussed in the first two sections of this article as a means to demonstrate how to create an S4 class. In the third section, we will define a hierarchy of classes based on the original Ant class. This hierarchy includes male and female classes. The worker class will then inherit from the female class, and the soldier class will inherit from the worker class.

Defining an S4 class

We will define the base Ant class called Ant. The class is represented in the following figure. The class is used to represent the fundamental aspects that we need to track for an ant, and we focus on creating the class and data. The methods are constructed in a separate step and are examined in the next section.

introduction-s4-classes-img-0

A class is created using the setClass command. When creating the class, we specify the data in a character vector using the slots argument. The slots argument is a vector of character objects and represents the names of the data elements. These elements are often referred to as the slots within the class.

Some of the arguments that we will discuss here are optional, but it is a good practice to use them. In particular, we will specify a set of default values (the prototype) and a function to check whether the data is consistent (a validity function). Also, it is a good practice to keep all of the steps necessary to create a class within the same file. To that end, we assume that you will not be entering the commands from the command line. They are all found within a single file, so the formatting of the examples will reflect the lack of the R workspace markers.

The first step is to define the class using the setClass command. This command defines a new class by name, and it also returns a generator that can be used to construct an object for the new class. The first argument is the name of the class followed by the data to be included in the class. We will also include the default initial values and the definition of the function used to ensure that the data is consistent. The validity function can be set separately using the setValidity command. The data types for the slots are character values that match the names of the R data types which will be returned by the class command:

# Define the base Ant class.
Ant <- setClass(
   # Set the name of the class
   "Ant",
   # Name the data types (slots) that the class will track
   slots = c(
       Length="numeric",           # the length (size) of this ant.
      
       Position="numeric",         # the position of this ant.
                                   # (a 3 vector!)
      
       pA="numeric",               # Probability that an ant will
                                   # transition from active to
                                    # inactive.
       pI="numeric",               # Probability that an ant will
                                   # transition from inactive to
                                   # active.
         ActivityLevel="numeric"     # The ant's current activity
                           # level.
       ),
   # Set the default values for the slots. (optional)
   prototype=list(
       Length=4.0,
       Position=c(0.0,0.0,0.0),
       pA=0.05,
       pI=0.1,
       ActivityLevel=0.5
       ),
   # Make a function that can test to see if the data is consistent.
   # (optional)
   validity=function(object)
   {
       # Check to see if the activity level and length is
       # non-negative.
       # See the discussion on the @ notation in the text below.
       if(object@ActivityLevel<0.0) {
           return("Error: The activity level is negative")
       } else if (object@Length<0.0) {
           return("Error: The length is negative")
       }
       return(TRUE)
 }
   )

With this definition, there are two ways to create an Ant object: one is using the new command and the other is using the Ant generator, which is created after the successful execution of the setClass command. Note that in the following examples, the default values can be overridden when a new object is created:

> ant1 <- new("Ant")
> ant1
An object of class "Ant"
Slot "Length":
[1] 4
Slot "Position":
[1] 0 0 0
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5

We can specify the default values when creating a new object.

> ant2 <- new("Ant",Length=4.5)
> ant2
An object of class "Ant"
Slot "Length":
[1] 4.5
Slot "Position":
[1] 0 0 0
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5

The object can also be created using the generator that is defined when creating the class using the setClass command.

> ant3 <- Ant(Length=5.0,Position=c(3.0,2.0,1.0))
> ant3
An object of class "Ant"
Slot "Length":
[1] 5
Slot "Position":
[1] 3 2 1
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5
> class(ant3)
[1] "Ant"
attr(,"package")
[1] ".GlobalEnv"
> getClass(ant3)
An object of class "Ant"
Slot "Length":
[1] 5
Slot "Position":
[1] 3 2 1
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5

When the object is created and a validity function is defined, the validity function will determine whether the given initial values are consistent:

> ant4 <- Ant(Length=-1.0,Position=c(3.0,2.0,1.0))
Error in validObject(.Object) :
invalid class “Ant” object: Error: The length is negative
> ant4
Error: object 'ant4' not found

In the last steps, the attempted creation of ant4, an error message is displayed. The new variable, ant4, was not created. If you wish to test whether the object was created, you must be careful to ensure that the variable name used does not exist prior to the attempted creation of the new object. Also, the validity function is only executed when a request to create a new object is made. If you change the values of the data later, the validity function is not called.

Before we move on to discuss methods, we need to figure out how to get access to the data within an object. The syntax is different from other data structures, and we use @ to indicate that we want to access an element from within the object. This can be used to get a copy of the value or to set the value of an element:

> adomAnt <- Ant(Length=5.0,Position=c(-1.0,2.0,1.0))
> adomAnt@Length
[1] 5
> adomAnt@Position
[1] -1 2 1
> adomAnt@ActivityLevel = -5.0
> adomAnt@ActivityLevel
[1] -5

Note that in the preceding example, we set a value for the activity level that is not allowed according to the validity function. Since it was set after the object was created, no check is performed. The validity function is only executed during the creation of the object or if the validObject function is called.

One final note: it is generally a bad form to work directly with an element within an object, and a better practice is to create methods that obtain or change an individual element within an object. It is a best practice to be careful about the encapsulation of an object's slots. The R environment does not recognize the idea of private versus public data, and the onus is on the programmer to maintain discipline with respect to this important principle.

Defining methods for an S4 class

When a new class is defined, the data elements are defined, but the methods associated with the class are defined on a separate stage. Methods are implemented in a manner similar to the one used for S3 classes. A function is defined, and the way the function reacts depends on its arguments. If a method is used to change one of the data components of an object, then it must return a copy of the object, just as we saw with S3 classes.

The creation of new methods is discussed in two steps. We will first discuss how to define a method for a class where the method does not yet exist. Next, we will discuss some predefined methods that are available and how to extend them to accommodate a new class.

Defining new methods

The first step to create a new method is to reserve the name. Some functions are included by default, such as the initialize, print or show commands, and we will later see how to extend them. To reserve a new name, you must first use the setGeneric command. At the very least, you need to give this command the name of the function as a character string. As in the previous section, we will use more options as an attempt to practice safe programming.

The methods to be created are shown in preceding figure. There are a number of methods, but we will only define four here. All of the methods are accessors; they are used to either get or set values of the data components. We will only define the methods associated with the length slot in this text, and you can see the rest of the code in the examples available on the website. The other methods closely follow the code used for the length slot. There are two methods to set the activity level, and those codes are examined separately to provide an example of how a method can be overloaded.

First, we will define the methods to get and set the length. We will first create the method to get the length, as it is a little more straightforward. The first step is to tell R that a new function will be defined, and the name is reserved using the setGeneric command. The method that is called when an Ant object is passed to the command is defined using the setMethod command:

setGeneric(name="GetLength",
           def=function(antie)
           {
               standardGeneric("GetLength")
           }
           )
setMethod(f="GetLength",
         signature="Ant",
         definition=function(antie)
         {
             return(antie@Length)
         }
         )

Now that the GetLength function is defined, it can be used to get the length component for an Ant object:

> ant2 <- new("Ant",Length=4.5)
> GetLength(ant2)
[1] 4.5

The method to set the length is similar, but there is one difference. The method must return a copy of the object passed to it, and it requires an additional argument:

setGeneric(name="SetLength",
           def=function(antie,newLength)
           {
               standardGeneric("SetLength")
           }
           )
setMethod(f="SetLength",
         signature="Ant",
         definition=function(antie,newLength)
         {
             if(newLength>0.0) {
                 antie@Length = newLength
             } else {
                 warning("Error - invalid length passed");
             }
             return(antie)
          }
         )

When setting the length, the new object must be set using the object that is passed back from the function:

> ant2 <- new("Ant",Length=4.5)
> ant2@Length
[1] 4.5
> ant2 <- SetLength(ant2,6.25)
> ant2@Length
[1] 6.25

Polymorphism

The definition of S4 classes allows methods to be overloaded. That is, multiple functions that have the same name can be defined, and the function that is executed is determined by the arguments' types. We will now examine this idea in the context of defining the methods used to set the activity level in the Ant class.

Two or more functions can have the same name, but the types of the arguments passed to them differ. There are two methods to set the activity level. One takes a floating point number and sets the activity level based to the value passed to it. The other takes a logical value and sets the activity level to zero if the argument is FALSE; otherwise, it sets it to a default value.

The idea is to use the signature option in the setMethod command. It is set to a vector of class names, and the order of the class names is used to determine which function should be called for a given set of arguments. An important thing to note, though, is that the prototype defined in the setGeneric command defines the names of the arguments, and the argument names in both methods must be exactly the same and in the same order:

setGeneric(name="SetActivityLevel",
           def=function(antie,activity)
           {
               standardGeneric("SetActivityLevel")
           }
         )
setMethod(f="SetActivityLevel",
         signature=c("Ant","logical"),
         definition=function(antie,activity)
         {
             if(activity) {
                 antie@ActivityLevel = 0.1
             } else {
                 antie@ActivityLevel = 0.0
             }
             return(antie)
         }
         )
setMethod(f="SetActivityLevel",
         signature=c("Ant","numeric"),
         definition=function(antie,activity)
         {
             if(activity>=0.0) {
                 antie@ActivityLevel = activity
             } else {
                 warning("The activity level cannot be negative")
             }
             return(antie)
         }
         )

Once the two methods are defined, R will use the class names of the arguments to determine which function to call in a given context:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at AU $19.99/month. Cancel anytime
> ant2 <- SetActivityLevel(ant2,0.1)
> ant2@ActivityLevel
[1] 0.1
> ant2 <- SetActivityLevel(ant2,FALSE)
> ant2@ActivityLevel
[1] 0

There are two additional data types recognized by the signature option: ANY and missing. These can be used to match any data type or a missing value. Also note that we have left out the use of ellipses (…) for the arguments in the preceding examples. The argument must be the last argument and is used to indicate that any remaining parameters are passed as they appear in the original call to the function. Ellipses can make the use of the overloaded functions in a more flexible way than indicated. More information can be found using the help(dotsMethods) command.

Extending the existing methods

There are a number of generic functions defined in a basic R session, and we will examine how to extend an existing function. For example, the show command is a generic function whose behavior depends on the class name of the object passed to it. Since the function name is already reserved, the setGeneric command is not used to reserve the function name.

The show command is a standard example. The command takes an object and converts it to a character value to be displayed. The command defines how other commands print out and express an object. In the preceding example, a new class called coordinate is defined; this keeps track of two values, x and y, for a coordinate, and we will add one method to set the values of the coordinate:

# Define the base coordinates class.
Coordinate <- setClass(
   # Set the name of the class
   "Coordinate",
   # Name the data types (slots) that the class will track
   slots = c(
       x="numeric", # the x position
     y="numeric"   # the y position
       ),
   # Set the default values for the slots. (optional)
   prototype=list(
       x=0.0,
       y=0.0
       ),
   # Make a function that can test to see if the data
   # is consistent.
   # (optional)
   # This is not called if you have an initialize
   # function defined!
   validity=function(object)
   {
       # Check to see if the coordinate is outside of a circle of
       # radius 100
       print("Checking the validity of the point")
       if(object@x*object@x+object@y*object@y>100.0*100.0) {
       return(paste("Error: The point is too far ",
       "away from the origin."))       }
       return(TRUE)
   }
   )
# Add a method to set the value of a coordinate
setGeneric(name="SetPoint",
           def=function(coord,x,y)
           {
               standardGeneric("SetPoint")
           }
           )
setMethod(f="SetPoint",
         signature="Coordinate",
         def=function(coord,x,y)
         {
             print("Setting the point")
             coord@x = x
             coord@y = y
             return(coord)
         }
         )

We will now extend the show method so that it can properly react to a coordinate object. As it is reserved, we do not have to use the setGeneric command but can simply define it:

setMethod(f="show",
         signature="Coordinate",
         def=function(object)
         {
             cat("The coordinate is X: ",object@x," Y: ",object@y,"n")
         }
         )

As noted previously, the signature option must match the original definition of a function that you wish to extend. You can use the getMethod('show') command to examine the signature for the function. With the new method in place, the show command is used to convert a coordinate object to a string when it is printed:

> point <- Coordinate(x=1,y=5)
[1] "Checking the validity of the point"
> print(point)
The coordinate is X: 1 Y: 5
> point
The coordinate is X: 1 Y: 5

Another import predefined method is the initialize command. If the initialize command is created for a class, then it is called when a new object is created. That is, you can define an initialize function to act as a constructor. If an initialize function is defined for a class, the validator is not called. You have to manually call the validator using the validObject command. Also note that the prototype for the initialize command requires the name of the first argument to be an object, and the default values are given for the remaining arguments in case a new object is created without specifying any values for the slots:

setMethod(f="initialize",
         signature="Coordinate",
         def=function(.Object,x=0.0,y=0.0)
         {
             print("Checking the point")
             .Object = SetPoint(.Object,x,y)
             validObject(.Object) # you must explicitly call 
             # the inspector              return(.Object)          }          )

Now, when you create a new object, the new initialize function is called immediately:

> point <- Coordinate(x=2,y=3)
[1] "Checking the point"
[1] "Setting the point"
[1] "Checking the validity of the point"
> point
The coordinate is X: 2 Y: 3

Using the initialize and validity functions together can result in surprising code paths. This is especially true when inheriting from one class and calling the initialize function of a parent class from the child class. It is important to test codes to ensure that the code is executing in the order that you expect. Personally, I try to use either validator or constructor, but not both.

Inheritance

The Ant class discussed in the first section of this article provided an example of how to define a class and then define the methods associated with the class. We will now extend the class by creating new classes that inherit from the base class. The original Ant class is shown in the preceding figure, and now, we will propose four classes that inherit from the base class. Two new classes that inherit from Ant are the Male and Female classes. The Worker class inherits from the Female class, while the Soldier class inherits from the Worker class. The relationships are shown in the following figure. The code for all of the new classes is included in our example codes available at our website, but we will only focus on two of the new classes in the text to keep our discussion more focused.

introduction-s4-classes-img-1
Relationships between the classes that inherit from the base Ant class

When a new class is created, it can inherit from an existing class by setting the contains parameter. This can be set to a vector of classes for multiple inheritance. However, we will focus on single inheritance here to avoid discussing the complications associated with determining how R finds a method when there are collisions. Assuming that the Ant base class given in the first section has already been defined in the current session, the child classes can be defined. The details for the two classes, Female and Worker, are discussed here.

First, the FemaleAnt class is defined. It adds a new slot, Food, and inherits from the Ant class. Before defining the FemaleAnt class, we add a caveat about the Ant class. The base Ant class should have been a virtual class. We would not ordinarily create an object of the Ant class. We did not make it a virtual class in order to simplify our introduction. We are wiser now and wish to demonstrate how to define a virtual class. The FemaleAnt class will be a virtual class to demonstrate the idea. We will make it a virtual class by including the VIRTUAL character string in the contains parameter, and it will not be possible to create an object of the FemaleAnt class:

# Define the female ant class.
FemaleAnt <- setClass(
   # Set the name of the class
   "FemaleAnt",
   # Name the data types (slots) that the class will track
   slots = c(
       Food ="numeric"     # The number of food units carried
       ),
   # Set the default values for the slots. (optional)
   prototype=list(
       Food=0
       ),
   # Make a function that can test to see if the data is consistent.
   # (optional)
   # This is not called if you have an initialize function defined!
   validity=function(object)
   {
       print("Validity: FemaleAnt")
       # Check to see if the number of offspring is non-negative.
       if(object@Food<0) {
           return("Error: The number of food units is negative")
       }
       return(TRUE)
   },
   # This class inherits from the Ant class
   contains=c("Ant","VIRTUAL")
   )

Now, we will define a WorkerAnt class that inherits from the FemaleAnt class:

# Define the worker ant class.
WorkerAnt <- setClass(
   # Set the name of the class
   "WorkerAnt",
   # Name the data types (slots) that the class will track
   slots = c(
       Foraging ="logical",   # Whether or not the ant is actively
                               # looking for food
       Alarm = "logical"       # Whether or not the ant is actively
                               # announcing an alarm.
      
       ),
   # Set the default values for the slots. (optional)
   prototype=list(
       Foraging = FALSE,
       Alarm   = FALSE
       ),
   # Make a function that can test to see if the data is consistent.
   # (optional)
   # This is not called if you have an initialize function defined!
   validity=function(object)
   {
       print("Validity: WorkerAnt")
       return(TRUE)
   },
   # This class inherits from the FemaleAnt class
   contains="FemaleAnt"
   )

When a new worker is created, it inherits from the FemaleAnt class:

> worker <- WorkerAnt(Position=c(-1,3,5),Length=2.5)
> worker
An object of class "WorkerAnt"
Slot "Foraging":
[1] FALSE
Slot "Alarm":
[1] FALSE
Slot "Food":
[1] 0
Slot "Length":
[1] 2.5
Slot "Position":
[1] -1 3 5
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5
> worker <- SetLength(worker,3.5)
> GetLength(worker)
[1] 3.5

We have not defined the relevant methods in the preceding examples. The code is available in our set of examples, and we will not discuss most of it to keep this discussion more focused. We will examine the initialize method, though. The reason to do so is to explore the callNextMethod command. The callNextMethod command is used to request that R searches for and executes a method of the same name that is a member of a parent class.

We chose the initialize method because a common task is to build a chain of constructors that initialize the data associated for the class associated with each constructor. We have not yet created any of the initialize methods and start with the base Ant class:

setMethod(f="initialize",
         signature="Ant",
         def=function(.Object,Length=4,Position=c(0.0,0.0,0.0))
         {
             print("Ant initialize")
             .Object = SetLength(.Object,Length)
             .Object = SetPosition(.Object,Position)
             #validObject(.Object) # you must explicitly call the inspector
             return(.Object)
         }
         )

The constructor takes three arguments: the object itself (.Object), the length, and the position of the ant, and default values are given in case none are provided when a new object is created. The validObject command is commented out. You should try uncommenting the line and create new objects to see whether the validator can in turn call the initialize method. Another important feature is that the initialize method returns a copy of the object.

The initialize command is created for the FemaleAnt class, and the arguments to the initialize command should be respected when the request to callNextMethod for the next function is made:

setMethod(f="initialize",
         signature="FemaleAnt",
         def=function(.Object,Length=4,Position=c(0.0,0.0,0.0))
         {
             print("FemaleAnt initialize ")
             .Object <- callNextMethod(.Object,Length,Position)
             #validObject(.Object) # you must explicitly call
             #the inspector
             return(.Object)
         }
         )

The callNextMethod command is used to call the initialize method associated with the Ant class. The arguments are arranged to match the definition of the Ant class, and it returns a new copy of the current object.

Finally, the initialize function for the WorkerAnt class is created. It also makes use of callNextMethod to ensure that the method of the same name associated with the parent class is also called:

setMethod(f="initialize",
         signature="WorkerAnt",
         def=function(.Object,Length=4,Position=c(0.0,0.0,0.0))
         {
             print("WorkerAnt initialize")
            .Object <- callNextMethod(.Object,Length,Position)
             #validObject(.Object) # you must explicitly call the inspector
             return(.Object)
         }
         )

Now, when a new object of the WorkerAnt class is created, the initialize method associated with the WorkerAnt class is called, and each associated method for each parent class is called in turn:

> worker <- WorkerAnt(Position=c(-1,3,5),Length=2.5)
[1] "WorkerAnt initialize"
[1] "FemaleAnt initialize "
[1] "Ant initialize"

Miscellaneous notes

In the previous sections, we discussed how to create a new class as well as how to define a hierarchy of classes. We will now discuss four commands that are helpful when working with classes: the slotNames, getSlots, getClass, and slot commands. Each command is briefly discussed in turn, and it is assumed that the Ant, FemaleAnt, and WorkerAnt classes that are given in the previous section are defined in the current workspace.

The first command, the slotnames command, is used to list the data components of an object of some class. It returns the names of each component as a vector of characters:

> worker <- WorkerAnt(Position=c(1,2,3),Length=5.6)
> slotNames(worker)
[1] "Foraging"     "Alarm"         "Food"         "Length"      
[5] "Position"     "pA"           "pI"           "ActivityLevel"

The getSlots command is similar to the slotNames command. The difference is that the argument is a character variable which is the name of the class you want to investigate:

> getSlots("WorkerAnt")
     Foraging         Alarm         Food       Length     Position
   "logical"     "logical"     "numeric"     "numeric"     "numeric"
           pA           pI ActivityLevel
   "numeric"     "numeric"     "numeric"

The getClass command has two forms. If the argument is an object, the command will print out the details for the object. If the argument is a character string, then it will print out the details for the class whose name is the same as the argument:

> worker <- WorkerAnt(Position=c(1,2,3),Length=5.6)
> getClass(worker)
An object of class "WorkerAnt"
Slot "Foraging":
[1] FALSE
Slot "Alarm":
[1] FALSE
Slot "Food":
[1] 0
Slot "Length":
[1] 5.6
Slot "Position":
[1] 1 2 3
Slot "pA":
[1] 0.05
Slot "pI":
[1] 0.1
Slot "ActivityLevel":
[1] 0.5
> getClass("WorkerAnt")
Class "WorkerAnt" [in ".GlobalEnv"]
Slots:
                                                                          
Name:       Foraging         Alarm         Food       Length     Position
Class:       logical      logical       numeric       numeric       numeric
                                              
Name:             pA           pI ActivityLevel
Class:       numeric       numeric       numeric
Extends:
Class "FemaleAnt", directly
Class "Ant", by class "FemaleAnt", distance 2
Known Subclasses: "SoldierAnt"

Finally, we will examine the slot command. The slot command is used to retrieve the value of a slot for a given object based on the name of the slot:

> worker <- WorkerAnt(Position=c(1,2,3),Length=5.6)
> slot(worker,"Position")
[1] 1 2 3

Summary

We introduced the idea of an S4 class and provided several examples. The S4 class is constructed in at least two stages. The first stage is to define the name of the class and the associated data components. The methods associated with the class are then defined in a separate step.

In addition to defining a class and its method, the idea of inheritance was explored. A partial example was given in this article; it built on a base class defined in the first section of the article. Additionally, the method to call-associated methods in parent classes was also explored, and the example made use of the constructor (or initialize method) to demonstrate how to build a chain of constructors.

Finally, four useful commands were explained. The four commands offered different ways to get information about a class or about an object of a given class.

For more information, you can refer to Mobile Cellular Automata Models of Ant Behavior: Movement Activity of Leptothorax allardycei, Blaine J. Cole and David Cheshire, The American Naturalist.

Resources for Article:


Further resources on this subject: