Object-Oriented Design Decisions: Stateful or Stateless Classes?
Get schooled on stateless and stateful classes: what the state of an object means, types of classes and states, and more!
Join the DZone community and get the full member experience.
Join For FreeWe know what object oriented programming is. But sometimes we need to spend more time to decide what properties are needed for a particular class. Otherwise, we will suffer at a later point, if the class has the wrong state attributes. Here we are going to discuss what type of class should be stateful and what stateless.
What is Meant by the State of an Object
Before we discuss Stateless or Stateful classes we should have a better understanding of what is meant by the state of an object. It's the same as the English meaning "the particular condition that someone or something is in at a specific time" of the state.
When we come to programming and think about the condition of an object at a specific time, it's nothing but the value of its properties or member variables at a given point in time. Who decides what are the properties of objects? It's the class. Who decides what are the properties and members inside a class? It's programmer who coded that class. Who is the programmer? Everybody who reads this article including me who is writing this. Are we all experts in making a decision on what are the properties needed for each class?
I don't think so. At least it's true in the case of programmers in India who enter the software industry by only looking at the salary and programming as a daily job. First of all it's not something that can be taught in colleges like how other engineering disciplines work. It requires experience because programming, unlike other types of engineering, is, in its early stages, more like art than engineering. Engineering can sometimes have hard rules but art doesn't always. Even after being into programming for around 15 years (sorry I count my college days as well in programming experience) I still take a considerable amount of time to decide what are the properties needed for a class and the name of the class itself.
Can we bring some rules to what are the properties needed? In other words what properties should the state of an object include? Or should the objects be stateless always? Below are some thoughts on this area.
Entity Classes/Business Objects
There are multiple names such as entity classes, business objects etc...given to classes which represent a clear state of something. If we take the example of an Employee class, its sole purpose is to hold the state of an employee. What might that state contain? EmpId, Company, Designation, JoinedDate etc...I hope there will be no confusions at this point. Everybody agrees that this type of class should be stateful without much arguments because this is taught in college.
But how we should do a salary calculation?
- Should the
CalculateSalary()
have a method inside the Employee class? - Should there be a
SalaryCalculator
class and that class should contain the Calculate() method? - If there is
SalaryCalculator
class:- Should it have properties such as BasicPay, DA HRA, etc?
- Or should the
Employee
object be a private member variable in that SalaryCalculator which is injected via the constructor? - Or should
SalaryCalculator
expose Employee public property (Get&Set Employee
methods in Java)?
Helper/Operation/Manipulator Classes
This is the type of class that does a task. SalaryCalculator falls into this type. There are many names for this type where classes do actions and can be found in programs with many prefixes and suffixes such as
- class
SomethingCalculator
eg: SalaryCalculator - class
SomethingHelper
eg: DBHelper - class
SomethingController
eg: DBController - class
SomethingManager
- class
SomethingExecutor
- class
SomethingProvider
- class
SomethingWorker
- class
SomethingBuilder
- class
SomethingAdapter
- class
SomethingGenerator
A long list can be found here. People have different opinions on using which suffix for what situation. But our interest is something else.
Can we add a state to this type of class? I suggest instead that you make them stateless. Let's examine why I say this.
Hybrid Classes
According to Wikipedia encapsulation in object oriented programming is "...the packing of data and functions into a single component". Does this mean all the methods which manipulate that object should be there in the entity class? I don't think so. The entity class can have state accessor methods such as GetName()
, SetName()
, GetJoiningDate
, GetSalary()
etc...
But CalculateSalary()
should be outside. Why is it so?
According to the SOLID - Single Responsibility Principle: "A class should change only for one reason". If we keep CalculateSalary()
method inside the Employee class that class will change for any of the below 2 reasons (which thus violates SRP):
- A state change in Employee class e.g.: a new property has been added to Employee
- There is a change in the calculation logic
I hope that is clear. Now we have two classes in this context: the Employee class and the SalaryCalculator class. How do they connect to each other? There are multiple ways. One is to create an object of SalaryCalculator class inside the GetSalary method and call the Calculate() to set the salary variable of Employee class. If we do so, it becomes hybrid because it is acting like an entity class and it initiates operation like a helper class. I really don't encourage this type of hybrid class. But in situations such as a Save entity method, this is kind of OK with some sort of delegation of operation.
Rule of thumb: "Whenever you feel that your class is falling into this hybrid category, think about refactoring. If you feel that your classes are not falling into any of the above categories then stop coding."
State in Helper/Manipulator Class
What is the problem if our helper classes keep state? Before that let's look at what are the different combinations of state values a SalaryCalculator
class can take Below are some examples:
Scenario 1 - Primitive Values
class SalaryCalculator
{
public double Basic { get; set; }
public double DA { get; set; }
public string Designation { get; set; }
public double Calculate()
{
//Calculate and return
}
}
Cons
There is a chance that the Basic salary can be of an "Accountant" and the Designation can be "Director" which is not at all matching.There is no enforced way to make sure that the SalaryCalculator can work independently.
Similarly, if this executes in a threaded environment, it will fail.
Scenario 2 - Object as State
class SalaryCalculator
{
public Employee Employee { get; set; }
public double Calculate()
{
//Calculate and return
}
}
Cons
If one SalaryCalculator object is shared by 2 threads and each thread is for a different employee, the sequence of execution might be as follows which cause logical errors:
- Thread 1 sets
employee1
object - Thread 2 sets
employee2
object - Thread 1 calls
Calculate
method and getsSalary
foremployee2
We can argue that the Employee
dependency can be injected via a constructor and make the property read-only. Then we need to create SalaryCalculator
objects for each and every employee object. Better: do not design your helper classes in this way.
Scenario 3 - No State
class SalaryCalculator
{
public double Calculate(Employee input)
{
//Calculate and return
}
}
This is a near perfect situation. But here we can argue that if all the methods are not using any member variable then what is the use of keeping it as a non-static class.
The second principle of SOLID says: "Open for extension and closed for modification". What does it mean? When we write a class, it should be complete. There should be no reason to modify it. But it should be extensible via subclassing and overriding. So what would our final one look like?
interface ISalaryCalculator
{
double Calculate(Employee input);
}
class SimpleSalaryCalculator:ISalaryCalculator
{
public virtual double Calculate(Employee input)
{
return input.Basic + input.HRA;
}
}
class TaxAwareSalaryCalculator : SimpleSalaryCalculator
{
public override double Calculate(Employee input)
{
return base.Calculate(input)-GetTax(input);
}
private double GetTax(Employee input)
{
//Return tax
throw new NotImplementedException();
}
}
As I mentioned many times in my blog, always program to interface. In the above code snippet, I implemented interface methods implicitly. That is to reduce the space here. Always implement explicitly. The Logic of calculation should be kept in a protected function so that the inherited classes can call that function in case required.
Below is the way how the Calculator
class(es) should be consumed
class SalaryCalculatorFactory
{
internal static ISalaryCalculator GetCalculator()
{
// Dynamic logic to create the ISalaryCalculator object
return new SimpleSalaryCalculator();
}
}
class PaySlipGenerator
{
void Generate()
{
Employee emp = new Employee() { };
double salary =SalaryCalculatorFactory.GetCalculator().Calculate(emp);
}
}
The Factory class encapsulates the logic of deciding which child class to be used. It can be static as above or dynamic using reflection. As far as the reason for change in this class is object creation, we are not violating the "Single Responsibility Principle."
In case you are going to a hybrid class, you may invoke calculation from the Employee.Salary
property or Employee.GetSalary()
as below.
class Employee
{
public string Name { get; set; }
public int EmpId { get; set; }
public double Basic { get; set; }
public double HRA { get; set; }
public double Salary
{
//NOT RECOMMENDED
get{return SalaryCalculatorFactory.GetCalculator().Calculate(this);}
}
}
Conclusion
"Don't code when we are thinking. Don't think when we are coding." This principle will give us enough freedom to think whether the class should be stateless or stateful. If stateful, here's what the state of an object should expose:
- Make entity classes stateful.
- Helper / Operation classes should be stateless.
- Make sure the Helper classes are not static.
- Even if there is a hybrid class, make sure it's not violating the SRP.
- Spend some time on the class design before coding. Show the class diagram to 2-3 fellow programmers and get their opinions.
- Name the class wisely. The names will help us to decide state. There is no hard rule for naming. Below are some I am following:
- Entity classes should be named with nouns which represent a type of object - eg:
Employee
- Helper / Worker class names should be reflecting that it's a worker. eg:
SalaryCalculator
,PaySlipGenerator
etc... - Verb should never be used as class name - eg: class
CalculateSalary{}
- Entity classes should be named with nouns which represent a type of object - eg:
Points of Interest
- Most of the classes we are writing fall into the hybrid category which violates SRP. Please comment if there are any scenario which cannot be coded without hybrid classes
Published at DZone with permission of Joy George. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments