Programming in practice
This article makes an effort to shortly describe what object oriented means, and gives a short overview about the main features and aspects of the OO World. The purpose of the paper is to help the reader to build up the object oriented approach, which is very different from the most frequently taught procedural programming concept. It is not about OO design however. One cannot design on the OO ways without first understanding the OO theory itself. Having said that, the design considerations cannot be omitted altogether, so references might occur in the text. Yet, those are neither exhausting nor systematic descriptions of the OO design.
So what is OO anyways?
The OO, or better the object oriented methodology is just an approach. Nothing will be better just because it was designed or implemented on OO ways, neither will be any worse just because another approach was followed. Strictly speaking, the OO approach doesn't necessarily related only to programming, or software development; it is possible to use it when solving everyday's problems too.
The approach basically takes advantage of the fact that nothing exists all alone as a single, isolated entity. Anything that happens in the Universe is an interaction of entities. Therefore, the processes of the real world might be seen and described as the cooperation of distinct entities – that is, the objects. During this cooperation, the objects interact with each other, which might be seen also that they provide services to each other.
However, when trying to describe a process, not all of the interactions of the objects are important. If one tries to understand the tide, then the objects that interact are the Earth, the Water, the Moon, and the Sun (how to identify the important objects, that’s out of scope now, and will be discussed in another article). The interaction that should be taken into account is the Gravity. Other interactions, like the light that comes from the Sun or mirrored from the Moon, are uninteresting and not needed to understand the tide, and therefore must be disregarded. Similarly, there are other planets in the Solar System, but their gravitational effect is so low that it is safe to disregard it without endangering the main goal, understanding the tide.
The Object Oriented approach is nothing more than the adaptation of this view for developing softwares. It is possible to write distinct pieces of code, and these pieces will provide services to each other.
So What is An Object?
In order to understand better, let's see a real world example. Suppose we have a company, and our job is to organize the operations of that company. One possible way to do that is collecting all the important processes influencing the operation of the company (these processes are the "business processes"), then to check which parts of the company are involved in these processes, and how these departments and units communicate with each other and the outer world. It will turn out soon that the company might be separated into very independent parts - these parts are the objects, and in the real world, these parts are the smaller or larger business units.
There must be a unit producing the value (production department). If there is a product (which might be a service too), then it must be sold, and the commodities must be purchased - therefore the company should have a trading department. Trading nowadays is impossible without researching and finding the market, and advertising the product, so the company needs a marketing department. It is worth to keep track the financials and the economical trends, so a finance and accounting department must be in place. As already there are quite a few independent departments, there must be a unit that manages and controls the interactions of all these - that's the management and the controlling department.
The main units of our company. Note that not all units have connections to every other unit!
It must be noted even at this very early stage that not all real world entities are necessarily objects. When designing software, different levels of abstractions are needed. One cannot model the Universe, because the model would be just as big as the Universe itself. Therefore, during design, only the important aspects should be grabbed, and the unimportant ones must be disregarded (remember the tide, the gravity and the light). In case of our company, since we concentrate only on those parts that allow the company to produce value, therefore only those units are mentioned that has something to do with that main process. Sure there is a unit which helps to keep the facilities clean, but it has nothing to do with the goal, understanding how the company produces the value, therefore it is ignored.
Further, an object is not necessarily something which exist in the sense as one can touch it. The object might be an abstract entity as well, like a bank account, or an exam, if on the given abstraction level these entities have something to do and provide services to other objects.
So we have a company, seen as a group of cooperating objects. It is evident that each such object has one particular responsibility. That's a word to remember! Each and every object minds its own business only. The production department won't sell, because selling is not part of its own responsibility. In the OO world, we say that each and every object has its own responsibility, something this object must do. This responsibility must be exclusive; that is, the responsibilities of different objects won't overlap each other. It would be funny if any other save the production department would be responsible for the production of the product, and these two would generate the product in turns.
The responsibility should be as narrow as possible. However, it again depends on the level of the abstraction. In our company, the „finance and accounting department” has two responsibility reflected even by its name, but on this level of abstraction, that’s quite okay because all those sub-responsibilities are related to the financial wellness of the company, which is currently the responsibility we care for. Later, when breaking down the problem and the abstraction level changes, it might be that the department will be divided into sub-objects. In this case, the complex responsibility itself is taken into disjunct sets apart, and each unit carries the burden of one particular such set. In the example above, one unit will be responsible for the financials, and the other for the accounting. Another example might be the marketing department, which could be separated into a marketing and an advertisement department; first is responsible for the market research, marketing strategy, setting up the goals, marking the target groups, and so on, and the other is responsible for creating advertisement materials fitting best for the goals the marketing department sets up.
The "tragedy of commons" is well known in the OO world; in Hungary the common proverb is "a jointly owned horse has a roughed-up back", which says all. That's why it is advised that „to each of its own responsibility” way be followed. Just recall from the history what happened when two kings claimed authority above the same land – tragedies followed.
So the main idea behind the OO approach is that an application might be seen as a cooperation of distinct, independent units (objects) providing services to each other. Cooperation means that one object asks something or requests something from another object, and the latter responses or just does something in return. In short, one object sends a message, and the other object reacts.
Any object gives the same answer and provides the same service any time when it receives the very same message. Ideally it should be so in the real world too, but it is expected in the world of programs. However that's true only if every other important and relevant condition is unchanged (identical). When one asks the pizza boy to deliver, the relevant conditions like the mere existence of the pizza, the availability of a transportation unit (bike), and the city map to find the address should be the "same" in the sense that each must be in a proper state to help the main process, the delivery.
State of the Object
In the above example, those important and relevant conditions are all belong to the object providing the service. That’s because in reality, the customer doesn’t ask the pizza boy to deliver, he orders a pizza from the restaurant instead. It is the restaurant which delivers, by the means of the pizza boy, and the existence of the pizza, the pizza boy, the transportation unit, and the navigation unit are all within the confines of the restaurant. That restaurant has a state – it either has that pizza or hasn’t, and so on.
Let's dig a bit deeper into it. The existence of the object start of its creation. There is an initial state it is in, which is set during this creation time. Once it's borne, this inner state is already settled. As the time passes, this inner state might change. In other words, the object has its own history. When a car is assembled, it's fuel tank is empty. That's an initial state. When that car is sold, that fuel tank will be filled up to some extent. The actual fill level of the fuel tank is part of the car's inner state.
Black Box Approach
The state of the object is not something the outer world must be aware of. The customer doesn’t really care whether the restaurant has already cooked that pizza or not, or whether the pizza boy uses a bicycle or a bike when delivers. All he wants is a warm, freshly made pizza being right at his entrance door within a reasonable time. In the world of programming, this another important principle says the object hides its internals from the outer world.
Hiding the internals is also important because the objects might reorganize their internal operations easily, because no external entity depends on them. That's true as long as the interface shown towards the outer world doesn't change. The internal operation might be anything, the other objects won't ever be aware of the change. This principle of hiding the internals is the one which says the objects are black boxes, meaning that their internals are not visible for other objects.
The outer world has no way of knowing what the object's current internal state is (unless the object exposes this information). In case of the car, there's the fuel gauge that exposes the fuel level state. Sometimes it is possible to make assumptions about the state by observing the behavior of the object, but that doesn't necessarily gives correct information, and never should be attempted. For example, if the gear is set, the accelerator pedal is pushed, and the clutch is released, the car is supposed to start. If it stands still, we might suppose that there's no gas, but it might be also that the engine is failed, the clutch is failed, and a lot more. It all means, if the object doesn't expose an inner state, then that state is private, the outer world shouldn't even know of its existence, and even less making attempts to observe or change that state.
Behavior, Interface, and Contract
The objects are there to provide services to each other, therefore this black box should communicate with the outer world. It receives messages (requests) from other objects, and (according to its internal state) provides an answer (service) in return. This is the behavior of the object, and the full set of the messages/answers (requests/services) is the surface shown to the outer world - it is the interface of the object. When responding to a message (request), the object will do something - it might provide a message in return, but might start a particular process too. If the management asks the financial department, how big amount of cash our company sits on, the answer is a number. If the management orders the production department to start producing goods (and accompanied with that order comes the exact identification of the specific good, the required quantity, and the deadline), then the production department just starts to work on it.
This behavior therefore might be seen as a contract bound with the outer world. The object's part of the contract is that any incoming message/request will be answered/served on a predictable, well defined manner, in every particular exposed inner state of the object. Being a contract, the object can do anything during processing the requests, as long as the contract is kept. The behavior of the pizza restaurant is delivering pizzas when ordered. Noone cares whether a pizza boy bikes and carries the pizza, or it is beamed up to the Enterprise and back to the customer’s door, as long as the contract is kept – the pizza is delivered.
The black box, the responsibility, and the exposed interface
Now we are quite far in the OO world. We know that the object is an entity, which has responsibility (the tasks it should be able to do); has behavior (the interface shown towards the public); has inner state (information/data required to solve its tasks), and has an inner structure (the data structure). The inner states are available for other objects only through the object's interface; they might observe that state, or might initiate its change, but they are not allowed to have access to it directly.
The Four Features of the Object
No let's go back to our company above. In a complex system, containing many simple processes (it is very likely that the company suits to this description), these processes might be grouped logically. It might be observed that each such group of the cohesive processes are assigned to a distinct unit. In other words, that particular unit's responsibility is to execute those processes. As a matter of fact, when trying to discover an unknown system, the objects might be identified by finding logically coherent tasks grouped together - whoever or whatever is supposed to do those tasks, and it is likely that only one such person or unit will be found, is an object.
If the system has multiple states, then each state flag or metric is likely belongs to one such unit or person. One of the company's metric is for example the amount of the raw material on stock. The current amount is indifferent for the production unit, supposing the supply of the raw material doesn't stop - however, when the amount sinks under a specific level, it should trigger an action of the trading department. It might make sense to say that the stock level, and therefore the stock itself, is a property of the trading department. However, in this case we would assign a responsibility to the trading department which responsibility shouldn't be there. So we need a subunit, the Store department. The stock level as a metric will be the state of the store, and other departments might ask the store department to give information about the level, release materials from the store, and so on. For example, the trading department might ask the stock department to send an alarm as soon as the level of a raw material sinks below a specific amount. Please note that the state (metric), the stock level, and the behavior that changes that state (adding more supply to, or removing supply from the store) are done by the same unit (object). Closing together the states and the processes dealing with those states is called encapsulation.
In other words, the object stores the data confidentially. The outer world doesn't see this data, and doesn't know anything about the data structure. That's the business of the object only. The same object provides all those operations that might be executed upon that data, and the outer world might use that data only through these operations. These operations are available on the interface of the object.
Again another important concept is here. "The data structure is a secret", the outer world doesn't know about it. The object is an entity, which differs from the other entities by some important features. Deciding which is important always depends on the task of the problem domain. If the task is to play with a three-years old by using a ball, then it is important that the ball be round, elastic (blisters), and lightweight. No need for a more specific definition. But if the task is to provide the ball for a international championship of basketball, then I have to abide all the rules settled in the rule book of the International Basketball Federation which determines the acceptable weight range, diameter, and so on.
So there are important data that features the object, and there are unimportant data which is just an add-on. In case of car, the brand and the similar information determines the object (those are the essential data), while some other data like the fuel amount are not determining it (although they might be important data too).
Now back to the original thread. The objects store some kind of data, but the internal structure of the data is none of the business of the public. It is known that the library object has books - but it is unknown whether those books are stored in cardboard boxes, piles, or shelves.
If there's a man with some money in his pocket, I might ask him to let me know the amount, and even give an attempt to change that amount (giving him money or asking him to loan). That's more or less accepted, but just putting a hand into his pocket and counting his money is simply not accepted, as it is highly reprehensible to put some banknotes into his pocket or picking out some. The interface of course might be restricted - the man will answer to the "how much money you have" question only after proper authentication and authorization; he will answer to his wife (telling the truth or not, that's another issue but we're not going to deal with that now), but will refuse to answer to a foreigner.
Generalization and Inheritance
Another concept that should be understood is the generalization. Strictly speaking, each brand makes different cars. Even within the brand, the several models are all different. At the very beginning of the mobilization age, that was absolutely true, each brand and model had its own "user interface". Even in these very early days however, the potential customers wanted standardization of some features. They didn't want to relearn how to drive a car whenever they change to another model. Today, that interface is quite standard, the main controls are handled almost exactly the same way. Therefore, one has to learn drive the car, then will own a Mercedes, a Volkswagen, a Citroën, or a Honda. Even if one learns to drive a Honda, he will be able to drive any other car. When applying for the driving license, the authority accepts this fact - there's no different Honda license and Mercedes license. Therefore, we have in general the car (universal object), which has some special cases (the models of the brands). Those models inherit their important behavior from the car.
So going on this way, we have the models, and in general we have the car, which determines the important behavior of the models. That's generalization. But it is possible to go on the other way, saying that we have the car, and the models inherit their behavior from the more broad term. Therefore, generalization and inheritance are the two names of the same thing, all that differs is how we interpret it (which is determined by the domain we're moving in).
In most of the cases both are true. From the university's point of view, there are students with a specific behavior (they register, pay tuition fee, visiting lectures, and so on), and the university narrows this general term when it is needed (there are students attending to daily courses, there are students attending to remote lessons or video lessons. There are students paying the tuition fee by themselves, and there are others with fellowships. From the students point of view however, there are groupmates visiting more or less the same courses, there are students going to acquire the same title, and so on. He recognizes that the general "student" term exists, but for him, his special groups are that count. Therefore, the university sees the same term from the broad towards the narrower, while the student goes from the special towards the more broad.
But it's all one. What is important, and that should be remembered, that there are general objects, and they might have special cases. When it is a must to deal with this feature, it is also important to know that the special case must be the "is a" relationship with the general term. The Volkswagen Golf is a car. Anything the car can do, the special case must be able to do too. All behavior of the general object is automatically becomes the behavior of the more specific child object. That's what inheritance really means.
It might be seen also that the special object must be able to be put in place of the more general one (replaceability). The special object must be able to provide all the services its parent can provide. The sandwitch is a food, the roasted chicken breast is a food, the crescent roll is a food. But a sandwitch, created from crescent roll and roasted chicken breast is neither a roasted chicken breast, nor a crescent roll. Similarly, if the world of geometry is discovered, the square is not a special case of the rectangle - it cannot execute the stretching along one axis operation without losing it's square status. Similarly, the rectangle is not a tricky square, because the important state that makes the square to be a square (namely the all sizes are equal) is not met. For a mathematician, the square is a rectangle where the shorter side is equal to the longer side (that is, for him it is a special rectangle) - for a software engineer however, it is not the case, unless the prohibiting operations are not needed. (Never forget, the software is an abstraction, no need to model the whole Universe, only the data and behavior of the business domain is needed.)
The fourth and last feature might be understood through inheritance best. It is not true, not even in the real world, that the descendant object inherits all of the behavior of the parent without any change. See that car again. Today, all of the models have an operation called "refueling". However, that operation might differ, depending on one of the main features of the car (that's an important or determining status information), which is the fuel to use. One might use gasoline, one might use diesel, one might use gas, or in some cases, refueling might mean charging batteries. Therefore, it is allowed for the descendant to change what really happens when the operation is called, providing it serves the same purpose. In case of refueling, the purpose is to prepare the car for a trip.
But there's even more behind this concept. It is also possible, that the same object might do an operation in more than just one way. For example, there are special cars that might operate equally by using gasoline, diesel, or kerosene. In this case, "refueling" is actually three operation - refueling with gasoline, refueling with diesel, or refueling with kerosene. That's what we call polymorphism.
Let's see another example. Suppose we have a dancer. An expected behavior is, that whenever a specific music sounds, he must dance. The dancing style must fit for the music. Therefore, the real message is not that "the music sounded", the real message is that "rock music sounded", "samba music sounded", and so on. So the dancer have many operations, like danceRock, danceSamba, and so on. It's again only depends how we are going to model the real world. If there's just one operation, and its input parameter is the actual music sounded, then the dancer will decide which style fits, and will execute the operation accordingly. But if there are different operations for dancing rock, dancing samba and so on, then it's not the dancers responsibility to decide what style should be followed - the outer world will tell him that. In the first case, we have a general operation with a general parameter (dance for music), and the operation will be executed according the actual specific type of the general term. In the second case, we have very specific operations, which must receive the compatible music style, otherwise the resulted behavior will be odd.
So, What's So Good About OO?
Well, it's not the OO theory per se, but the four aforementioned concepts (encapsulation, information hiding, generalization/specialization and inheritance, and polymorphism) make this approach very useful. In a system containing huge number of objects, any of those objects could be replaced by another object, providing the replacement implements the very same interface towards the outer world, because the internals of the original object are absolutely unseenable for the public. In this world, the proverb "if it looks like a dog, and behaves like a dog, then it is a dog" is absolutely true. This will be important when it comes to later improvements, performance tuning, and so on, because the objects performing poorly might be replaced by a better one easily.
Due to the fact that the objects see each other only by their interface, therefore each and every object might be developed by different people, who don't know about each other at all. All they have to obey is the interface definition, and then their work is easily integrated into the full. No common knowledge share is necessary within the development group any more (providing the specification and design went well). It is likely that all that means shorter deadlines, and more effective work. That's all because of information hiding and encapsulation.
The methodology might be used in an OO environment mainly; other environment won't prohibit the application of these concepts, but the OO environment enforces rules aiming to the strict obedience of the theory and concepts. Unfortunately, even the OO environment won't prohibit the misuse of the possibilities (in a Java environment, one might write all the methods in one single class, and might use the structural programming theory within that class if one really wants).
Another useful aspect is the reusability through inheritance. One might extend (or modify) the behavior of an existing class without the need of having its source code. Further, if I have a properly written application, and one of the classes used there might be used providing the same functionality for another application, then one might easily just adopt that particular class, without the need of checking whether there are any side effects that class does.
Polymorphism is a very useful feature when it comes the usability of the object. By polymorphism, the interface shown towards the outer world might be more simple, containing less operations - and the object will decide how to respond when a specific operation is called. Trying to be simple and easy is a very useful asset, because it shortens the time of understanding the problem and developing the solution, plus lessens the number of possible bugs too. Further, it is easy to extend the general behavior with more specifics - adding a new music style won't be done by introducing a new operation, instead the specific music type will initiate the use of the newly added dance.
So I Know What Object is - but What is This Class?
So far, the meaning of the object was introduced, and many of the related and relevant aspects were identified, but one very important feature was omitted (or better, it was silently implied). So go a bit more formally, the object has four main features:
Let's see the car again. Each and every representative of one specific brand and model could be used the same way, for the same message will respond the same way (it's behavior is determined by the brand and model). It's capabilities, like how much fuel might be fueled, how many persons might be carried, are again determined by the brand and model (so the structure is determined). The current, actual state of these capabilities (how much fuel we have in the tank, etc.) usually differs (the state usually differs). But even if the state is the very same in two of those cars, they are still independent objects, each has its own place in the space. So they are unique.
The first three concepts were mentioned above, but the fourth was hidden. As it was said in the previous paragraph, if two objects have the same type (both are the same brand and model), which means their capabilities, operations and structure are the very same, they are still distinct and independent of each other. If I have two identical cars in the same state, I might opt to use one or the other, but whichever I use, the other still remains there; the state of the selected one will change during the usage, but the state of the other won't.
Now if we would go to the marine terms, then we shouldn't use the term "model" any more. Instead, the navy uses the term "class". For example, they know the "Los Angeles class submarine", which is a specific kind of the many submarines. In many cases, the name of the class is derived from the name of the first specimen. So basically, the class for them means a type, a kind.
In the world of the programming, our variables have types. That's important, because ultimately the computer stores these variables in the memory, where they appear as a sequence of bits. The same sequence will mean different things, depending on the type of the variable stored there.
In the OO world, the class is a type, which identifies the blueprint of an object. It is this blueprint that helps us to generate the objects, each such object is an instance of the class. Once it is created, that instance became unique - no changes of other instances will have any effect on another instance. So the class is a type notation.
Just recall our company above. It is possible that two distinct production departments coexist. That is, we have two production objects. But they are not different kind of objects, they are two different instances of the same kind (two instances of the same class).
In the real world, the broad terms might be the classes, and the specific terms are the objects. Whenever I say "child", it is likely that I refer to a kid in general, so its the class. But I might refer one specific child too - if I say "I have a daughter, her name is Renate", then I refer to one specific object. The type of this „object” is child - we all know what to expect from a child, what kind of "operations" she has, and so on.
When I refer to the "Citroën C4 Picasso", then it's a broad term - that is, a class. This class has instances, the particular cars of this kind, purchased by some entities. When creating the instances, the factory uses the plans of the model as a blueprint. In other words, the class is the blueprint, and the instantiation (creating one particular entity, that is the object, according to the blueprint, that is the class) is the process of producing. So one might think on the class as a object factory, with the sole responsibility and capability of producing instances of that particular class (objects of that kind), which produced instances have a well-defined state.
Saving some prototypes, there are many instances of each car class on the world. Each such instance is an object, which has its own state (attributes). These attributes describe it's optional capabilities, and the actual amount of other possibilities (e.g. the amount of fuel). Sometimes it is very important to differentiate between the particular instances (when one is going to drive, one must first decide which one he wants to sit in). In other cases it is not at all important - when I decide that I'm going to buy an instance, I don't know which one it will be, I just go and check many instances, and will select one. But when I tell it to others what I am going to do, all I just say "I am going to buy a Citroën C4 Picasso". At that time, I don't know which one it will be, I just know that I want one such car.
One more note by the end. There are some implied stereotypes, known by both the object and the other object sending messages to. These implied stereotypes should be kept always. If these stereotypes are broken, then the message will fail. For example, if one has a trained dog, one might order "sit down" in vain, if the implied stereotype, that the dog was trained by using the German language, is broken. One should have had said "platze", which means the same but obeys the implied stereotype. When designing softwares, it is a very good practice to eliminate these implied stereotypes as much as possible.
A Few More Words about Inheritance
In many cases, during OO programming we're talking about inheritance. That's because usually we write descendant classes, to modify or extend the behavior of an existing one. Therefore, usually the parent class is already exist, and the programmer creates the descendant class, which inherits the behavior of the parent.
As it was said, the descendant is a special case of the ancestor, and wherever the ancestor might appear, the descendant might be used too. The opposite is not true - the ancestor cannot necessarily replace the descendant - after all, the descendant exists because it either alters some behavior of the ancestor, or extends its capabilities. Therefore, when the ancestor behaves on its original manner, it replaces the descendant badly.
Inheritance should be used during the design phase, if two very important conditions are met.
If the two conditions above are not met, then it is advisable to use a different technique instead of inheritance. Recall the square and rectangle example given above - the square might inherit from the rectangle only if some of the operations of the rectangle are not needed in our problem domain.
So the class, as the blueprint of a particular entity of the OO world was introduced above. It is clear by now that by using this blueprint, one might create instances of the class - these instances are the objects. The four major feature of the object (behavior, structure, state, and uniqueness) was also introduced. It was stated that the objects encapsulate the data and the operations upon that data, and they hide all the details of this (so they behave as black boxes), and they provide only their interface towards the outer world. It was further stated that each object must have its own responsibility, and that responsibility should be very simple and straightforward. It was shown that the objects might be identified by finding the responsibilities. It was recognized that one of the huge advantage of the OO theory is to make the reuse of the classes very easy through inheritance. The inheritance was also described as a process during that the descendant might modify or extend the original behavior of the ancestor. Finally, it was recognized that polymorphism simplifies the usage of the objects.
Now if all these terms of the previous paragraph are clear and understood, then the OO theory is understood. Next step is to practice this theory by designing classes. It is a surprise that although the OO theory is so close to the behavior of the real world, yet it takes a relatively long time to sharpen the skills necessary to use it effectively.
CímkékAlapok , Basics , Java , ObjectOriented , OO
Eddig 1 komment érkezett
A szövegben nem lehet HTML-t használni, a linkeket pedig automatikusan aláhúzzuk. Az email cím megadása kötelezo, de az oldalon nem jelenik meg. Ha van freeblogos felhasználóneved, itt bejelentkezhetsz.
Az IP címedet megjegyezzük, de ezt csak a komment spam jellegének vizsgálatához használjuk fel.