Design Patterns: Structural Patterns
-
What we’ll cover
- What is a Structural Pattern?
- Facade Pattern
- Decorator Pattern
- Adapter Pattern
-
Structural Patterns
- Eases design by identifying way to realize relationships between entities while reducing code-redundancies
- Some examples:
- Facade
- Decorator
- Adapter
-
Facade Pattern
- provides a simplified interface to a larger body of code, such as a class library.
- prevents client from accessing additional methods that may lead to unexpected user-error.
- limits rather than extends functionality of an encapsulated object.
-
Encountering Facade Necessity
- Consider the case where a client needs to keep a collection of items, but does only needs to publicly expose 6 methods:
add(ObjectType object): voidremove(ObjectType object): voidget(Integer index): ObjectTypesize(): Integerforeach(Consumer<ObjectType> consumer): void
-
Defining a facade
public class ListFacade<T> {
private final List<T> list;
public ListFacade(List<T> list) { this.list = list; }
public ListFacade() { this(new ArrayList<>()); }
public boolean add(T object) { return list.add(object); }
public void remove(T object) { list.remove(object); }
public T get(Integer index) { return list.get(index);
}
public int size() { return list.size(); }
public void foreach(Consumer<T> consumer) { list.forEach(consumer); }
}
-
Using a Facade
- Client can only access composite
listfield by interfacing withListFacade.public void demo() { ListFacade<String> facade = new ListFacade(); facade.add("Hello world"); String firstElement = facade.get(0); System.out.println(firstElement); }
-
Decorator Pattern
- Attach additional responsibilities to an object dynamically
- Provides a flexible alternative to subclassing for extending functionality.
-
Encountering Decorator Necessity
- Consider the case where an application:
- has a notion of a
Profile.Profilegetters and setters for fieldsid,name, andbalance.
- has a notion of a
Player.Playergetters and setters for fieldsid,name,balance, andprofile.
- this proposed design creates redundancies
- has a notion of a
-
Defining Profile
public class Profile {
// field declaration omitted for brevity
public Profile(Integer id, String name, Double balance) {
this.id = id;
this.name = name;
this.balance = balance;
}
// getter and setter definition omitted for brevity
}
-
Defining Player
public class Player {
// field declaration omitted for brevity
public Player(Integer id, String name, Double balance, Profile profile) {
this.id = id;
this.name = name;
this.balance = balance;
this.profile = profile;
}
// getter and setter definition omitted for brevity
}
-
Creating a Decorator
- redundancies from this design are preventable by decorating
Profilewith aPlayer.
public class Player extends Profile {
private Profile profile;
public Player(Profile profile) { this.profile = profile; }
@Override
public Integer getId() { return this.profile.getId(); }
@Override
public String getName() { return this.profile.getName(); }
@Override
public Double getId() { return this.profile.getBalance(); }
}
- notice that getters and setters in
Playerare inherited, but deferred to compositeProfilefield.
-
Adapter Pattern
- Converts interface of a class into another interface expected by client.
- Lets classes work together that couldn’t otherwise because of incompatible interfaces
-
Encountering Adapter Necessity
- Consider the case where a client needs to derive a
Dateobject from aLocalDateobject.
-
Defining an Adapter class
public class TemporalAdapter {
private Date date;
public TemporalAdapter(Date date) {
this.date = date;
}
public LocalDate getLocalDate() {
Instant instant = date.toInstant();
ZoneId zoneId = ZoneId.of("America/New_York");
ZonedDateTime zdt = instant.atZone(zoneId);
LocalDate localDate = zdt.toLocalDate();
return localDate;
}
}
-
Using an Adapter class
public void demo() {
Date date = new Date();
TemporalAdapter adapter = new TemporalAdapter(date);
LocalDate localDate = adapter.getLocalDate();
}
-
