How to create a factory method
https://softwareengineering.stackexchange.com/questions/350673
-
13-01-2021 - |
Question
Say I have a Business class called person:
public class Student { }
Say I want to create a factory method for this Student class - something like this:
public Student getStudent(string studentType)
{
if (StudentType=="P")
return new PostGraduate();
else
return new Undergraduate();
}
Should this method go in the BusinessLayer.Student class? I believe this would break the single responsibility principle. On that basis should it go in a new class called: StudentFactory?
If it should go in a new class called: StudentFactory then should this class be in the ServiceLayer (which creates the Student object) or the BusinessLayer? Finally should I use IOC (Castle Windsor) to create the Student object? The answerer in this question suggests that you should not use an IOC for business layer objects: Rich vs Anemic Domain Model
Therefore to recap:
1) Should the factory method be created in the Student class or in its own class? 2) If the answer to 1 is "its own class", then should the class be located in the service layer or business layer? 3) Should the business layer object be IOC managed?
Solution
1) Should the factory method be created in the Student class or in its own class?
Definitely its own class. As you said, your Student class has a responsibility, and constructing instances of itself is not that responsibility.
That said, I don't normally write factories for business layer objects. I use an anaemic domain model, and these object are usually constructed by my data access object (DAO) from persistence. Your example of a factory seems like just the logic that would be in my DAO. It looks like a method of polymorphic tables in a RDBMS where type is stored as a column in the table. Something like this psuedocode:
def getStudents()
dbCursor = query rdbms "select id, studentType, name from student"
students = new List
while dbCursor has next
studentType = dbCursor get column 2
if studentType == "P"
students += new PostGraduate
else
students += new Undergraduate
return students
I would use the abstract factory pattern for instances of the DAO. Especially if multiple implementations may exist. E.g. in-memory dao for testing and PostgreSQL impl
StudentDaoFactory {
create(Config config): StudentDao
}
2) If the answer to 1 is "its own class", then should the class be located in the service layer or business layer?
That really depends. Who is using the factory? Do you believe that the factory could and would be used by anybody using Student
? If everybody is going to use this factory to construct instances of Student
than I would put it in the same layer as Student
. If it's a factory only conceivably used by a different layer, then put it there. These questions usually work themselves out as the code base matures because inevitably the class will end up in the lowest common denominator layer that all dependent layers can access.
3) Should the business layer object be IOC managed?
No. As I mentioned earlier, these objects are usually created by a DAO. IOC is a reasonable choice for managing your factories and DAOs.
OTHER TIPS
The very first line and code example outlines the real problem to be solved:
Say I have a Business class called person:
public class Student { }
You have a business class called "person" but the class is named "Student." You want to know how to return a concrete type based on the kind of degree they are going for. This is the problem to solve.
You need a Person class:
public class Person { }
And you need a Student
class, which takes a Person
and a Degree
:
public class Student
{
private Degree degree;
private Person person;
public Student(Person person, Degree degree)
{
this.person = person;
this.degree = degree;
}
}
The fact that a person is a student at a school is information about a person (an attribute). A person can be a student at multiple schools. In the U.S.A. students in high school can "dual enroll" at a local college, essentially making them a student at two academic institutions.
Just thinking of colleges, a person can be a former student as an Undergrad, and come back for the Graduate degree, and then their P.H.D.
So the kind of degree is also an attribute - not of the Person but of the Student.
Any behavior that is specific to a kind of degree should be pushed down into the Degree class. The Degree
class is actually where you want to take advantage of polymorphism and inheritance:
public abstract class Degree
{
public abstract bool CanRegisterForClass(Class classToRegister);
}
public class UndergraduateDegree : Degree
{
public override bool CanRegisterForClass(Class classToRegister)
{
return classToRegister.Level < 400;
}
}
public class GraduateDegree : Degree
{
public override bool CanRegisterForClass(Class classToRegister)
{
return classToRegister.Level < 600
&& classToRegister.Level >= 200;
}
}
public class DoctoralDegree : Degree
{
public override bool CanRegisterForClass(Class classToRegister)
{
return classToRegister.Level < 700
&& classToRegister.Level >= 500;
}
}
Here we have an abstract method called CanRegisterForClass(...)
and three concrete classes. Each degree checks the Level
of the class to see if a Student
can register for it:
public class Student
{
private Degree degree;
private Person person;
private List<RegisteredClass> registeredClasses;
public Student(Person person, Degree degree)
{
this.person = person;
this.degree = degree;
this.registeredClasses = new List<RegisteredClass>();
}
public RegisteredClass RegisterForClass(Class classToRegister)
{
if (!degree.CanRegisterForClass(classToRegister))
throw new InvalidOperationException("Cannot register for this class");
var registeredClass = new RegisteredClass(this, classToRegister);
registeredClasses.Add(registeredClass);
return registeredClass;
}
}
}
Whether or not a student can register for a class depends on the degree. This behavior is pushed down into a class that can handle it with out repetitious if
statements.
Now you are at a point where you need a factory method, which can be as simple as a static method on the Degree
class:
public abstract class Degree
{
public abstract bool CanRegisterForClass(Class classToRegister);
public static Degree CreateDegree(string studentType)
{
switch (studentType)
{
case "U":
return new UndergraduateDegree();
case "G":
return new GraduateDegree();
case "D":
return new DoctoralDegree();
default:
throw new InvalidOperationException("Invalid student type");
}
}
}
The last question to answer is who should call this factory method. In this case, the part of your application responsible for creating Student
objects is where this factory method should be invoked. If you are using an ORM like Entity Framework or NHibernate to map these objects then you should be able to configure the ORM to do this mapping for you based on values in a certain column of a table in the database.
With no ORM available, a service class can do this, or even a factory class for Student
objects, which just delegates to the static method on the Degree
class.