How to practice SOLID principle of OOP design?

Ruby picture Ruby · May 19, 2013 · Viewed 14.5k times · Source

I'm new to SOLID principle but I understand it. My main problem is having a hard time designing my classes to follow the SOLID specially the Dependency Inversion. Sometimes it's easy to write the whole logic into procedural pattern rather than to use SOLID.

For example:

Let's say that we are creating an Attendance Monitoring System, and we have logic(or procedure) that scan the employee fingerprint, get it's ID, determine whether or not it's valid or not, determine what time he was in, write the login info to the database, and show if it's successful or not.

It is easy to write this in a procedural manner with a bunch of 'if else', loop and switch. But in future I'm gonna suffer the 'code debt'.

If we applying SOLID principle here. I know that we need to have a some kind of object like 'AttendanceServiceClass' that has a method like 'scanEmployeeID()', 'processthislogin()' or 'isItsucessful()'. And I know that this class has a dependency to a repository, userinfo, and other objects.

Basically my problem is analyzing about the design of the class and its dependencies

What is the step by step way of analyzing the design of your class?

sorry for my English.

Answer

Henrique Barcelos picture Henrique Barcelos · May 20, 2013

First of all, solid is not ONE principle, it stands for 5 different principles:

  • SRP (Single Responsibility Principle): your component should have one single reason to change;
  • OCP (Open-Closed Principle): your component should be open for extension, but closed for modification;
  • LSP (Liskov's Substitution Principle): this one helps you deciding whether you should build an hierarchical relationship between class A and B through inheritance. Inheritance is suitable whenever all objects of a derived class B can be replaced by objects of their parent class A without any loss of functionality;
  • ISP (Interface Segregation Principle): states that no component should be forced to depend on methods it does not use;
  • DIP (Dependency Injection/Inversion): states that high level components should not depend on low level components.

These principles are guides, but it does not mean you have to use them strictly every time.

From your description, I can see your main difficulty is to think OO. You are still thinking about how to do things and this is a procedural mindset. But in OOP it is more important decide who will do these things.

Thinking about DI, using your example, let's see your scenario:

public class AttendanceService {
    // other stuff...
    
    public boolean scanEmployeeId() {
        // The scanning is made by an barcode reader on employee's name tag
    }
}

What is the problem here?

Well, first of all, this code violates SRP: What if the authentication process changes? If the company decided that name tags are insecure and install a biometrical recognition system? Well, there's here a reason for your class to change, but this class does not do just authentication, it does other things, so, there will be other reasons for it to change. SRP states that your classes should have just ONE reason to change.

It also violates OCP: What if there's another authentication method available and I want to be able to used as I wish? I can't. To change the auth method, I have to modify the class.

It violates ISP: Why a ServiceAttendance object has a method for employee authentication if it should just provide service attendance?


Let's improve it a little bit:

public class BarCodeAuth {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class AttendanceService {
    private BarCodeAuth auth;
    public AttendanceClass() {
        this.auth = new BarCodeAuth();
    }

    public void doOperation() {
        if(this.auth.authenticate()) {
           // do stuff..
        }
    }
}

Now that's a little bit better. We solved the problems with SRP and ISP, but if you think better, it still violates OCP and now violates DIP. The problem is that AttendanceService is tightly coupled with BarCodeAuth. I still can't change the auth method without touching AttendanceService.

Now let's apply OCP and DIP together:

public interface AuthMethod {
    public boolean authenticate();
}

public class BarCodeAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class BiometricAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class FooBarAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class AttendanceClass {
    private AuthMethod auth;
    public AttendanceClass(AuthMethod auth) {
        this.auth = auth;
    }

    public void doOperation() {
        if(this.auth.authenticate()) {
           // do stuff..
        }
    }
}

Now I can do:

new AttendanceClass(new BarCordeAuth());
new AttendanceClass(new BiometricAuth());

To change the behavior, I don't need to touch the class. If some other auth method appears, I just need to implement it, respecting the interface and it is ready to use (remember OCP?). This is due to me being using DIP on ServiceAttendance. Although it needs an authentication method, it is not its responsibility to create one. In deed, for this object, it does not matter the method of authentication, it just need to know if the caller (user) is or is not authorized to do what he's trying to do.

This is all about DIP is: your components should depend on abstractions, not implementations.