Took a while to figure out this answer and what it really means. A few examples should make it more clear.
Proxy
first:
public interface Authorization {
String getToken();
}
And :
// goes to the DB and gets a token for example
public class DBAuthorization implements Authorization {
@Override
public String getToken() {
return "DB-Token";
}
}
And there is a caller of this Authorization
, a pretty dumb one:
class Caller {
void authenticatedUserAction(Authorization authorization) {
System.out.println("doing some action with : " + authorization.getToken());
}
}
Nothing un-usual so far, right? Obtain a token from a certain service, use that token. Now comes one more requirement to the picture, add logging: meaning log the token every time. It's simple for this case, just create a Proxy
:
public class LoggingDBAuthorization implements Authorization {
private final DBAuthorization dbAuthorization = new DBAuthorization();
@Override
public String getToken() {
String token = dbAuthorization.getToken();
System.out.println("Got token : " + token);
return token;
}
}
How would we use that?
public static void main(String[] args) {
LoggingDBAuthorization loggingDBAuthorization = new LoggingDBAuthorization();
Caller caller = new Caller();
caller.authenticatedUserAction(loggingDBAuthorization);
}
Notice that LoggingDBAuthorization
holds an instance of DBAuthorization
. Both LoggingDBAuthorization
and DBAuthorization
implement Authorization
.
- A proxy will hold some concrete implementation (
DBAuthorization
) of the base interface (Authorization
). In other words a Proxy knows exactly what is being proxied.
Decorator
:
It starts pretty much the same as Proxy
, with an interface:
public interface JobSeeker {
int interviewScore();
}
and an implementation of it:
class Newbie implements JobSeeker {
@Override
public int interviewScore() {
return 10;
}
}
And now we want to add a more experienced candidate, that adds it's interview score plus the one from another JobSeeker
:
@RequiredArgsConstructor
public class TwoYearsInTheIndustry implements JobSeeker {
private final JobSeeker jobSeeker;
@Override
public int interviewScore() {
return jobSeeker.interviewScore() + 20;
}
}
Notice how I said that plus the one from another JobSeeker, not Newbie
. A Decorator
does not know exactly what it is decorating, it knows just the contract of that decorated instance (it knows about JobSeeker
). Take note here that this is unlike a Proxy
; that, in contrast, knows exactly what it is decorating.
You might question if there is actually any difference between the two design patterns in this case? What if we tried to write the Decorator
as a Proxy
?
public class TwoYearsInTheIndustry implements JobSeeker {
private final Newbie newbie = new Newbie();
@Override
public int interviewScore() {
return newbie.interviewScore() + 20;
}
}
This is definitely an option and highlights how close these patterns are; they are still intended for different scenarios as explained in the other answers.