Article Image
Article Image

read

Overview

This article is the first one of the series Spring Framework Fundamentals.

In this very first article, we will explain Object Oriented Programming and some basic principles both with definitions and code samples.

Object Oriented Programming

One of the popular programming paradigms is called Object Oriented Programming, generally abbreviated as OOP, suggests that objects should be used in computer programs.

Objects are special structures in programming contain data in forms of their properties, also named as attributes.

Besides, they contain procedures that are responsible for altering this data which are mostly called as functions.

Hopefully, Java is a clean implementation of the object oriented programming and contains many high-level structures for developers which are formed by objects. Thus we do not need to worry about most of the low-level operations.

However, there are further principles to learn to apply OOP correctly. These are widely known as solid principles in the programming world. When we say solid, this is because they literally form the SOLID. It is a funny story about that our former computer scientists culminated with an acronym for five OOP design principles intended to make software designs better and understandable.

What are these SOLID principles?

We have five principles each of them stands for each letter S-O-L-I-D. They are Single Responsibility Principle, Open Closed Principle, Liskov Substitution Principle, Interface Segregation Principle and Dependency Inversion Principle.

Single Responsibility Principle

Single Responsibility Principle in software programming suggests that an object should only have single responsibility. We usually refer any module, class or function here. In this way, one object can only modify one part of the software’s specifications.

Objects trying to handle more than one responsibility will eventually ensue fragility and become impossible to maintain. Thus, it is highly possible, the violation of this principle causes us the famous God Object anti-pattern in time.

An example to indicate a violation of the single responsibility principle:

LoginManager.java

if(LoginType.LOCAL_DB.equals(type)){

    // authenticating user from local db

} else if(LoginType.REMOTE_DB.equals(type)){

    // authenticating user from remote db

} else if(LoginType.LDAP.equals(type)){

    // authenticating user from ldap

} else if(LoginType.SOCIAL.equals(type)){

    // authenticating user from social network accounts

}
// and this conditional cases can go on...

So this code seems to start smelling like becoming a god object. Although it looks like a semi-god for now, it definitely intends to become bigger in time unless we do not stop it by refactoring:

LoginManager.java

public LoginManager() {
    // registering our manager implementations
    loginManagers.add(new LocalDBLoginManager());
    loginManagers.add(new RemoteDBLoginManager());
    loginManagers.add(new LdapLoginManager());
    loginManagers.add(new SocialLoginManager());
}

public void authenticate(User user, LoginType type) {
    // getManager method returns the right implementation
    // according to login type we need
    ILoginManager loginManager = getManager(type);
    loginManager.authenticate(user);
}

You can check the source code for this sample over here.

Besides, you can read single responsibility principle over on Wikipedia for further details.

Open Closed Principle

Open Closed Principle in software programming means that an ideal software application should be open for extensions but closed for modifications. Doing modification here is thought for changing the existing codes of pre-made modules, classes, etc.

On the other hand, what is mentioned when we say extension is; adding new classes, modules or even functions without touching the rest of the codebase.

Some implications of modification:

  • Increases fragility, decreases maintainability.
  • Causes strictly tight modules and classes.
  • Hard to test, leads to unexpected bugs.

A clear example to show a piece of code which will probably need modifications later:

ModificationManager.java

if(ModificationType.LESS.equals(type)){

    // less modification :)

} else if(ModificationType.MEDIUM.equals(type)){

    // medium modification :(

} else if(ModificationType.QUITE.equals(type)){

    // quite a lot modification :O

} else if(ModificationType.ENORMOUS.equals(type)){

    // vast modification >:O

}
// and this conditional cases can go on...

One proper solution to this problem is Chain of Responsibility pattern which is also a good example of design by extensions, can be achieved like the code below:

ModificationManager.java

public ModificationManager() {
    // new modifier implementations can be easily added
    // without touching the rest of the code
    modifiers.add(new LessModifier());
    modifiers.add(new MediumModifier());
    modifiers.add(new QuiteModifier());
    modifiers.add(new EnormousModifier());
}

// main part of the code that handles the logic
// is not affected by adding or removing different type of modifiers
public boolean modifySystem(ModificationType type) {

    for (IModifier modifier : modifiers) {
        // each modifier instance knows that they can modify it
        // or not and returns the result as boolean
        if (modifier.modify(type)) {
            return true;
        }
    }

    // if we are here then it seems no modification is done
    return false;

}

You can check the source code for this sample over here.

How the OCP can be used in Java?

One of the best practices is programming to interface when it comes to applying OCP into java. Programming to interface means to prefer using interfaces instead of concrete types unless you specifically need to.

Interfaces are the contracts to expose the behavior type of our program to the outer world. With the help of well-defined interfaces, you always have a chance to create new implementations and easily extend your project without affecting the world outside. This technically means adding an extension.

Hence, we can say that interfaces really play nice with the OCP.

A simple example to show an advantage of using interfaces over concrete types:

CoffeeMachine.java

public interface Coffee {
    public void taste();
}

Coffee coffee = new FilterCoffee();

// you taste filter coffee!
coffee.taste();

