• Nem Talált Eredményt

The useful solutions of the OOP

In document Programming Technologies (Pldal 10-14)

2. Principles of Object-oriented programming

2.5. The useful solutions of the OOP

We would think, of the above mentioned three principles, inheritance is the strongest, as it allows the simple recycling of the parent‟s code. Perhaps, this is what made OOP so popular, but OOP‟s strength lies in the following techniques:

• Automatic Garbage Collection,

• The Field, as a local-global variable,

• The use of polymorphism to substitute classes,

• Decreasing coupling by object linking.

2.5.1. Automatic garbage collection

Automatic garbage collection carries off the burden of releasing used memory (every „new‟ command uses memory) from the programmer. By the programmer this step (releasing memory)

• - can be forgotten,

• - can be done in an incorrect way (release too early, for example).

As it is known, what can go wrong; will go wrong, especially when the programmers are pushed. If this task can be automatically done by the kernel, it will reduce both the development and the testing time. In the same time, this is not an OOP specific ability.

2.5.2. The field, as a local-global variable

The field, as a local-global variable is a very useful innovation. It is know that many imperative languages have global variables. These allow the development of faster and shorter code, as there is no need to assign a global variable as a parameter. At the same time, the use of global variables has a side-effect.

If a subroutine (function, procedure or method) changes its environment, we call it a side-effect, namely:

• - changes a global variable,

• - writes to an output (screen, printer, output port),

• - writes to a file.

By the use of the side-effect, we can quicken the run of the program, but it results in errors that are difficult to find, as the error may affect a line of the code which is far from the place of the change. To find such error, the tracking of the new function is not enough. It‟s often a must to examine the whole source code, which is a time-consuming task. This is why it‟s not advisable to use a side-effect, a global variable.

Yet, the use of global variables quickens the program and results in a shorter, more elegant source code. So it‟d be nice to have global variables or rather it wouldn‟t be. The field is just like that, as it is global inside the class, but unavailable from outside. With the use of the fields we can trigger a side-effect, but this is local inside the class, so the errors that are the results of the side-effect are easier to find.

To tell the truth, we can make a totally global variable too. A public, class-level field can be written or read from anywhere, so such a field is global. Luckily, because of encapsulation, we feel the public fields unnatural, so no one uses global variables on OOP languages.

2.5.3. The use of polymorphism for class substitution

Polymorphism ensures the flexibility of our code. While inheritance results in quite rigid structures, polymorphism serves flexibility. This is based on the fact, that an instance of the child‟s class can be used where we are expecting a parent class type parameter. This is the essence of polymorphism.

For example, we can easily create a pipe factory class. The exact type of the pipe we are producing depends only on the instancing of the Corncob pipe‟s or the Calabash pipe‟s child.

Now, where is polymorphism, weren‟t we talking about inheritance heretofore? The observation is right, as there is no polymorphism without inheritance. The child class can be substituted for the parent‟s place. The tone is on substitution. The program‟s functioning depends on which child do we substitute. We thank this substitution to polymorphism, which is not by all means accessible via inheritance, but by implementing an interface. When do we substitute a class for another? If this class:

• - is the child of the other class,

• - implements the expected interface,

• - has all the methods that we want to call (only in the case of weak typing languages).

Where do we have a chance for substitution?

• - parameter passing (we are expecting a parent class instance, but receiving a child),

• - instancing (the reference is parent class type, but points to a child instance),

• - Responsibility injection (we are receiving an object from outside and we only know its surface).

We will see that all design patterns are based on the possibility of substitution.

2.5.4. The decreasing of coupling by object-compounding

By coupling we mean that to what extent does a class (or some other module) is based on the other classes.

Coupling is usually interpreted as the opposite of cohesion. Low level compounding results in high level cohesion, there and back. The extent of compounding is measured - based on the work Larry Constantine and his group - as follows:

Definition: In OOP, coupling is the measure of strength of a connection between a class and the other classes.

The extent of coupling, between two classes, like „A‟ and „B‟, is growing, if:

• - „A‟ has a field with the type of „B‟.

• - „A‟ calls any method of „B‟.

• - „A‟ has a method with a returning type of „B‟.

• - „A‟ is a descendant of „B‟, or implements „B‟.

The levels of coupling (from the strongest to the weakest):

