"안녕, 친구!"

"안녕, 빌라보!"

"아직 시간이 좀 남아서 세 가지 패턴을 더 알려줄게."

"3개 더? 모두 몇 개야?"

"현재 인기 있는 패턴은 수십 가지가 있지만 «성공적인 솔루션»의 수는 무제한입니다."

"그렇구나. 그럼 수십 가지의 패턴을 배워야 하는 거지?"

"실제 프로그래밍 경험이 있을 때까지 그들은 당신에게 많은 것을 주지 않을 것입니다."

"조금 더 많은 경험을 쌓은 다음 1년 후에 이 주제로 돌아가서 더 깊이 이해하려고 노력하는 것이 좋습니다. 적어도 수십 개의 가장 인기 있는 디자인 패턴입니다."

"다른 사람의 경험을 활용하지 않고 110번이나 무언가를 발명하는 것은 죄입니다."

"나는 동의한다."

"그럼 시작하겠습니다."

어댑터(또는 래퍼) 패턴

패턴: 어댑터, 프록시, 브리지 - 1

"당신이 중국에 와서 전기 콘센트가 다른 표준을 따른다고 상상해 보세요. 구멍은 둥글지 않고 평평합니다. 이 경우 어댑터가 필요합니다."

"프로그래밍에서도 비슷한 일이 발생할 수 있습니다. 클래스는 비슷하지만 다른 인터페이스에서 작동합니다. 따라서 클래스 간에 어댑터를 만들어야 합니다."

"이렇게 생겼습니다."

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

interface TotalTime
{
 int getTotalSeconds();
}

" Time  과  TotalTime이라는 두 개의 인터페이스가 있다고 가정합니다 ."

"Time 인터페이스를 사용하면 getSeconds (),  getMinutes () 및  getHours () 메서드를 사용하여 현재 시간을 얻을 수 있습니다."

" TotalTime 인터페이스를 사용하면 자정부터 현재 순간까지 경과한 초 수를 얻을 수 있습니다."

" TotalTime 개체가 있지만 Time 개체가 필요 하거나 그 반대인 경우 어떻게 해야 합니까 ?"

"이를 위해 어댑터 클래스를 작성할 수 있습니다. 예를 들면 다음과 같습니다."

 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
 }
}
 
용법
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"그리고 다른 방향의 어댑터:"

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();
 }
}
용법
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"아. 좋아요. 그런데 예문은 없나요?"

"물론입니다! 예를 들어 InputStreamReader는 클래식 어댑터입니다. InputStream을 Reader로 변환합니다."

"새 클래스가 다른 개체를 '래핑'하기 때문에 때때로 이 패턴을 래퍼라고도 합니다."

" 여기에서 다른 흥미로운 내용을 읽을 수 있습니다 ."

프록시 패턴

"프록시 패턴은 래퍼 패턴과 다소 비슷합니다. 그러나 그 목적은 인터페이스를 변환하는 것이 아니라 프록시 클래스 내부에 저장된 원래 개체에 대한 액세스를 제어하는 ​​것입니다. 게다가 원래 클래스와 프록시는 일반적으로 동일한 인터페이스를 가지며, 그러면 원래 클래스의 개체를 프록시 개체로 쉽게 바꿀 수 있습니다."

"예를 들어:"

실제 클래스의 인터페이스
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
원래 클래스의 구현
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
프록시 클래스의 구현
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);
 }
}

"위의 예에서 우리는 Bank 인터페이스와 이 인터페이스의 구현인 CitiBank 클래스를 설명했습니다."

"인터페이스를 사용하면 사용자 계정 잔액을 가져오거나 변경할 수 있습니다."

그런 다음 Bank 인터페이스를 구현하고 다른 Bank 인터페이스에 대한 참조를 저장하는 BankSecurityProxy 를 만들었습니다 . 이 클래스의 메서드는 사용자가 계좌 소유자인지 은행 관리자인지 확인합니다. 그렇지 않으면 SecurityException이 발생합니다."