// After some time we discover a new taste and add our new coffee formula!
Coffee coffee = new EspressoCoffee();

// now you taste espresso, we do not need to modify the code below!
coffee.taste();

Besides, you can read open closed principle over on Wikipedia for further details.

Liskov Substitution Principle

Liskov Substitution Principle suggests that objects in a software program should be replaceable with the instances of their subtypes without need to change properties of theirs.

Another use case of interfaces transpires here, since we need a behavioral similarity between subtypes, also called as strong behavioral subtyping. Different behaviors can output different results so we need to group subtypes with similar behavior by using interfaces not to break our program’s communication with the outside.

An example to demonstrate this problem:

public class Fish {
    public void swim(){
        System.out.println("I'm swimming")
    }
}

public class DeadFish extends Fish {
    public void swim(){
        System.out.println("Cannot swim because I'm dead!")
    }
}

// Let's say that we need a fishing pool which every Fish instance should swim.
// However as you can see some instances will not be able to swim because they are dead.
List<Fish> pool = new ArrayList<>();
pool.add(new Fish());
pool.add(new Fish());
pool.add(new DeadFish());

for(Fish fish:pool){
    fish.swim();
}

An elegant solution comes with the help of interfaces to discriminate subtypes according to their behaviors:

public interface Alive {
    public void swim();
}

public interface Dead {
    public void sink();
}

public class AliveFish extends Fish implements Alive {

    @Override
    public void swim() {
        System.out.println("I'm swimming because I'm alive :)");
    }
}

public class DeadFish extends Fish implements Dead {

    @Override
    public void swim() {
        System.out.println("Cannot swim because I'm dead!");
    }

    public void sink() {
        System.out.println("I'm sinking :(");
    }
}

// So we need only alive fish for our fishing pool.
// Now we are sure that every Fish instance in our pool can swim!
List<Alive> pool = new ArrayList<>();
pool.add(new AliveFish());
pool.add(new AliveFish());
pool.add(new AliveFish());
// compilation error prevents us to add DeadFish instances by mistake!
// pool.add(new DeadFish());

for(Alive fish:pool){
    fish.swim();
}

You can check the source code for this sample over here.

Besides, you can read liskov substitution principle over on Wikipedia for further details.

Interface Segregation Principle

Interface Segregation Principle in software simply tells us that instead of one general-purpose interface, it is better to use many client-specific ones. One obvious problem we can encounter when we violate this principle, is the boilerplate invasion of meaningless, empty methods.

Let us show this problem with an example:

public interface Animal {

    public void swim();
    public void fly();
    public void run();

}

public class SwimmingAnimal implements Animal {

    public void swim(){
        System.out.println("Oh it's swimming, I know how to swim :)");
    }

    // Ideally we do not need to implement the methods below
    public void fly(){
        System.out.println("What am i going to do?");
    }

    public void run(){
        System.out.println("What am i going to do?");
    }

}

And the solution would be splitting our general interface into more specific ones:

public interface Animal {
    // let this be just a marker interface
}

public interface CanSwim extends Animal {
    public void swim();
}

public interface CanFly extends Animal {
    public void fly();
}

public interface CanRun extends Animal {
    public void run();
}

// no need to implement unnecessary methods
public class SwimmingAnimal implements CanSwim {
    public void swim(){
        System.out.println("Oh it's swimming, I know how to swim :)");
    }
}

You can check the source code for this sample over here.

Besides, you can read interface segregation principle over on Wikipedia for further details.

Dependency Inversion Principle

Dependency Inversion Principle states that in a software program high-level objects should not depend on low-level objects, on the contrary, both should depend on abstractions.

Similarly, concrete classes should depend on abstractions, but abstract ones should not. After these explanations, let’s be a little bit more explanatory.

An example of a DIP violation:

public class OperatingSystem {

    private HttpService httpService = new HttpService();
    private SmtpService smtpService = new SmtpService();

    public void runOnStartup() {
        httpService.startHttpd();
        smtpService.startSmtpd();
    }

}

Instead of depending on concrete classes we should definitely make an abstraction by the help of interfaces and refactor our tiny operating system to accept only abstract services in order to initiate on the os startup.

See the code below:

public class HttpService implements IService {
    public void start(){
        System.out.println("Starting httpd service...");
    }
}

public class SmtpService implements IService {
    public void start(){
        System.out.println("Starting smtpd service...");
    }
}

public class OperatingSystem {

    private List<IService> services = new ArrayList<>();

    public OperatingSystem() {
        register(new HttpService());
        register(new SmtpService());
    }

    public void register(IService service){
        this.services.add(service);
    }

    public void runOnStartup() {
        this.services.forEach(s -> s.start());
    }

}

You can check the source code for this sample over here.

Besides, you can read dependency inversion principle over on Wikipedia for further details.

Conclusion

After we explained OOP and the solid principles shortly we will go on Containers, Inversion of Control and Dependency Injection and give some examples about how they are applied in our next article.

Blog Logo

Yavuz Tas


Published

Image

Yavuz is logging...

A bunch of java experience from a java lover

Back to Home