CodeGym /Java Course /Java Collections /Patterns: Adapter, Proxy, Bridge

Patterns: Adapter, Proxy, Bridge

Java Collections
Level 7 , Lesson 2
Available

"Hello, friend!"

"Hi, Bilaabo!"

"We still have some time left, so I'll tell you about three more patterns."

"Three more? How many are there in all?"

"There are currently dozens of popular patterns, but the number of «successful solutions» is unlimited."

"I see. So I have to learn several dozen patterns?"

"Until you have real programming experience, they won't give you much."

"You better get a little more experience, and then, in a year, return to this topic and try to understand them more deeply. At least a couple dozen of the most popular design patterns."

"It's a sin not to use someone else's experience and instead invent something for the 110th time."

"I agree."

"Then let's begin."

Adapter (or wrapper) pattern

Patterns: Adapter, Proxy, Bridge - 1

"Imagine that you come to China and find that the electrical outlets follow a different standard. The holes are not round, but flat. In this case, you'll need an adapter."

"Something similar can also happen in programming. Classes operate on similar but different interfaces. So we need to make an adapter between them."

"This is how it looks:"

Example
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

"Suppose we have two interfaces: Time and TotalTime."

"The Time interface lets you get the current time using the getSeconds(), getMinutes() and getHours() methods."

"The TotalTime interface lets you get the number of seconds that have passed from midnight to the current moment."

"What should we do if we have a TotalTime object, but we need a Time object or vice versa?"

"We can write adapter classes for this. For example:"

Example
 class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
 
Usage
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"And an adapter in the other direction:"

Example
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Usage
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. I like it. But are there any examples?"

"Of course! As an example, InputStreamReader is a classic adapter. It converts an InputStream to a Reader."

"Sometimes this pattern is also called a wrapper, because the new class 'wraps' another object."

"You can read some other interesting things here."

Proxy pattern

"The proxy pattern is somewhat similar to the wrapper pattern. But its purpose is not to convert interfaces, but to control access to the original object stored inside the proxy class. Moreover, both the original class and the proxy usually have the same interface, which makes it easier to replace an object of the original class with a proxy object."

"For example:"

Interface of the real class
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementation of the original class
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementation of the proxy class
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

"In the example above, we described the Bank interface and the CitiBank class, an implementation of this interface."

"The interface lets you get or change a user's account balance."

And then we created BankSecurityProxy, which also implements the Bank interface and stores a reference to a different Bank interface. The methods of this class check whether the user is the account owner or a bank manager. If it is not, then a SecurityException is thrown."

"Here's how it works in practice:"

Code without security checks:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Code with security checks:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"In the first example, we create a bank object and call its setUserMoney method.

"In the second example, we wrap the original bank object in a BankSecurityProxy object. The have the same interface, so subsequent code continues to work as it did. But now a security check will be performed each time a method is called."

"Cool!"

"Yep. You can have many such proxies. For example, you could add another proxy that checks whether the account balance is too large. The bank manager might decide to put a lot of money in his own account and abscond to Cuba with the funds."

"What's more... The creation of all these chains of objects can be put into a BankFactory class, where you can enable/disable the ones you need."

"BufferedReader works using a similar principle. It's a Reader, but it does additional work."

"This approach lets you «assemble» an object with the needed functionality from various «pieces»."

"Oh, I almost forgot. Proxies are used much more widely than what I just showed you. You can read about other uses here."

Bridge pattern

Patterns: Adapter, Proxy, Bridge - 2

"Sometimes as a program runs it is necessary to significantly change an object's functionality. For example, suppose you have a game with a donkey character who is later turned into a dragon by a mage. The dragon has completely different behavior and properties, but it's the same object!"

"Can't we just create a new object and be done with it?"

"Not always. Suppose your donkey is friends with a bunch of characters, or perhaps it was under the influence of several spells, or it was involved in certain quests. In other words, the object may already be in use in lots of places — and linked to lots of other objects. So in this case, it's not an option to simply create a new object."

"Well, what can be done then?"

"The Bridge pattern is one of the most successful solutions."

"This pattern entails splitting an object into two objects: an «interface object» and an «implementation object»."

"What's the difference between the interface and the class that implements it?"

"With an interface and a class, we end up with one object. But here — we have two. Look at this example:"

Example
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

"And then you can declare several subclasses of UserImpl, for example UserDonkey(donkey) and UserDragon(dragon)."

"All the same, I don't really understand how this will work."

"Well, something like this:"

Example
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
How it works
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"So it's something like a proxy."

"Yeah, but in a proxy the main object could be stored somewhere separately, and the code works with proxies instead. Here we're saying that everybody works with the main object, but its parts change internally."

"Ah. Thanks. Will you give me a link to read more about it?"

"Of course, Amigo, my friend. Here you go: Bridge pattern."

Comments (2)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Andrei Level 41
23 July 2021
Bridge pattern looks interesting, looking forward to working with them in real life.
Henrique Level 41, São Paulo, Brazil
5 January 2021
Заместитель (шаблон проектирования)