Composite Pattern
The Composite Pattern organizes objects into tree structures to represent part-whole hierarchies. It treats individual objects and compositions uniformly, allowing clients to work with complex structures through a common interface.
Consider a university department structure where employees form a hierarchy: Department Chairs manage Faculty Members, who in turn supervise Teaching Assistants. This natural hierarchy aligns well with the Composite Pattern.
Implementation Example
interface UniversityMember {
void showDetails();
void addMember(UniversityMember member);
void removeMember(UniversityMember member);
List<universitymember> getMembers();
}
class Employee implements UniversityMember {
private String employeeName;
private String role;
private List<universitymember> teamMembers;
public Employee(String name, String position) {
this.employeeName = name;
this.role = position;
this.teamMembers = new ArrayList<>();
}
@Override
public void showDetails() {
System.out.println("Employee: " + employeeName + ", Role: " + role);
}
@Override
public void addMember(UniversityMember member) {
teamMembers.add(member);
}
@Override
public void removeMember(UniversityMember member) {
teamMembers.remove(member);
}
@Override
public List<universitymember> getMembers() {
return teamMembers;
}
}
public class CompositeImplementation {
public static void main(String[] args) {
UniversityMember chair = new Employee("Dr. Smith", "Department Chair");
UniversityMember professor = new Employee("Dr. Johnson", "Professor");
UniversityMember assistant = new Employee("Mr. Davis", "Teaching Assistant");
professor.addMember(assistant);
chair.addMember(professor);
chair.showDetails();
chair.getMembers().forEach(member -> {
member.showDetails();
member.getMembers().forEach(subMember -> {
subMember.showDetails();
});
});
}
}
</universitymember></universitymember></universitymember>
Output
Employee: Dr. Smith, Role: Department Chair
Employee: Dr. Johnson, Role: Professor
Employee: Mr. Davis, Role: Teaching Assistant
Benefits and Limitations
Advantages: Simplifies client code working with complex hierarchies and makes adding new components straightforward.
Limitations: Violates the Dependency Inversion Principle when child component are concrete implementations rather than interfaces.
Filter Pattern
The Filter Pattern enables object filtering based on different criteria through decoupled, chainable operations. This structural pattern combines multiple criteria to form complex filters.
In a student database scenario, we might need to filter students by various attributes: major, year, GPA range, or combinations thereof. The Filter Pattern provides a flexible solution.
Implementation Example
class Scholar {
private String scholarName;
private String major;
private int year;
private double gpa;
public Scholar(String name, String field, int year, double grade) {
this.scholarName = name;
this.major = field;
this.year = year;
this.gpa = grade;
}
public String getMajor() { return major; }
public int getYear() { return year; }
public double getGPA() { return gpa; }
@Override
public String toString() {
return scholarName + " - " + major + ", Year: " + year + ", GPA: " + gpa;
}
}
interface Criteria {
List<scholar> meetCriteria(List<scholar> scholars);
}
class MajorCriteria implements Criteria {
private String field;
public MajorCriteria(String academicField) {
this.field = academicField;
}
@Override
public List<scholar> meetCriteria(List<scholar> scholars) {
return scholars.stream()
.filter(s -> s.getMajor().equalsIgnoreCase(field))
.collect(Collectors.toList());
}
}
class YearCriteria implements Criteria {
private int academicYear;
public YearCriteria(int year) {
this.academicYear = year;
}
@Override
public List<scholar> meetCriteria(List<scholar> scholars) {
return scholars.stream()
.filter(s -> s.getYear() == academicYear)
.collect(Collectors.toList());
}
}
class AndCriteria implements Criteria {
private Criteria firstCondition;
private Criteria secondCondition;
public AndCriteria(Criteria first, Criteria second) {
this.firstCondition = first;
this.secondCondition = second;
}
@Override
public List<scholar> meetCriteria(List<scholar> scholars) {
return secondCondition.meetCriteria(firstCondition.meetCriteria(scholars));
}
}
class OrCriteria implements Criteria {
private Criteria firstCondition;
private Criteria secondCondition;
public OrCriteria(Criteria first, Criteria second) {
this.firstCondition = first;
this.secondCondition = second;
}
@Override
public List<scholar> meetCriteria(List<scholar> scholars) {
List<scholar> firstResult = firstCondition.meetCriteria(scholars);
List<scholar> secondResult = secondCondition.meetCriteria(scholars);
secondResult.forEach(scholar -> {
if (!firstResult.contains(scholar)) {
firstResult.add(scholar);
}
});
return firstResult;
}
}
public class FilterImplementation {
public static void main(String[] args) {
List<scholar> scholars = Arrays.asList(
new Scholar("Alice", "Computer Science", 2, 3.8),
new Scholar("Bob", "Physics", 3, 3.5),
new Scholar("Carol", "Computer Science", 3, 3.9),
new Scholar("David", "Mathematics", 2, 3.2),
new Scholar("Eve", "Computer Science", 2, 3.6)
);
Criteria csMajor = new MajorCriteria("Computer Science");
Criteria secondYear = new YearCriteria(2);
Criteria csAndSecondYear = new AndCriteria(csMajor, secondYear);
Criteria csOrSecondYear = new OrCriteria(csMajor, secondYear);
System.out.println("CS Majors: " + csMajor.meetCriteria(scholars));
System.out.println("Second Year: " + secondYear.meetCriteria(scholars));
System.out.println("CS Second Year: " + csAndSecondYear.meetCriteria(scholars));
System.out.println("CS or Second Year: " + csOrSecondYear.meetCriteria(scholars));
}
}
</scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar></scholar>
Output
CS Majors: [Alice - Computer Science, Year: 2, GPA: 3.8, Carol - Computer Science, Year: 3, GPA: 3.9, Eve - Computer Science, Year: 2, GPA: 3.6]
Second Year: [Alice - Computer Science, Year: 2, GPA: 3.8, David - Mathematics, Year: 2, GPA: 3.2, Eve - Computer Science, Year: 2, GPA: 3.6]
CS Second Year: [Alice - Computer Science, Year: 2, GPA: 3.8, Eve - Computer Science, Year: 2, GPA: 3.6]
CS or Second Year: [Alice - Computer Science, Year: 2, GPA: 3.8, Carol - Computer Science, Year: 3, GPA: 3.9, Eve - Computer Science, Year: 2, GPA: 3.6, David - Mathematics, Year: 2, GPA: 3.2]
Pattern Benefits
The Filter Pattern provides flexibility in defining and combining criteria while maintaining separation between filtering logic and business objects.