Question

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.

Was it helpful?

Solution 3

Sometimes it's easy to write the whole logic into procedural pattern rather than to use SOLID

I cannot agree more, it is easier for us programmer to handle code in procedural pattern. This makes OOP hard for programmer who accustomed to procedural programming.

However I found it easier to write the general interface and consumer first rather than breaking the interface designed to smaller modules. This is, a kind of Test First Development -> Red, green, refactor practice. (please note that, if you want to achieve the neat design, consider following TDD instead this guide. This guide is just a small section of doing TDD)

Say that we want to create the ServiceAttendance to do scanEmployeeID. We will have an interface like (please notice the example is in C# naming):

public interface IServiceAttendance{
    bool ScanEmployeeId();
}

Please note that I decide the method to return bool instead of void to determine success/failure operation. Please notice the consumer example below does not implement any DI because I just want to show how to consume it. Then in the consumer, we can have:

public void ConsumeServiceAttendance(){
    IServiceAttendance attendance = Resolve<IServiceAttendance>();
    if(attendance.ScanEmployeeId()){
        // do something
    }
}

This concludes the consumer. Now we move to implementation. Say that you can develop it using procedural programming and got the monolithic code block. You can state the implementation with pseu-like statement.

public class ServiceAttendance : IServiceAttendance{
    public bool ScanEmployeeId(){
        bool isEmpValid = false;
        // 1 scan the employee id
        // 2 validate the login
        // 3 if valid, create the login session
        // 4 notify the user
        return isEmpValid;
    }
}

Now we have 4 steps to be done in this one operation. My principal is, not to do over 3 facade process in one method so I can simply refactor the 3 and 4 to one process. Now we have

public class ServiceAttendance : IServiceAttendance{
    public bool ScanEmployeeId(){
        bool isEmpValid = false;
        // 1 scan the employee id
        // 2 validate the login
        // 3 if valid, create the login session and notify the user
        return isEmpValid;
    }
}

This, we have 3 main operation. We can analyze whether we need to create a smaller module or not by breaking down the operation. Say we want to break the second operation. We can get:

// 2 validate the login
// 2.1 check if employee id matches the format policy
// 2.2 check if employee id exists in repository
// 2.3 check if employee id valid to access the module

The breakdown operation itself is obvious enough to break the second module into another smaller module. For 2.2 and 2.3, we need a smaller module to be injected. Simply because it will need dependency to repository, thus need to be injected. The same case apply for operation step 1 scan the employee id, because it will need dependency to fingerprint scanner, so the scanner handler must be implemented in separated module.

We can always breakdown the operation, as we can do it in 2.1:

// 2.1 check if employee id matches the format policy
// 2.1.1 employee id must match the length
// 2.1.2 employee id must has format emp#####

Now I am unsure if 2.1.1 and 2.1.2 need to be broken down into 2 separated modules, it is up to you to decide. And now we got the interfaces, then we can start the implementation. Expect to throw exceptions during validation or you will need to pass custom class to handle error messages.

OTHER TIPS

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.

Not specifically about SOLID, but worth mentioning as a very interesting OOP-training approach by Jeff Bay: Object Oriented Calisthenics. The idea is that you can try to follow set of very strict rules on a non-real-life, small project.

The Rules

1. One level of indentation per method
2. Don’t use the ELSE keyword 
3. Wrap all primitives and Strings
4. First class collections
5. One dot per line
6. Don’t abbreviate
7. Keep all entities small
8. No classes with more than two instance variables
9. No getters/setters/properties

By suspending disbelief, and rigidly applying these rules on a small, 1000 line project, you’ll start to see a significantly different approach to designing software. Once you’ve written 1000 lines of code, the exercise is done, and you can relax and go back to using these 9 rules as guidelines.

This is a hard exercise, especially because many of these rules are not universally applicable. The fact is, sometimes classes are a little more than 50 lines. But there’s great value in thinking about what would have to happen to move those responsibilities into real, first-class-objects of their own. It’s developing this type of thinking that’s the real value of the exercise. So stretch the limits of what you imagine is possible, and see whether you start thinking about your code in a new way.

First off, think of different parts of the attendance system. User interface, finger print scanner, database repository, login process and workflow. To design this system we can start designing parts in isolation and connect them as a system.

A rough design could be around following parts of the system:

  • Fingerprint Scanner and Listener
  • Attendance Service
  • Employee Repository
  • Login Repository
  • User Interface
  • Attendance Workflow Controller
  • Fingerprint Signature

In the following code listing certain aspects of design principles will be visible already:

  • SRP - One entity is responsible for one job
  • LoD - Law of Demeter - Only talk to your immediate friends. You will notice Controller does not know anything about repositories.
  • DbC (Design by Contract) - Work against interfaces
  • Use dependency injection and IoC - constructor injection and method injections
  • ISP (Interface Segregation Principle) - Interfaces are lean
  • OCP - Override interface methods in derived classes or pass different implementation as injected interfaces can extend the behaviour without the need to modify the class.

Based on this much thought, the system might work like this:

[You can further improve on it and add missing logic, I am providing a very quick design outline with brief implementation.]

Code Listing

interface IAttedanceController
{
    run();
}

interface IFingerprintHandler
{
    void processFingerprint(IFingerprintSignature fingerprintSignature);
}

interface IFingerprintScanner
{
    void run(IFingerprintHandler fingerprintHandler);
}

interface IAttendanceService
{
    void startService();
    void stopService();
    bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature);
    string getFailureMessage();
}