• - tightly coupled

• - loosely coupled

• - layer

Strong coupling means strong dependence too. We differentiate the following kinds of dependencies:

• Dependency on hardware and software environment: If our program depends on a given hardware or software (Operating System in the most cases), we can use its special abilities and properties, so our program will be difficult or impossible to port to another environment. One great solution for this is the use of virtual machines. We compile our source code to the commands of a virtual machine. If the virtual machine runs on a given operating system, on a given hardware, than our program will run too.

• Implementation dependency: A class depends on the implementation of another class, so if we change one of the classes and have to change the other one to, then we are talking about implementation dependency. This is a kind of environmental dependency, as a class depends on one or more classes in its environment, but the environment here is the program‟s source code. If we depend on the surface of another class, so it doesn‟t matter how we implement the methods of another class till they give proper solution, than we can‟t talk about implementation dependency. We will deal with this dependency in detail later.

• Algorithmic dependency: We talk about algorithmic dependency if the fine tuning of algorithms is cumbersome. It often happens that we need to make one part of a program faster, like using quick sort instead of bubble sort. For example, when we are demonstrating the process of the sorting, than it‟s difficult to change from one sorting to another.

Of the three dependencies, we only deal with the implementation dependency, but with that one, we deal in detail. We already mentioned that inheritance causes implementation dependency. Let‟s see an example of this in Java. The task is to expand the inbuilt HashSet class by counting the inserted elements.

import java.util.*; number of inserted elements that have been added to the hash set. We use the addCount field, which is zero at the beginning. There are two methods we can use to add elements to the set: the „add‟ and the „addAll‟, so we overwrite these. The „add‟ increases the „addCount‟ by one and calls the parent‟s „add‟ method, as that is the one to know how to solve this task, we‟re just sitting on the solution. The „addAll‟ works similar, but in that case, we add more elements in the same time to the list, so the value of „addCount‟ is increased by the number of the elements.

This exercise would have been done in the same way by everybody, as inheritance is the easiest way to recycle the code. But there is a problem. This does not work properly!

import java.util.*;

public class Main {

public static void main(String[] args){ MyHashSet via the „addAll‟ method. Then we write how many members have we added to the set. We expect the program to write three, but it writes six instead.

What have happened? We didn‟t know that in the parent (in the HashSet class), the „addAll‟ method is realized by a loop that calls the „add‟ method to get the members When we called the child‟s „addAll‟ method; it added three to the „addCount‟ and called the parent‟s „addAll‟ method. This invited the „add‟ method thrice. Because of late binding, it called the child‟s add method instead of the parent‟s, which increased the value of „addCount‟

in every step. This is how we got the six above. So we seriously caught on the implementation dependency caused by inheritance.

The above code can be easily corrected by only increasing the value of „addCount‟ in the add method.

import java.util.*;

When we are coding the child class, we need to know how the parent is implemented or we will face similar, not easily understood problems. In the same time, if we exploit how the parent is implemented, than the parent‟s change may result that the child needs to change to. And this is an implementation dependency!

How can we avoid this? The solution is to use object-coupling instead of inheritance. When class A has a field with B class type, we say that we are using object-coupling.

Object-coupling can always substitute inheritance, as the two highly simplified programs below do the same:

class A

Console.Write("hello");

“a” that is an object-coupling, and writes “hello”.

Object-coupling is quite flexible as it happens in runtime, unlike inheritance which is already known while compiling. Now, inheritance is easier to take in, understand and explain. So object-coupling, that ensures lesser connectivity, lesser implementation dependency and more flexible code, is only used when we have gathered sufficient programming experience.

During object-coupling, when we make a method, that‟s substantive part is to call one of its methods through the reference that establishes the coupling, we say that we delegate the responsibility to the embedded object. In the above example, m2 is one such method, as it does nothing else but calls method m1. One form of responsibility delegation in .NET is the callback mechanism.

In object-coupling, it is a question how do we get the object operating in the coupling. In the above example, we created our own instance. We will deal with this question later, under the topic of responsibility injection.

Later we will see, although inheritance can always be substituted with object-coupling, it is impractical to do it every time, as there is no polymorphism without inheritance. And it is impossible to write flexible code without polymorphism.

In document Programming Technologies (Pldal 10-14)