“朋友你好!”

“嗨,比拉博!”

“還有一些時間,我再告訴你三種模式。”

“還有三個?一共有多少個?”

“目前流行的模式有幾十種,但《成功解法》的數量是無限的。”

“原來如此,所以我要學幾十種紋路?”

“除非你有真正的編程經驗,否則他們不會給你太多。”

“你最好多積累一點經驗,然後,一年後,回到這個話題,試著更深入地理解它們。至少有幾十種最流行的設計模式。”

“不利用別人的經驗而是第 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類,它是該接口的一個實現。”

“該界面可讓您獲取或更改用戶的帳戶餘額。”

然後我們創建了BankSecurityProxy,它也實現了Bank接口並存儲了對不同 Bank 接口的引用。此類的方法檢查用戶是帳戶所有者還是銀行經理。如果不是,則拋出 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

“所以它有點像代理。”

“是的,但在代理中,主要對象可以單獨存儲在某個地方,代碼與代理一起工作。這裡我們說每個人都使用主要對象,但它的部分在內部發生變化。”

“啊。謝謝。你能給我一個鏈接來閱讀更多嗎?”

“當然,阿米戈,我的朋友。給你:橋樑模式。”