interface ILoginRepository
{
    bool loginEmployee(IEmployee employee, DateTime timestamp);
    void open();
    void close();
}

interface IEmployeeRepository
{
    IEmployee findEmployee(IFingerprintSignature fingerprintSignature);
    void open();
    void close();
}

//-----------------------------------------

class AttendanceService : IAttendanceService
{
    private IEmployeeRepository _employeeRepository;
    private ILoginRepository _loginRepository;
    private string _failureMessage;

    public class AttendanceService(
        IEmployeeRepository employeeRepository,
        ILoginRepository loginRepository)
    {
        this._employeeRepository = employeeRepository;
        this._loginRepository = loginRepository;
    }

    public bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature)
    {
        IEmployee employee = this._employeeRepository.findEmployee(fingerprintSignature);

        if(employee != null)
        {
            //check for already logged in to avoid duplicate logins..

            this._loginRepository.loginEmployee(employee, DateTime.Now);
            //or create a login record with timestamp and insert into login repository

            return true;
        }
        else
        {
            this._failureMessage = "employee not found";
            return false;
        }
    }

    public string getFailureMessage()
    {
        return "reason for failure";
    }

    public void startService()
    {
        this._employeeRepository.open();
        this._loginRepository.open();
    }

    public void stopService()
    {
        this._employeeRepository.close();
        this._loginRepository.close();
    }
}

//-----------------------------------------

class AttendanceController : IAttedanceController, IFingerprintHandler
{
    private ILoginView _loginView;
    private IAttendanceService _attedanceService;
    private IFingerprintScanner _fingerprintScanner;

    public AttendanceController(
        ILoginView loginView,
        IAttendanceService attendanceService,
        IFingerprintScanner fingerprintScanner)
    {
        this._loginView = loginView;
        this._attedanceService = attedanceService;
        this._fingerprintScanner = fingerprintScanner;
    }

    public void run()
    {
        this._attedanceService.startService();
        this._fingerprintScanner.run(this);
        this._loginView.show();
    }

    public void IFingerprintHandler.processFingerprint(IFingerprintSignature fingerprintSignature)
    {
        if(this._attedanceService.login(fingerprintSignature))
        {
        this._loginView.showMessage("Login successful");
        }
        else
        {
        string errorMessage = string getFailureMessage();
        this._loginView.showMessage("errorMessage");
        }

        // on return the fingerprint monitor is ready to take another finter print
    }
}

//-----------------------------------------

App.init()
{
    // Run app bootstrap
    // Initialize abstract factories or DI containers

    IAttedanceController attedanceController = DIContainer.resolve("AttedanceController");
    attedanceController.run();
}

//-----------------------------------------

Certainly, procedural programming is much easier for people who are used to writing code procedurally. For those used to writing well factored object oriented code, procedural code is actually harder.

Yes, well factored object oriented code often results in more work, and more actual code. But if done correctly, it makes the code easier to maintain, easier to extend, easier to debug (and more importantly easier to test).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top