「こんにちは、友達!」
「こんにちは、ビラーボ!」
まだ少し時間があるので、あと3パターンほどお伝えします。
「あと3つ?全部で何つある?」
「現在、人気のパターンは数十ありますが、«成功するソリューション» の数は無限です。」
「なるほど。数十パターン覚えないといけないんですね?」
「本当のプログラミング経験を積まない限り、多くは得られません。」
「もう少し経験を積んでから、1 年後にこのトピックに戻って、より深く理解するように努めた方がよいでしょう。最も人気のあるデザイン パターンを少なくとも 20 個ほど。」
「他人の経験を利用せず、110回目に何かを発明するのは罪だ。」
"同意します。"
「それでは始めましょう。」
アダプター (またはラッパー) パターン
「中国に来て、コンセントが異なる規格に従っていることを想像してみてください。穴は丸くなく、平らです。この場合、アダプターが必要になります。」
「プログラミングでも同様のことが起こります。クラスは似ていますが、異なるインターフェイスで動作します。そのため、クラス間にアダプターを作成する必要があります。」
「見た目はこんな感じです。」
interface Time
{
int getSeconds();
int getMinutes();
int getHours();
}
interface TotalTime
{
int getTotalSeconds();
}
「 Time と TotalTimeという 2 つのインターフェイスがあるとします。」
Time インターフェイスでは、getSeconds ()、 getMinutes ()、および getHours () メソッドを使用して現在時刻を取得できます。
「TotalTimeインターフェイスを使用すると、午前 0 時から現在までの経過秒数を取得できます。」
「 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メソッドを呼び出します。
「2 番目の例では、元の銀行オブジェクトをBankSecurityProxyオブジェクトでラップします。これらは同じインターフェイスを持っているため、後続のコードは引き続き動作します。ただし、メソッドが呼び出されるたびにセキュリティ チェックが実行されるようになります。」
"いいね!"
「そうです。そのような代理人はたくさん持つことができます。たとえば、口座残高が大きすぎるかどうかをチェックする別の代理人を追加することができます。銀行の支店長は自分の口座に大金を預け、その資金を持ってキューバに逃亡することを決めるかもしれません」 。」
「さらに... これらすべてのオブジェクト チェーンの作成をBankFactoryクラスに入れることができ、そこで必要なものを有効/無効にすることができます。」
「BufferedReader も同様の原理で動作します。これはReaderですが、追加の作業を行います。」
「このアプローチにより、さまざまな「部品」から必要な機能を備えたオブジェクトを「組み立て」ることができます。」
「ああ、忘れるところでした。プロキシは、先ほど紹介したものよりもはるかに広く使用されています。他の使用方法については、ここで読むことができます。」
ブリッジパターン
「プログラムの実行中に、オブジェクトの機能を大幅に変更する必要がある場合があります。たとえば、ロバのキャラクターが後に魔術師によってドラゴンに変えられるゲームがあるとします。ドラゴンはまったく異なる動作と特性を持っていますが、同じ物体だ!」
「新しいオブジェクトを作成してそれで終わりではないでしょうか?」
「必ずしもそうとは限りません。あなたのロバがたくさんのキャラクターの友達であるか、おそらくいくつかの呪文の影響下にあるか、特定のクエストに関与しているとします。言い換えれば、そのオブジェクトはすでに多くの場所で使用されている可能性があります—そして、他の多くのオブジェクトにリンクされています。したがって、この場合、単純に新しいオブジェクトを作成するという選択肢はありません。」
「それでは何ができるでしょうか?」
「ブリッジ パターンは、最も成功したソリューションの 1 つです。」
「このパターンでは、オブジェクトを 2 つのオブジェクト、«インターフェイス オブジェクト» と «実装オブジェクト» に分割する必要があります。」
「インターフェイスとそれを実装するクラスの違いは何ですか?」
「インターフェイスとクラスを使用すると、最終的には 1 つのオブジェクトになります。しかし、ここには 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
「つまり、代理のようなものです。」
「そうです。でも、プロキシではメイン オブジェクトを別の場所に保存することができ、コードは代わりにプロキシを使用して動作します。ここでは、誰もがメイン オブジェクトを使用して動作しますが、その部分は内部で変更されると言っています。」
「ああ、ありがとうございます。詳細を読むためのリンクを教えていただけますか?」
「もちろんですよ、友人のアミーゴ。どうぞ。ブリッジパターンです。」
GO TO FULL VERSION