A Systematic Approach to Write Better Code With OOP Concepts
This real-world approach to applying OOP will walk you through important concepts like encapsulation, inheritance, and access modifiers in a new light.
Join the DZone community and get the full member experience.
Join For Freehaaaah another article on object-oriented programming. you might be thinking that i will be discussing the same oop definitions with boring examples. the same definitions and examples that you have studied before, maybe in your class or when preparing for a job interview. but today i will not rephrase those disjointed definitions but give you a simple layout that will make it easy for you to understand oop concepts.
oop is easy to understand. we often learn it with the classic metaphors like cat, duck, animal, etc., but sometimes, it is hard to swallow these metaphors, and most oop material is filled with them. there is nothing wrong with these metaphors and examples when introducing oop concepts to a beginner programmer, but the learning materials never progress in the quality of oop examples. they start with metaphors and end at metaphors.
well, i also struggled with oop concepts during my undergrad studies. i understand oop concepts, but when faced with writing actual code, i stalled and could not think of where to start. at one time, i thought that i should stop learning oop, but the world around me kept telling me the importance of oop .
if you combine all embedded developers and low-level api developers, then that will only make 10% of all the developers in the universe. the remaining 90% of developers are programming in oop-based programming languages.
besides, object-oriented programming skills will be with you for life. on the contrary, if you are tied to a specific framework for two, then in three or five years, you will be in the rabbit hole. what happens if that framework is replaced by another optimized framework — you will have to start from scratch.
but if you know object-oriented concepts and have applied them firmly in your work, then you have that experience for life. you can always get leverage out of that object-oriented programming experience.
also, i don't want to be the kind of developer who writes only glue code and becomes just an api consumer. i want to learn skills that i can use to produce frameworks that are used by others.
so how did i grasp oop concepts? a different mindset. over the past 10 years of programming experience, i have developed a mindset towards oop, and i will present that mindset in the following template or layout. this is a systematic approach that i will use if i introduce oop concepts to a new student.
- context : previously, people were tired of procedural programming because the information was available to all.
- so, they developed a method to hide information called encapsulation.
- but they needed a way to communicate besides hiding, so they invented communication methods: association and inheritance.
- meanwhile, communication came with a dependency problem. the solution is: abstraction and polymorphism.
- design patterns were discovered to decrease dependency issues. for design patterns, i have another article here.
context of oop: information is everywhere
well, after reading the steve jobs biography, i understood the evolution of computers, operating systems, and graphical user interface. all of this helped me understand modern technologies. therefore, it is necessary to understand a little bit of background.
where does oop fit in the larger context of software development? is it a process? is it an architecture? something else? well, i am feeling stupid right now by asking these questions, but there are people who ask these types of questions and they are still confused. object-oriented programming is a development methodology.
developers, especially young developers, know about oop only as a development methodology. if they read a little bit of history, then they will know that there is another development methodology that people have abandoned (or tried to abandon) — procedural development.
procedural programming languages like cobol, fortran, and pascal were the default choices for our programming ancestors, and before that, assembly language programming was a must for every computer scientist.
in procedural programming, we divide a large set of instructions into procedures — like functions in c#. before procedural programming, there was another paradigm, monolithic programming. in monolithic programming, everything is written in one large method, and all the variables are defined at the topmost location. but as program sizes grew, managing monolithic and procedural code became difficult so oop was invented.
encapsulation: hiding the information
let me share a personal story. one night, there was a call from my boss. i had to update the demo for our product and show a couple of parameters on the screen. demos in trade shows are important for marketing. usually, it is a high-pressure situation. every engineer was counting on me.
the update was small. i had to add two text fields to the display. i created a class, and since both fields were related, i put them in a single class and used the object of that class. i did this to make the code clean. in such stressful situations, you cannot afford to make mistakes and, fortunately, the update worked flawlessly.
one of the engineers praised me in such a way that i still remember him after so many years. he told me that “wow, you created a new variable!” (he was not a software engineer, so we can forgive him.)
first of all, i don’t want my readers to be like that, i.e treating an object like a variable. secondly, you can see how i used encapsulation to save the day. you merge one or more pieces of data and/or one or more methods into a single entity — encapsulation.
encapsulation is also defined as the mechanism for data hiding/data protection/data security. data hiding is achieved via access modifiers . if a data member is defined using the 'private' access modifier, it will be accessible within the boundary of the class. meanwhile, if an access modifier is public, it will be accessible by everybody else. but this definition needs clarification. let's see if it is true for the following example:
example 1:
class storeitem
{
private int itemprice;
public int getprice()
{
return itemprice;
}
public void setprice(int price)
{
itemprice = price;
}
}
here, i define a private data member and give its access to everybody else via a public method. is the item price hidden? let's see another example:
example 2:
class storeitem
{
public int itemprice;
}
everybody can access the item price, since it is public now. so what is the difference in the above two code examples? this is not data hiding, and if someone told you that the first example is data hiding, then you should run away.
in my opinion, data hiding is controlled access to internal data via public methods. you are hiding data if the outside user does not know how many variables are in your class but still get the desired result from your class via public methods.
example 3:
class process {
private int ingredient1;
private int ingredient2;
private int ingredientfromoutside1;
private int ingredientfromoutside2;
public int getfinalproduct() {
int result = getlocalproduct() + getproductfromoutside();
return result;
}
private int getlocalproduct() {
return ingredient1 + ingredient2;
}
private int getproductfromoutside() {
return ingrdientfromouside1 + ingredientfromoutside2;
}
}
in example 3, there are several variables, and i hid them from the outside world and gave only the controlled access to them. outside the class, no one knows how many data members or methods are there. they just know about one public method named getfinalpoduct and nothing else. in this way, we hide data from outside world using encapsulation.
accessors
in example 1, i have just exposed the instance variable — the property type. there are several methods to access property types:
-
write your own public methods for accessing member variables (first example).
-
make your member variables public (second example — never use that).
-
use accessors.
writing your own methods is a good approach, but it is not extensible. the most suitable method is using accessors. getter and setter methods in java and the property type in c# are called accessors. with accessors, you can give unified access to the user of your class.
c# is the programming language for the following example:
public class automobile
{
int _weight = 200;
public int weight
get
{
return _weight;
}
set
{
_weight = val;
}
}
there are a lot of things that you can do here in the accessor code. you can do any validation before setting any value, you can update or calculate any other value, or you can store/retrieve a value from storage devices directly from here.
sharing responsibilities/information via communication
the primary purpose of oop is to write a lot of software code easily. you divide a large problem into smaller problems and then write classes and create objects for those smaller problems.
but many people write one huge class and fill it with dozens of responsibilities. i believe that this is because people from the procedural programming background are unable to distribute responsibilities in different classes. you can solve this problem by distributing responsibilities among classes using association and inheritance.
association
consider there are two classes that do two different things. with association, one class uses the power of the other class to accomplish a common goal. that is it. the purpose of association is sharing responsibilities.
e.g.
public class member{
}
public class community{
member amember;
}
by having a reference to another object, your object has the ability to call the referenced object's methods. you can say that both of these citizens have joined hands to accomplish a goal.
in this article, i will not tell you the intricate details of association, aggregation, and composition in the uml's latest version or their differences. for me, association means sharing responsibilities between two classes. but if you are interested in minute details, here is a good article on that .
if a class a object is communicating with a class b object to accomplish a goal, then class a is dependent upon class b. dependency is considered a bad thing in the programming world (nobody likes dependency in the real world, either!). how do you reduce this dependency and still make the objects work together as a team? this is the question for which many design patterns and principles were invented. i will discuss some tools to reduce dependency later in the article, but first, let's discuss another tool that we can use to share responsibilities.
inheritance
inheritance is about distributing responsibilities between two classes: parent and child. the benefit is that the child class inherits all the qualities of the parent. as the child is using the parent's capabilities, it is said that inheritance supports reusability.
let’s see some examples of inheritance:
as you can see, i have modeled the vehicle class and its children so that the common features for both vehicles, namely ‘car’ and ‘boat,’ are combined in one parent class named ‘vehicle’.
now if i want another specialized car, like a sports car, i can extend the ‘car’ class and implement the logic specific to a sports car.
nice hierarchy! it sounds logical, but the problem is that real life problems are hardly like that. real world problems are complex, and any code that you write will evolve over time. evolving requirements will cause evolving inheritance hierarchies.
i have seen systems where developers' lives are consumed in managing large hierarchies of inheritance. due to this, inheritance is not good for maintainability. but don't underestimate the power of inheritance. there are some scenarios where using inheritance is a natural fit, such as winforms.
similiar to composition, there is the dependency between child and parent classes, and dependency is a bad thing in programming. now i will discuss how can you minimize the side effects of communication known as dependency.
reduce dependency
abstraction
my boss once asked me to update one of my software creations. this update enabled the software to receive data from network devices as well as from serial devices.
in order to implement this update, i needed to take care of a lot of things. for example, i had to include conditional statements to determine the kind of operation (serial or network) and update i/o methods, since there are different methods for receiving data from a network device and a serial device. hence this code is dependent upon tiny details.
but by using abstraction, i designed my code in a way that i can defer tiny details of implementation. i have abstracted out the tiny details, and now my code depends upon abstraction. therefore, my code will be updated easily with any new device code.
hence his ability to communicate with any new code without hassle and dependency is achieved through abstraction. there are two tools of abstraction that i will discuss in this article.
interface: first tool of abstraction
the concept of deferring the responsibilities to the implementing classes is called abstraction, and a tool for accomplishing this feature is ‘interface’ in java or c#.
this is how i define the interface for the example described above:
interface istream
{
byte[] getdata();
}
every class that implements an interface must define methods of the interface:
networkstream :istream
{
public byte[] getdata()
{
byte[] data = networkcard.getdata();// read data from network
return data;
}
}
here the 'interface' acts as an abstraction layer. it does not define or implement the behavior, but it gives the hint or overall picture of how a behavior should look like (i.e signatures of methods). it defers the responsibility for defining the behavior to the implementing class.
in above example, every class that wants to act like a stream should implement the methods in the istream interface. in this way, any piece of code that wants to work with a network stream will talk to the interface of the implementing class. here is the example code:
void coreclassmethod()
{
istream adatastream =new networkstream(); // using the interface
byte[] data = adatastream.getdata();
display(data);
}
therefore, if in future my boss asks me to extend the capability of coreclassmethod to handle pci data, i will just write a new class that implements the istream interface and creates the instance of that class in coreclassmethod:
pcistream:istream{
public byte[] getdata(){
// read data here using low leve i/o libraries and return it.
}
}
void coreclassmethod()
{
istream adatastream =new pcistream(); // using the interface
byte[] data = adatastream.getdata();
display(data);
}
the good thing is that the code that was written months ago can work with the newly written code with minimal effort.
abstract class: the second tool of abstraction
an abstract class is something in between a full-fledged class and an interface. you can define some behaviors and defer some behaviors to be defined by its children.
for example:
abstract class geometricalobject {
void showonscreen() {
// implementation steps
}
abstract void draw();
}
// child 1:
class rectangle: geometricalobject {
void draw() {
//specifics of drawing a rectangle
}
}
// child 2:
class circle: geometricalobject {
void draw() {
//specifics of drawing a circle
}
}
code that is shared among all children is written in the method showonscreen(). the principle that makes all the above interfacing and abstraction possible is polymorphism.
polymorphism
in simpler words, polymorphism is the ability of the parent reference to hold the reference to any of its children. a parent can be an interface, an abstract class, or a full-fledged class. so when you call from parent references, the desired method gets called on the appropriate children. see the following code for an example of streaming data.
interface istream {
byte[] getdata();
}
networkstream: istream {
public byte[] getdata() {
byte[] data = networkcard.getdata(); // read data from network
return data;
}
}
serialstream: istream {
public byte[] getdata() {
byte[] data = serialport.getdata(); // read data
return data;
}
}
// now if i have the reference of the parent class, istream,
// i can call the method of any child class
istream anobject = new networkstream();
data = anobject.getdata();
//change this in the run-time
anobject = new seriastream();
data = anobject.getdata();
so these are the basic tools for lowering the damage done by communication between objects. these tools help us reduce the dependency between objects.
final words
hence, in this oop article, i did not give you an index of definitions. rather, i tried to give you a layout of oop concepts that you can use to understand the most important concepts of oop. this article answers most of the 'why' question and a little bit of the 'how' question. why do you need encapsulation? why abstraction? how do you communicate while reducing the side effects of communication? i hope this layout will give you a different perspective of oop concepts.
Published at DZone with permission of Muhammad Umair. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments