How to implement a composite pattern in Java? - java

I want to implement a composite pattern in Java in order to map a software development organization. So, let's assume there are multiple project managers and multiple developers. Each developer is assigned to exactly one project manager and each developer is able to code in various programming languages. The project managers lead the developers and know exactly their workload.
I am not a hundred percent sure about this design pattern, but I think that it is the perfect use case for this scenario, isn't it?
The result should be as follows:
I want to query the project manager to check the workload of all developers which are able to code in a specific programming language, e.g. Java.
Here is what I have so far:
Employee.java:
public class Employee {
private String name = null;
public Employee() {
name = "Noob";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ProgrammingLanguages.java:
public enum ProgrammingLanguages {
JAVA,
JAVASCRIPT,
C,
PHP,
SWIFT,
PYTHON
}
ProjectManager.java:
import java.util.ArrayList;
import java.util.List;
public class ProjectManager extends Employee {
private List<Employee> employeeList = null;
public ProjectManager() {
employeeList = new ArrayList<Employee>();
}
public List<Employee> getEmployees() {
return employeeList;
}
public void setEmployees(List<Employee> employees) {
employeeList = employees;
}
public int getTotalWorkload() {
int workload = 0;
for (Employee employee : employeeList) {
workload += employee.getWorkload(); // Error! Cannot resolve method 'getWorkload()'
}
return workload;
}
}
Developer:
import java.util.ArrayList;
import java.util.List;
public class Developer extends Employee {
private List<ProgrammingLanguages> languagesList = null;
private int workload = 0;
public Developer() {
languagesList = new ArrayList<ProgrammingLanguages>();
}
public void setLanguagesList(List<ProgrammingLanguages> languagesList) {
this.languagesList = languagesList;
}
public void addProgrammingLanguage(ProgrammingLanguages language) {
languagesList.add(language);
}
public List<ProgrammingLanguages> getLanguagesList() {
return languagesList;
}
public void setWorkload(int workload) {
this.workload = workload;
}
public int getWorkload() {
return workload;
}
}
Unfortunately, I get a compiler error in my ProjectManager class, any idea why?
Thanks in advance.

I am not a hundred percent sure about this design pattern, but I think that it is the perfect use case for this scenario, isn't it?
The GoF structure of Composite is as follows:
As you can see, Operation() is common in all elements. That would be your scenario's getWorkload() method.
However, it's somewhat inconsistent with the pattern in that it implies that a Manager has a workload that is composed of her employees. It's the contrary in real life, at least with a good manager. I would suggest changing the method name to something like getEffortUnderMyResponsibility(), to imply a responsibility for getting the work done, rather than actually doing the work. For programmers, it's true they actually do it; for managers, they are responsible for it getting done.

Yes, the composite pattern is indeed the right choice if you want to map tree structures. With reference to your example, the composite design pattern implies that your class Employee acts as a node, the class ProjectManager acts as a branch and the class Developer acts as a leaf. Within this context, the main advantage of the composite pattern is that it treats objects of your compositions uniformly. As a result, you can represent entire hierarchies of instances with this particular GoF design pattern.
You need the following participants:
The abstract class Employee must declare the interface of the composition and implements a common behaviour to a certain degree.
The ProjectManager class extends the abstract class Employee and implements a behaviour to treat Employee children, i.d. in your case ProjectManager or Developer instances.
The Developer also extends the abstract class Employee and represents a leaf which does not have any children.
I used your example code to demonstrate the composite pattern. Please note that it may vary from your desired outcome, but you can take it as a reference.
Employee.java (node):
package me.eckhart;
import java.util.List;
public abstract class Employee {
private String name = null;
public static final String OPERATION_NOT_SUPPORTED = "Operation not supported.";
public String getName() {
return name;
}
public Employee setName(String name) {
if (name == null) throw new IllegalArgumentException("Argument 'name' is null.");
this.name = name;
return this;
}
public Employee addEmployee(Employee employee) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
public List<Employee> getEmployees() {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
public Employee setEmployees(List<Employee> employees) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
public Employee setLanguagesList(List<ProgrammingLanguages> languagesList) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
public Employee addProgrammingLanguage(ProgrammingLanguages language) {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
public List<ProgrammingLanguages> getLanguagesList() {
throw new UnsupportedOperationException(OPERATION_NOT_SUPPORTED);
}
/* Composite operations. */
public abstract int getWorkload(ProgrammingLanguages language);
public abstract Employee setWorkload(int workload);
}
ProjectManager.java (branch):
package me.eckhart;
import java.util.ArrayList;
import java.util.List;
public class ProjectManager extends Employee {
private List<Employee> employeeList = null;
public ProjectManager() {
this.employeeList = new ArrayList<>();
}
#Override
public Employee addEmployee(Employee employee) {
if (employee == null) throw new IllegalArgumentException("Argument 'employee' is null.");
this.employeeList.add(employee);
return this;
}
#Override
public List<Employee> getEmployees() {
return this.employeeList;
}
#Override
public Employee setEmployees(List<Employee> employeeList) {
if (employeeList == null) throw new IllegalArgumentException("Argument 'employeeList' is null.");
this.employeeList = employeeList;
return this;
}
/* Composite operations. */
public int getWorkload(ProgrammingLanguages language) {
int workload = 0;
for (Employee employee : employeeList) {
workload += employee.getWorkload(language);
}
return workload;
}
public Employee setWorkload(int workload) {
throw new UnsupportedOperationException(Employee.OPERATION_NOT_SUPPORTED);
}
}
Developer.java (leaf):
package me.eckhart;
import java.util.ArrayList;
import java.util.List;
public class Developer extends Employee {
private List<ProgrammingLanguages> languagesList = null;
private int workload = 0;
public Developer() {
this.languagesList = new ArrayList<>();
}
#Override
public Employee setLanguagesList(List<ProgrammingLanguages> languagesList) {
this.languagesList = languagesList;
return this;
}
#Override
public Employee addProgrammingLanguage(ProgrammingLanguages language) {
this.languagesList.add(language);
return this;
}
#Override
public List<ProgrammingLanguages> getLanguagesList() {
return this.languagesList;
}
/* Composite operations. */
public Employee setWorkload(int workload) {
if (workload < -1) throw new IllegalArgumentException("Workload cannot be negative.");
this.workload = workload;
return this;
}
public int getWorkload(ProgrammingLanguages language) {
if (this.languagesList.contains(language)) return workload;
return 0;
}
}
ProgrammingLanguages.java (enumeration):
package me.eckhart;
public enum ProgrammingLanguages {
JAVA,
JAVASCRIPT,
C,
PHP,
SWIFT,
PYTHON
}
I created a unit test to demonstrate how you can access the workload for one particular programming language.
EmployeeTest.java (JUnit 4.11):
package me.eckhart;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class EmployeeTest {
protected Employee projectManagerIt;
#Before
public void setUp() throws Exception {
Employee webDevSr = new Developer();
webDevSr.setName("Jane").addProgrammingLanguage(ProgrammingLanguages.JAVASCRIPT).addProgrammingLanguage(ProgrammingLanguages.PYTHON).setWorkload(10);
Employee webDevJr = new Developer();
webDevJr.setName("Alex").addProgrammingLanguage(ProgrammingLanguages.PHP).setWorkload(15);
Employee projectManagerWebDev = new ProjectManager();
projectManagerWebDev.setName("James").addEmployee(webDevSr).addEmployee(webDevJr);
Employee softwareDevSr = new Developer();
softwareDevSr.setName("Martin").addProgrammingLanguage(ProgrammingLanguages.C).addProgrammingLanguage(ProgrammingLanguages.JAVA).setWorkload(35);
Employee softwareDevJr = new Developer();
softwareDevJr.setName("John").addProgrammingLanguage(ProgrammingLanguages.JAVA).setWorkload(30);
Employee projectManagerBackend = new ProjectManager();
projectManagerBackend.setName("Tom").addEmployee(softwareDevSr).addEmployee(softwareDevJr);
Employee freelanceSoftwareDev = new Developer();
freelanceSoftwareDev.setName("Marco").addProgrammingLanguage(ProgrammingLanguages.JAVA).addProgrammingLanguage(ProgrammingLanguages.PYTHON).addProgrammingLanguage(ProgrammingLanguages.C).setWorkload(25);
Employee freelanceWebDev = new Developer();
freelanceWebDev.setName("Claudio").addProgrammingLanguage(ProgrammingLanguages.SWIFT).addProgrammingLanguage(ProgrammingLanguages.JAVASCRIPT).addProgrammingLanguage(ProgrammingLanguages.PHP).setWorkload(10);
Employee freelanceProjectManager = new ProjectManager();
freelanceProjectManager.setName("Angie").addEmployee(freelanceSoftwareDev).addEmployee(freelanceWebDev);
projectManagerIt = new ProjectManager();
projectManagerIt.setName("Peter").addEmployee(projectManagerWebDev).addEmployee(projectManagerBackend).addEmployee(freelanceProjectManager);
}
#Test
public void testComposite() throws Exception {
Assert.assertEquals(90, projectManagerIt.getWorkload(ProgrammingLanguages.JAVA));
Assert.assertEquals(20, projectManagerIt.getWorkload(ProgrammingLanguages.JAVASCRIPT));
Assert.assertEquals(60, projectManagerIt.getWorkload(ProgrammingLanguages.C));
Assert.assertEquals(25, projectManagerIt.getWorkload(ProgrammingLanguages.PHP));
Assert.assertEquals(10, projectManagerIt.getWorkload(ProgrammingLanguages.SWIFT));
Assert.assertEquals(35, projectManagerIt.getWorkload(ProgrammingLanguages.PYTHON));
}
}
The UML class diagram looks like this:
The code in the setUp() method of EmployeeTest.java implements the following tree structure:
The main disadvantage of the composite design pattern is that you need to restrict certain operations with runtime-checks, since clients usually do not know whether they are dealing with a ProjectManager (branch) or a Developer (leaf) instance.

The method getWorkload() is not defined in class Employee and you're trying to access it.
In order to solve the compilation error you should add this method to Employee - I would add it as abstract in order to force any (new) sub-class to implement it.
BTW, that's not composition-pattern - that's inheritance. You can (and should) read more about it.

Related

How to transform entities extending a generic class to another entity extending another generic class

I'm developing a service oriented platform for retrieving, creating and updating entities from DB.
The point here is that every single java entity extends AbstractEntity, so for example, I have,
MyCar extends AbstractEntity implements Serializable
This AbstractEntity has common fields (such as id or audit ones).
So I have already developed a GenericReadService that, receiving a classname and a parameterMap, can read any entity and creates a EntityActionResult<T> including a List<T extends AbstractEntity>.
My problem comes when trying to transform that T type into something like <K extends GenericDTO>, as the client asking doesn't know AbstractEntity (obvious) but only GenericDTO. Doing this for safety and modularization.
So, now, I'm stuck on transforming the ListResponse<T> to a ReturnList<K extends GenericDTO>, as I don't find the way for knowing which K class should apply for each T.
This is what I actually have written:
private GenericEntityActionResult transform (EntityActionResult result) {
AsnGenericEntityActionResult r = new AsnGenericEntityActionResult();
if (result.getCode() == EntityActionResult.Code.ENTITY || result.getCode() == EntityActionResult.Code.ENTITY_LIST ) {
r.setCode(AsnGenericEntityActionResult.Code.OK);
List <? extends GenericDTO> list = new ArrayList<>();
if (result.getEntity() != null) {
//transform this entity into DTO
//<b>SOMETHING LIKE list.add(result.getEntity());</b>
} else if (result.getList() != null && !result.getList().isEmpty()) {
for (AbstractEntity a:result.getList()) {
//transform this entity into DTO
//<b>SOMETHING LIKE list.add(result.getEntity());</b>
//list.add(result.getEntity());
}
}
r.setList(list);
}
return r;
I´m obviously stuck on the SOMETHING LIKE, but can't find a proper way of doing so.
I thought about creating a abstract <T extends GenericDTO> transformToDTO() method on AbstractEntity, but can't do it since there are lots (and i mean hundreds) of Entities extending AbstractEntity and this client-service approach is a new development for some Entities, not the whole system.
Any clever ideas?
You can try to use the Java Introspection API or some more robust library on top of this API like apache commons beanutils or even more powerful bean mapping library like Dozer or something newer see
Following example demonstrating the basic technique, only raw introspection and reflection with two compatible POJO beans.
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
class Tranformation {
public static void main(String[] args) {
MyCar car = new MyCar();
car.setId("id01");
car.setName("Komodo");
car.setMadeIn("Jakarta");
CarDTO dto = toDto(CarDTO.class, car);
System.out.println(dto);
}
public <E extends AbstractEntity, D extends GenericDTO> D toDto(Class<D> dtoClass, E entity) {
if (null == entity) {
throw new NullPointerException("Entity can not be null");
}
try {
final D ret = dtoClass.newInstance();
BeanInfo dtoBeanInfo = Introspector.getBeanInfo(dtoClass);
final Map<String, PropertyDescriptor> mapping = Arrays.stream(dtoBeanInfo.getPropertyDescriptors())
.collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
final BeanInfo entityBeanInfo = Introspector.getBeanInfo(entity.getClass());
Arrays.stream(entityBeanInfo.getPropertyDescriptors()).forEach(src -> {
if (!"class".equals(src.getName())) {
PropertyDescriptor dst = mapping.get(src.getName());
if (null != dst) {
try {
dst.getWriteMethod().invoke(ret, src.getReadMethod().invoke(entity, null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
});
return ret;
} catch (InstantiationException | IntrospectionException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
public static class GenericDTO {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class CarDTO extends GenericDTO {
private String madeIn;
public String getMadeIn() {
return madeIn;
}
public void setMadeIn(String madeIn) {
this.madeIn = madeIn;
}
#Override
public String toString() {
return String.format("CarDTO [id=%s, name=%s, madeIn=%s]", getId(), getName(), madeIn);
}
}
public static class AbstractEntity implements Serializable {
private static final long serialVersionUID = 70377433289079231L;
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class MyCar extends AbstractEntity {
private static final long serialVersionUID = 8702011501211036271L;
private String madeIn;
public String getMadeIn() {
return madeIn;
}
public void setMadeIn(String madeIn) {
this.madeIn = madeIn;
}
}
}
Outputs:
CarDTO [id=id01, name=Komodo, madeIn=Jakarta]

using arraylist to create to inputs

I think I have got myself into a bit of muddle. I had teacher and student below in two separate addMember sections. For the purpose of what I am doing they have to in one section but when someone wants to add either student or teacher they need to be able to be added separately (so one call which will give the option of adding either). But my knowledge of arrayLists is not very good and as you can see isn't working very well. Any help be much needed and appreciated.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Committee {
private String name;
private List<Object> members;
public Committee(String name)
{
this.name = name;
members = new ArrayList<Object>();
List list = new ArrayList();
}
public void addMember(Student student, Teacher teacher)
{
List members1 = new ArrayList();
members1.add(student);
System.out.println(members1);
List members2 = new ArrayList();
members2.add(teacher);
System.out.println(members2);
}
public void printMembership()
{
System.out.println("Membership of the " + name + " Committee ");
Iterator<Object> it = members.iterator();
while (it.hasNext())
{
Object member = it.next();
System.out.println(members);
}
}
}
I'm not sure what you want to achieve, so I will write two solutions:
Situation where you want to have one list
interface Member {...}
class Student implements Member {...}
class Teacher implements Member {...}
public class Comittee {
private list<Member> members;
public void addMember(Member member) {
members.add(member);
}
public void printMembership() {
members.forEach(System.out::println);
}
}
Situation where you want to have two lists
interface Member ...
class Student implements Member ...
class Teacher implements Member ...
public class Comittee {
private list<Teacher> teachers;
private list<Student> students;
public void addMember(Student student, Teacher teacher) {
students.add(student);
teachers.add(teacher);
}
public void printMembership() {
getAllMembers().forEach(System.out::println);
}
private void getAllMembers(): List<Member> {
List<Member> allMembers = ArrayList<>(students.size() + teachers.size());
allMembers.addAll(teachers);
allMembers.addAll(students);
return allMembers;
}
}

Avoid using instanceof with entities

Today I read this article about avoiding the use of instanceof.
This makes absolutely sense to me. I'm also aware of the visitor pattern.
Lets say I have the following situation:
#Entity
public abstract class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer employeeId;
private String name;
}
#Entity
public class FullTimeEmployee extends Employee {
protected Integer salary;
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
}
#Entity
public class PartTimeEmployee extends Employee{
protected Float hourlyWage;
public Float getHourlyWage() {
return hourlyWage;
}
public void setHourlyWage(Float hourlyWage) {
this.hourlyWage = hourlyWage;
}
}
How could I avoid using instanceof when calling a method like this?
public void convertEmployee(Employee employee) {
if (employee instanceof FullTimeEmployee) {
FullTimeEmployee fullTimeEmployee = (FullTimeEmployee) employee;
calcSalaray(fullTimeEmployee);
} else if (employee instanceof PartTimeEmployee) {
PartTimeEmployee partTimeEmployee = (PartTimeEmployee) employee;
calcHourlywage(partTimeEmployee);
}
}
Here is a simple example using the visitor pattern. The fact that your classes are entities is irrelevant. What matters is that the base class has a fixed, well-known number of subclasses.
Let's start with the abstract class:
public abstract class Employee {
public abstract void accept(EmployeeVisitor visitor);
}
It contains an accept() method taking a visitor as argument. Every subclass must override this method.
Now the two subclasses, each having a distinct set of fields and methods:
public class FrenchEmployee extends Employee {
private int eiffelTowerVisits;
#Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
public int getEiffelTowerVisits() {
return eiffelTowerVisits;
}
}
public class EnglishEmployee extends Employee {
private int towerBridgeVisits;
#Override
public void accept(EmployeeVisitor visitor) {
visitor.visit(this);
}
public int getTowerBridgeVisits() {
return towerBridgeVisits;
}
}
What is this visitor? It's an interface that does something specific for each subclass:
public interface EmployeeVisitor {
void visit(EnglishEmployee employee);
void visit(FrenchEmployee employee);
}
And here is an example of it usage, compared to the usage of instanceof:
public class EmployeeService {
public void displayEmployeeWithUglyInstanceof(Employee employee) {
if (employee instanceof EnglishEmployee) {
EnglishEmployee english = (EnglishEmployee) employee;
System.out.println("An English employee that visited the tower bridge " + english.getTowerBridgeVisits() + " times");
}
else if (employee instanceof FrenchEmployee) {
FrenchEmployee french = (FrenchEmployee) employee;
System.out.println("A French employee that visited the eiffel tower " + french.getEiffelTowerVisits() + " times");
}
}
public void displayEmployeeWithVisitor(Employee employee) {
EmployeeVisitor visitor = new EmployeeVisitor() {
#Override
public void visit(EnglishEmployee employee) {
System.out.println("An English employee that visited the tower bridge " + employee.getTowerBridgeVisits() + " times");
}
#Override
public void visit(FrenchEmployee employee) {
System.out.println("A French employee that visited the eiffel tower " + employee.getEiffelTowerVisits() + " times");
}
};
employee.accept(visitor);
}
}
You might see that as overengineering, but in the case of Hibernate entities, it's actually very useful, because Hibernate uses dynamic proxies for lazy associations. So an employee might very well not be an EnglishEmployee nor a FrenchEmployee. In that case, instanceof will return false for both cases, and your code won't do anything. Whereas with the visitor, the proxy will delegate the call to the French or English employee it wraps, and everything will go smoothly.
Another advantage is that, if you add a SpanishEmployee, you will immediately see all the visitor implementations that must be modified to handle a spanish employee.
One possible trick goes:
public abstract class Employee {
// ...
FullTimeEmployee asFullTimeEmployee() {
return null;
}
}
public class FullTimeEmployee extends Employee {
// ...
#override
FullTimeEmployee asFullTimeEmployee() {
return this;
}
}

How to forbid creation of two class objects with the same parameters?

friends and colleagues. I have following class:
public class Department {
private String departmentName;
private int moneyForDepartment;
public Department(String departmentName, int moneyForDepartment){
if (moneyForDepartment < 0){
throw new IllegalArgumentException("invalid value");
}
this.departmentName = departmentName;
this.moneyForDepartment = moneyForDepartment;
}
public Department(){
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public int getMoneyForDepartment() {
return moneyForDepartment;
}
public void setMoneyForDepartment(int moneyForDepartment) {
this.moneyForDepartment = moneyForDepartment;
}
}
and for example i will create two objects of this class:
Department dep1 = new Department("Storage", 100000);
Department dep2 = new Department("Storage", 200000);
Please tell me how can i forbid to create class object in case when object of this class with the same first parameter is already exists ?
It's probably not a good idea to do that globally across your entire application (in static state), because then testing becomes difficult. Instead, create a factory for your Department class, maybe looking something like this:
public class Department {
private Department(String departmentName, int moneyForDepartment) { ... }
...
public static class Factory {
private Set<String> seenDepartmentNames = new HashSet<>();
public Department create(String departmentName, int moneyForDepartment) {
if (!seenDepartmentNames.add(departmentName)) {
throw new IllegalArgumentException("Department already created");
}
return new Department(departmentName, moneyForDepartment);
}
}
}
This forces all construction to go through the Factory, since the Department constructor is private. Just use one Factory across your application, and you can still create new ones for testing without running into problems.
You could create a static field in the Department class and keep a Set with the department names already used:
public class Department {
private static final Set<String> usedDepartmentNames = new HashSet<>();
...
}
Then, in the constructor, do:
public Department(String departmentName, int moneyForDepartment) {
if (usedDepartmentNames.contains(departmentName)) {
throw new IllegalArgumentException("Department already exists");
}
if (moneyForDepartment < 0){
throw new IllegalArgumentException("invalid value");
}
this.departmentName = departmentName;
this.moneyForDepartment = moneyForDepartment;
usedDepartmentNames.add(departmentName);
}

#GroupSequenceProvider and group is a superset

Simple class Person.class
class Person {
#NotNull(groups = {PartlyCheck.class})
private String name;
#NotNull(groups = {FullCheck.class})
private String adress;
private boolean isFullCheck;
}
Check interfaces
public interface PartlyCheck{}
public interface FullCheck extends PartlyCheck{}
I use two approach:
if(person.isFullCheck) {
validator.validate(person, FullCheck.class);
else {
validator.validate(person, PartlyCheck.class);
}
1.
If isFullCheck=true used both checks (FullCheck.class and PartlyCheck.class)
If isFullCheck=false used only PartlyCheck.class.
It is an understandable behavior.
#GroupSequenceProvider(PersonGroupSequenceProvider.class)
#Override
public List<Class<?>> getValidationGroups(Person person) {
List<Class<?>> defaultGroupSequence = new ArrayList<>();
defaultGroupSequence.add(Person.class);
if (person.isFullCheck) {
defaultGroupSequence.add(FullCheck.class);
} else {
defaultGroupSequence.add(PartlyCheck.class);
}
return defaultGroupSequence;
}
In the second case, I added #GroupSequenceProvider(PersonGroupSequenceProvider.class).
If isFullCheck=true used only FullCheck.class.
Why extends is not considered for this case?
If isFullCheck=false used only PartlyCheck.class.

Categories