Educational outcomes
Student:
- explains how to implement constructs such as class extents, extent persistence, complex attribute, optional attribute, mult-valued attribute, class attribute, derived attribute in natural language (understanding);
- identifies and corrects errors in the source code, which is an implementation of the above structure;
- lists constructs implemented in the source code (understanding);
- implements the above structures using the selected programming language (application);
- indicates functional differences between two selected programs based on code snippets source (analysis);
- creates a program that implements specific business requirements using the constructs learned from the lectures (synthesis);
Justification
Modern programming languages are mostly based on the object paradigm. Therefore, its practical understanding is a necessary basis for working in this profession. Moreover, programmers often make systems based on analytical materials. And these largely use object-oriented graphical notations (such as UML). During this and the next lessons, you will deepen your theoretical bond, but first of all you will learn how to implement different concepts from object-orientness. This will allow you to design and implement modern business applications.
Object and class
Object It is a being that has well defined boundaries and properties and is distinctive in the analyzed fragment of the problem domain.
The concept of the object is related to the problem of its identity. It is assumed that the object is recognized on the basis of existence rather than using some combination of its features. Examples of objects may be: a chair, a building, an invoice, a student, a person, etc.
It is worth noting that the above definition does not take into account the specificity of computer systems. In practice (eg. in the Java or C# language), the object has its own identifier (reference) - usually it is simply an address in computer memory.
Class is a named description of a group of objects that share the same set of properties (invariants).
According to this definition, a class is not a collection of objects (for that there is a separate concept). This is a very important statement, because it is often confused. In other words, the class is like a recipe for an object. It describes its properties (attributes) ,behavior (methods) and construction (in the context of programming languages). Class name should be a singular noun (eg. employee). You can use the plural form if a single instance of the class (object) describes many elements (eg Employees when we store information about their groups). It is also possible to use a few words if it has a business justification.
How it is related to popular object-oriented programming languages? In languages such as Java, MS C#, C++ classes occur in a manner consistent with the above definition.
Unfortunately, this does not apply to all concepts known from object-orientness (UML).
The following code in Java will create a new class whose objects will describe some of the movies.
public class Movies {
/* Body of the class: attributes, methods, etc. */
}
Abstract class
Abstract class is a class that can not have its own objects (direct instances).
This concept also occurs directly in popular programming languages. All we need to add is the abstract keyword.
public abstract class Movie {
/* Body of the class: attributes, methods, etc. */
}
We will return to this issue in case of the inheritance.
Class extent
Class extent is a set of currently existing objects (in other words, instances) of a given class.
Of course, in a working computer system there will be many class extents, approximately (see inheritance lesson) one for each existing business class, eg Movie, Actor, etc.
Unfortunately, we have come to a term that does not occur directly in popular programming languages. We mean the fact that a programmer does not have default access to all currently existing objects of a given class. So there is no special keyword for the language or any other construct that will make the relevant list available. We can implement class extent ourselves, using existing language constructs. Let us stress once again: what we do is a sort of a workaround, not a native solution. From the compiler point of view, there is no class extent, and only some constructs that the developer will treat in this way.
I think that a fairly obvious solution is to create a dedicated container that will store references to individual instances of the class (ie, objects). The open questions are:
- Where to hold this container?
- When and how to add objects to it?
For the first question (where to hold this container) we have two general approaches:
- Within the same business class.
- Using an external class, e.g. for the Movie class, its extent Movies.
Both approaches have their advantages and disadvantages:
- One could say that the first solution introduces to the business classes some technical elements,
- Someone else could criticize the second way because it multiplies beings: for each business class it creates the corresponding class that manages extent.
As you can see, there is not single perfect solution. For reasons that will become clear soon, despite all else the first way seems to be a better one.
Implementation of a class extent within the same class
To implement class extent in Java or C#, we need to create a container that will store references to objects. Because we want to include it in the business class and all of its objects must have access to it, we will use an attribute with the keyword static. In addition to the container, it is also worth adding some methods which will facilitate the addition or removal of objects.
public class Movie {
// Business part
private String title;
private float price;
private LocalDate additionDate; // requires Java 8+
public Movie() {
// Add to the extent
addMovie(this);
}
@Override
public String toString() {
return "Movie: " + title + ", id: " + super.toString();
}
private static List<Movie> extent = new ArrayList<>();
private static void addMovie(Movie movie) {
extent.add(movie);
}
private static void removeMovie(Movie movie) {
extent.remove(movie);
}
public static void showExtent() {
System.out.println("Extent of the class: " + Movie.class.getName());
for (Movie movie : extent) {
System.out.println(movie);
}
}
}
Some of the more interesting snippets (the following numbers in parentheses refer to the listing line):
Note that in line (20), we refer to the collection as List (an interface, not a specific class). Thanks to this, we can easily replace its specific implementation with eg ArrayList on any other class implementing this interface.
This is a fairly common technique that raises the level of abstraction and improves code flexibility. It's worth using it.
- (9). Let's get back to our second question: how to add objects to extent. The first solution to this problem is simply a manual call from the programmer, adding a newly created object to the container. Will this work? Of course - unless someone forgets to do it. Then some of the objects will be in the extent (because he/she remembered to call the method), and some of them will not. Can we do anything about it? We will use the constructor, and more specifically add the appropriate method call to the constructor. This approach ensures that each created object of this class will be placed in the container.
- (20). Here is the declaration of our container. We used the so-called a parameterized class that allows a particular type (and its sub-types) to be stored. We had to use the keyword
staticto access this item from all objects in this class. This implementation uses the List type, but you can use other types, such as ArrayList, depending on your needs. - (22, 26). Methods to add and remove items. Because the container on which they operate is declared as
static, then the methods must also be decorated with the keyword. - (30). An auxiliary method that allows you to display (to the console) objects in class extent. It is worth to pay attention to the "new" for loops. This makes it unnecessary to use iterators, which are less convenient to use.
A sample test code for this solution is listed below along with the result of its operation. The reader who runs this program will receive other numbers (they are addresses in memory where the objects are stored).
public static void main(String[] args) {
var movie1 = new Movie("Terminator 1", LocalDate.now(), 29.90f);
var movie2 = new Movie("Terminator 2", LocalDate.now(), 34.90f);
Movie.showExtent();
}
Extent of the class: mt.mas.Movie
Movie: Terminator 1, id: mt.mas.Movie@61a485d2
Movie: Terminator 2, id: mt.mas.Movie@1.8Me
Implementation of class extent using an external class
Another way to implement extent management is to create an additional "technical" class for each business class, such as Movies for the Movie business class or MovieExtent. With this approach, all extent functionality is placed in a separate class and does not "clutter" our business class. An additional potential benefit is the ability to manipulate many different extents for eg. the Movie class. Although the practical usefulness of this can be debatable.
public class Movie {
// Class body
}
public class MovieExtent {
// Extent implementation
/** The extent. */
private List<Movie> extent = new ArrayList<>();
public void addMovie(Movie movie) {
extent.add(movie);
}
public void removeMovie(Movie movie) {
extent.remove(movie);
}
public void showExtent() {
System.out.println("Extent of the class: " + Movie.class.getName());
for (Movie movie : extent) {
System.out.println(movie);
}
}
}
Some interesting snippets of the implementation:
- (1). A business class whose extent we want to manage.
- (10). A container that holds references to specific objects belonging to the extent. In contrast to the previous solution, this attribute is not marked as
static(although it may be). Thanks to that, creating more instances of theMovieExtentclass, we can create multiple extents for one class. - (12, 16). Methods for adding and removing objects to / from extent.
- (20). Auxiliary method to display extent.
Let's compare two implementations and let's see if there is something missing in the latter. Yes - there is no automatic addition to extent in the business class constructor. Is this oversight?,Of course not. With this implementation we do not know the extent management object, and therefore we can not write a direct reference. We can pass it, for example, as a parameter to the business object constructor - is it convenient? Probably not.
The intorduced approach in action. More interesting places:
- (4). We create a MovieExtent object that will manage the business class Movie.
- (6). We create a business object describing a single movie.
- (7). Manually adding a created object to class extent. Without this the new object will not be stored in the extent.
public static void main(String[] args) {
// Test: Implementacja ekstensji przy uzyciu klasy dodatkowej
var movieExtent = new MovieExtent();
var movie1 = new Movie("Terminator 1", LocalDate.now(), 29.90f);
movieExtent.addMovie(movie1);
var movie2 = new Movie("Terminator 2", LocalDate.now(), 34.90f);
movieExtent.addMovie(movie2);
movieExtent.showExtent();
}
As we mentioned before, the first approach (with the implementation of extent within the same business class) seems to be more convenient.
Simple attributes
Attributes are used to describe the properties of objects belonging to a certain class. Due to the different criteria they can be divided into several categories.
Simple attribute stores atomic values, e.g.: number, text, boolean, eg. salary, name, color name, weight.
This type of attribute occurs in all popular programming languages, the same way like in object-oriented UML. Listing presents an example of a price attribute for the Movie class.
public class Movie {
private float price;
}
Complex attributes
Complex attributes consists of simple attributes or other complex attributes, such as address (can include street, house number, city, zip code), position coordinates in 3D space (consist of values for x, y, z axis)
When creating a data model, think carefully about using a complex attribute or a separate class connected by association. The attribute will not be shared with other objects, i.e. each object will have its own copy, which makes it difficult to search. In the case of associations, the same value will be combined with many objects, which significantly simplifies the search.
The complex attribute is described using a dedicated class (eg, date). This class can be provided by developers of a given programming language, external library, or user-created (by a programmer). In effect:
- In the business class (eg Movie), we store a reference to its instance, not a (complex) value.
- As a result, we can share it, for example, another object can hold a reference to the same date. This is (somewhat) in contradiction to the "theoretical" semantics of a complex attribute that should not be shared. In C ++ the situation is a little different - you can store another class object as a "value".
public class Movie {
private LocalDate additionDate;
}
We may consider one more approach that involves direct placement of the content of the complex attribute in the class. For example, instead of a complex attribute, we can put simple attributes in the class: street, house number, apartment number, city and zip code. Sometimes such a solution can be more convenient than creating a separate, dedicated class.
Single and multivalued attributes
Single attribute has exactly one value, eg name.
Exactly the same semantics as in object-orientness. One attribute holds one value, such as a name (provided that a person has only one, if not, see next).
Multivalued attribute may have many values, eg. names or known foreign languages.
Many values for such an attribute are stored in a container or regular array. This first solution is preferred when we do not know how many of these elements we will need. The type of container you select may depend on how you work with that attribute, such as whether you add items more often, or rather read them, etc.
public class Person {
private ArrayList<String> names;
}
Theoretically, one more approach can be imagined, but it is not recommended and I would classify them as "tricks" (and, as you know, sooner or later they are the cause of a trouble). We can store multiple names, for example, in a single String, separated by commas. The level of comfort and safety with this type of "repetitive attribute" is very low.
Mandatory and optional attributes
Mandatory attribute must have value. The system should not allow you to create an object without that value, eg. a name in the Employee class.
Optional attribute may not have value. This is a situation that is quite correct from a business point of view, such as a maiden name in a worker class (men usually do not have a maiden name).
In this case, we have to individually analyze two types of attributes:
- Simple. Each simple attribute holds a value - it can not be empty. Even if we put zero, it is some kind of value - just 0.
- Complex. A complex attribute holds a reference to an object "which is its value.". Because it is a reference, it may be null, or "no value". For security (to get no exception), you should "manually" check that it is null.
From the above explanation, it is clear that complex attributes can be either required or optional. How about simple attributes - can we do soomething to store optional information such as salary? Of course we can - but it will be a little more complicated. The simple attribute is implemented as a special class that wraps a simple value. Thanks to this we will be able to substitute null, that is, information about the lack of value. There are already such wrapping classes in Java, such as Integer for int.
public class Employee {
private Double salary; // could be null (note the capitalized letter)
private int number; // can not be null
}
In addition to the discussed ability to remember missing information, the implementation of optional attributes should also take this fact into account during processing them. Instead of directly reading the value, first check if it exists, e.g.
if(salary != null) {
// calculate something
}
else {
// no value
}
Alternatively, you can use a dedicated construct called Optional (requires Java 8+). Note the sample usages in the getIncome() and toString() methods:
public class Employee {
// ...
private Optional<Double> extraBonus = Optional.empty(); // initialization without a value
public Optional<Double> getExtraBonus() {
return extraBonus;
}
public void setExtraBonus(Optional<Double> extraBonus) {
this.extraBonus = extraBonus;
}
public double getIncome() {
return getSalary() + getExtraBonus().orElse(0d);
}
@Override
public String toString() {
return String.format("Emp '%s', sal: %s, bonus: %s", getName(), getSalary(), getExtraBonus().isPresent() ? getExtraBonus().get() : "(no bonus)");
}
}
Object and class attributes
Attribute of an object can have its own value for each of the objects in the extent, eg. name, surname, date of birth.
The same semantics as in object-orientness (in UML). Each object within a particular class can store its own value.
public class Employee {
private string firstName;
private string lastName;
private BigDecimal salary;
}
Class attribute has the same value for all objects of a given class, eg. for an Employee class it could be: minimum age, maximum age.
The way of implementation depends on the approach to extensibility:
- Extent within the same class. We put class attributes in the same class with the
statickeyword, - Extent as an extra class. We implement class attributes in the external class (without the
statickeyword).
public class Employee {
private static int minAge = 18;
private static int maxAge = 68;
}
Specific and derived (calculated) attributes
Specific attribute is one whose value can not be determined in another (indirect) way and must be stored in the system, eg. date of birth.
public class Person {
private Date birthDate;
}
Derived attribute is one whose value can be determined by the state of the system (usually using other attributes), eg. gross price using net price.
In Java or C++ there is no native way to implement derived attributes. They are "simulated" using methods, which means that we really refer to methods in the code, not to attributes. In the case of orthodox encapsulation, this is not a big problem, since all attributes are still hidden and access is based on methods (for Java so called setter and getter ,that is, methods that allow you to change the value and read it). Special attribute treatment is implemented in the body of the method that reads (and sometimes also changes) its value.
final static double taxVat = 1.23; // VAT as a constant
/**
* Derived attribute implemented as a method.
* @return
*/
public double getPrice() {
return netPrice * taxVat;
}
It is also worth noting that in most cases, derived attributes are read-only (eg. age). Therefore, we only implement a getter. Sometimes, we store the calculated value (due to performance improvements) - of course this means data redundancy (with all the consequences).
Generally speaking, encapsulation consists of hiding information with particular emphasis on attributes. Access to such hidden attributes is achieved through methods. This gives us greater control and, for example, we can detect unauthorized changes of their value. Developers have long argued that this approach is useful (because it takes a bit more work to write these methods - although more and more modern programming environments can help with this). Orthogonal encapsulation means that the decision about whether to hide any element (attribute, method) depends only on the developer. According to the orthodox encapsulation, all attributes are hidden and can not be shared. Of course, C++, C# and Java are compatible with the orthogonal encapsulation philosophy.
Although this lecture is mainly about Java, it is impossible not to mention on this occasion the excellent mechanism implemented in MS C#: properties). We can define special "methods" that we use exactly as attributes. The example for the price attribute is shown below (C#):
private double netPrice;
private const double vatTax = 1.23;
// Derived attribute implemented as a property in C#.
private double price {
get {
return netPrice * vatTax;
}
set {
netPrice = value / vatTax;
}
}
I think that everyone agrees that practically every business application requires a class extent. But should we always implement it on the programming language side? It seems we do not, for example, when using a database or an object-relational mapper. We will return to this topic in subsequent lectures.