"실제로 작동하는 방법은 다음과 같습니다."

보안 검사가 없는 코드 :
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
보안 검사 가 포함된 코드 :
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"첫 번째 예에서는 은행 개체를 만들고 setUserMoney 메서드를 호출합니다.

"두 번째 예에서는 원본 은행 개체를 BankSecurityProxy 개체 에 래핑합니다 . 동일한 인터페이스를 가지므로 후속 코드는 이전과 같이 계속 작동합니다. 하지만 이제는 메서드가 호출될 때마다 보안 검사가 수행됩니다."

"시원한!"

"네. 그러한 프록시를 많이 가질 수 있습니다. 예를 들어 계좌 잔액이 너무 큰지 확인하는 다른 프록시를 추가할 수 있습니다. 은행 관리자는 자신의 계좌에 많은 돈을 넣어 자금을 가지고 쿠바로 도망갈 수 있습니다. ."

"게다가... 이러한 모든 개체 체인의 생성은 필요한 항목을 활성화/비활성화할 수 있는 BankFactory 클래스에 넣을 수 있습니다."

" BufferedReader 는 유사한 원리를 사용하여 작동합니다. Reader 이지만 추가 작업을 수행합니다."

"이 접근 방식을 사용하면 다양한 «조각»에서 필요한 기능을 가진 개체를 «조립»할 수 있습니다."

"아, 잊을 뻔 했어요. 프록시는 방금 보여드린 것보다 훨씬 더 광범위하게 사용됩니다. 여기에서 다른 용도에 대해 읽을 수 있습니다 ."

브리지 패턴

패턴: 어댑터, 프록시, 브리지 - 2

"때때로 프로그램이 실행될 때 개체의 기능을 크게 변경해야 하는 경우가 있습니다. 예를 들어 당나귀 캐릭터가 나중에 마법사에 의해 용으로 변하는 게임이 있다고 가정해 보겠습니다. 용은 완전히 다른 동작과 속성을 가지고 있지만 같은 물건!"

"그냥 새 개체를 만들고 끝낼 수는 없나요?"

"항상 그런 것은 아닙니다. 당신의 당나귀가 많은 캐릭터와 친구이거나, 여러 주문의 영향을 받았거나, 특정 퀘스트에 관여했다고 가정해 봅시다. 즉, 해당 개체는 이미 많은 곳에서 사용되고 있을 수 있습니다 — 다른 많은 개체에 연결되어 있습니다. 따라서 이 경우 단순히 새 개체를 만드는 옵션이 아닙니다."

"글쎄, 그럼 어떻게 할 수 있니?"

"브리지 패턴은 가장 성공적인 솔루션 중 하나입니다."

"이 패턴은 개체를 «인터페이스 개체»와 «구현 개체»의 두 개체로 분할하는 것을 수반합니다."

"인터페이스와 이를 구현하는 클래스의 차이점은 무엇입니까?"

"인터페이스와 클래스를 사용하면 하나의 객체로 끝납니다. 하지만 여기에는 두 개가 있습니다. 이 예를 보십시오."

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()
 {
 }
}

"그런 다음 UserImpl의 여러 하위 클래스, 예를 들어 UserDonkey (당나귀) 및 UserDragon (용)을 선언할 수 있습니다."

"그래도 이게 어떻게 될지 잘 모르겠어."

"음, 다음과 같습니다."

class User
{
 private UserImpl realUser;

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

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

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
작동 방식
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"그래서 프록시와 같은 것입니다."

"예, 하지만 프록시에서 메인 개체는 별도로 어딘가에 저장될 수 있고 코드는 대신 프록시와 함께 작동합니다. 여기서 우리는 모든 사람이 메인 개체로 작업하지만 그 부분은 내부적으로 변경된다는 것을 의미합니다."

"아. 감사합니다. 자세한 내용을 읽을 수 있는 링크를 주시겠습니까?"

"물론이죠, 아미고, 친구. 여기요: 브리지 패턴 ."